Android里帐户同步的实现
来源:互联网 发布:c语言指令命令大全 编辑:程序博客网 时间:2024/06/07 19:31
简介
我们在手机系统设置里会有“帐户”一项功能,点击进去会发现有一些我们认识的APP在里头,如QQ、微信、邮箱等。没错,这就是Android系统提供的帐户同步功能。任何第三方APP都可以通过此功能将数据在一定时间内同步到服务器中去。比如微信会将当前系统中登录的微信帐户的信息、聊天记录同步到微信服务器上去,又比如Android原生系统中可以使用Google账号进行数据的同步。这里顺便提一下,系统在将APP帐户同步时,也会将深睡中的APP进程拉活,这点我们在文章后面也会说到。好了,下面我们将一步一步地来实现一个帐户同步的Demo。
创建帐户服务
第一步,定义一个action为android.accounts.AccountAuthenticator的Intent的Service,并在meta-data的resource属性指定一定说明该Account基本显示信息的xml文件,AndroidMainifest.xml添加代码如:
<service android:name="project.test.com.myapplication.AuthenticationService" android:enabled="true" android:exported="true"> <intent-filter> <actionandroid:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /></service>authenticator.xml:
<?xml version="1.0"encoding="utf-8"?><account-authenticatorxmlns:android="http://schemas.android.com/apk/res/android" android:accountType="project.test.com.myapplication.account.type" android:icon="@mipmap/ic_launcher" android:smallIcon="@mipmap/ic_launcher" android:label="@string/app_name" />
注意:
android:accountType表示的是您的Account的类型,它必须是唯一的
第二步,创建帐户Service,并在Service的onBind中调AbstractAccountAuthenticator的getIBinder()返回其用于远程调用的IBinder:
public class AuthenticationService extends Service { privateAuthenticationService.AccountAuthenticator mAuthenticator; privateAuthenticationService.AccountAuthenticator getAuthenticator() { if(mAuthenticator == null) mAuthenticator = new AuthenticationService.AccountAuthenticator(this); returnmAuthenticator; } @Override public voidonCreate() { mAuthenticator = new AuthenticationService.AccountAuthenticator(this); } @Nullable @Override publicIBinder onBind(Intent intent) { returngetAuthenticator().getIBinder(); } classAccountAuthenticator extends AbstractAccountAuthenticator { publicAccountAuthenticator(Context context) { super(context); } @Override publicBundle editProperties(AccountAuthenticatorResponse response, StringaccountType) { returnnull; } @Override publicBundle addAccount(AccountAuthenticatorResponse response, String accountType,String authTokenType, String[] requiredFeatures, Bundle options) throwsNetworkErrorException { return null; } @Override publicBundle confirmCredentials(AccountAuthenticatorResponse response, Accountaccount, Bundle options) throws NetworkErrorException { return null; } @Override publicBundle getAuthToken(AccountAuthenticatorResponse response, Account account,String authTokenType, Bundle options) throws NetworkErrorException { return null; } @Override publicString getAuthTokenLabel(String authTokenType) { return null; } @Override publicBundle updateCredentials(AccountAuthenticatorResponse response, Accountaccount, String authTokenType, Bundle options) throws NetworkErrorException { return null; } @Override publicBundle hasFeatures(AccountAuthenticatorResponse response, Account account,String[] features) throws NetworkErrorException { return null; } }}
说明:
我们在AuthenticationService类中定义了一个继承于AbstractAccountAuthenticator的内部类AccountAuthenticator,然后在AuthenticationService的onBind方法中返回了AccountAuthenticator类对象的getIBinder()。
通过这简单的两步,我们运行程序后再在“设置”-“帐户”-“添加帐户”中的列表中可以发现到我们的app了。
如图:
添加帐户
您会发现,像微信,如果当前手机微信没有登录过的话,在帐户列表中点击微信会自动添转到一个登录微信的Activity,但点击我们刚才app是跳转到一个空白没有任务操作,那是因为我们只是创建了帐户服务,还没有对其进行添加帐户的代码处理。
第一步,声明两个必要的权限:
<uses-permissionandroid:name="android.permission.GET_ACCOUNTS" /> // 查看帐户需要权限<uses-permissionandroid:name="android.permission.AUTHENTICATE_ACCOUNTS" /> // 添加帐户需要权限
第二步,添加AuthenticatorActivity:
public class AuthenticatorActivity extendsAppCompatActivity { publicstatic final String ACCOUNT_TYPE ="project.test.com.myapplication.account.type"; // TYPE必须与account_preferences.xml中的TYPE保持一致 privateAccountManager mAccountManager; @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_authenticator); mAccountManager = (AccountManager)getSystemService(ACCOUNT_SERVICE); Account[] accounts =mAccountManager.getAccountsByType(ACCOUNT_TYPE); // 获取系统帐户列表中已添加的帐户是否存在我们的帐户,用TYPE做为标识 if(accounts.length > 0) { Toast.makeText(this, "已添加当前登录的帐户",Toast.LENGTH_SHORT).show(); finish(); } ButtonbtnAddAccount = (Button)findViewById(R.id.btn_add_account); btnAddAccount.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Account account = new Account(getString(R.string.app_name),ACCOUNT_TYPE); mAccountManager.addAccountExplicitly(account, null, null); // 帐户密码和信息这里用null演示 finish(); } }); }}
说明:
1、Activity中定义的成员变量ACCOUNT_TYPE用于我们当前APP获取系统帐户的唯一标识,这个在前面account_preferences.xml中也有提过,两处的声明必须是一致;
2、在onCreate中去获取系统帐户列表中已添加的帐户是否存在我们的帐户,用我们的TYPE做为标识,如果存在表示已经添加过,则退出当前页面;
3、如若从未添加过帐户,则模拟了一个登户操作,在操作成功后使用AccountManager的addAccountExplicitly方法往系统帐户中添加我们的帐户。
第三步,在AccountAuthenticator类中的addAccount方法里添加跳转登录的Activity:
@Overridepublic Bundle addAccount(AccountAuthenticatorResponseresponse, String accountType, String authTokenType, String[] requiredFeatures,Bundle options) throwsNetworkErrorException { final Bundle bundle = new Bundle(); final Intent intent = new Intent(mContext, AuthenticatorActivity.class); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,response); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle;}
我们再次运行程序,在“添加帐户”的点击和模拟登录后,便可在“设置”-“帐户”中看到我们的APP已添加了帐户了,如图:
同步帐户数据
当我们都完成了上面添加帐户的操作后,试着在帐户中点击会跳转到一个页面,如图:
这个页面只是列出了我们APP的图标和名称,并没有其它的操作和我们预期中的同步功能。所以接下来我们继续为APP加入同步的服务代码。
第一步,定义一个action为android.content.SyncAdapter的Intent的Service,并在meta-data的resource属性指定一定说明该同步基本显示信息的xml文件,AndroidMainifest.xml添加代码如:
<service android:name=".SyncService" android:exported="true"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" /></service>
sync_adapter.xml:
<?xml version="1.0"encoding="utf-8"?><sync-adapterxmlns:android="http://schemas.android.com/apk/res/android" android:accountType="project.test.com.myapplication.account.type" android:allowParallelSyncs="false" android:contentAuthority="project.test.com.myapplication.account.provide" android:isAlwaysSyncable="true" android:supportsUploading="false"android:userVisible="true"/>说明:
android:accountType表示的是您的Account的类型,一定要跟前面保持一致
android:allowParallelSyncs 是否支持多账号同时同步
android:contentAuthority 指定要同步的ContentProvider
android:isAlwaysSyncable 设置所有账号的isSyncable为1
android:supportsUploading 设置是否必须notifyChange通知才能同步
android:userVisible 设置是否在“设置”中显示
第二步,创建同步Service,并在Service的onBind中调AbstractThreadedSyncAdapter的getIBinder()返回其用于远程调用的IBinder:
public class SyncService extends Service { privatestatic final Object sSyncAdapterLock = new Object(); privatestatic SyncAdapter sSyncAdapter = null; @Override public voidonCreate() { synchronized (sSyncAdapterLock) { if(sSyncAdapter == null) { sSyncAdapter = new SyncAdapter(getApplicationContext(), true); } } } @Nullable @Override public IBinderonBind(Intent intent) { returnsSyncAdapter.getSyncAdapterBinder(); } classSyncAdapter extends AbstractThreadedSyncAdapter { publicSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } @Override publicvoid onPerformSync(Account account, Bundle extras, String authority,ContentProviderClient provider, SyncResult syncResult) { //TODO 实现数据同步 // getContext().getContentResolver().notifyChange(AccountProvider.CONTENT_URI,null, false); } }}
说明:
1、我们在SyncService类中定义了一个继承于AbstractThreadedSyncAdapter的内部类SyncAdapter,然后在SyncService的onBind方法中返回了SyncAdapter类对象的getIBinder()。
2、SyncAdapter类中onPerformSync方法就是实现同步数据到服务器的代码,这里省略
第三步,上面我们在sync_adapter.xml中指定了一个ContentProvider,所以此刻当然是创建用于上传的ContentProvider:
<provider android:name=".AccountProvider" android:authorities="project.test.com.myapplication.account.provide" android:exported="false" android:syncable="true"/>
public class AccountProvider extends ContentProvider{ publicstatic final String AUTHORITY ="project.test.com.myapplication.account.provide"; publicstatic final String CONTENT_URI_BASE = "content://" + AUTHORITY; publicstatic final String TABLE_NAME = "data"; publicstatic final Uri CONTENT_URI = Uri.parse(CONTENT_URI_BASE + "/" +TABLE_NAME); @Override publicboolean onCreate() { returnfalse; } @Nullable @Override publicCursor query(Uri uri, String[] projection, String selection, String[]selectionArgs, String sortOrder) { returnnull; } @Nullable @Override publicString getType(Uri uri) { returnnull; } @Nullable @Override public Uriinsert(Uri uri, ContentValues values) { returnnull; } @Override public intdelete(Uri uri, String selection, String[] selectionArgs) { return0; } @Override public intupdate(Uri uri, ContentValues values, String selection, String[] selectionArgs){ return0; }}
说明:
这里我们只是用于演示同步数据,并没有对ContentProvider进行实际操作,大家在实际开发中可以自行去完善。
最后一步,执行同步。我们来修改AuthenticatorActivity的模拟登录代码,使在在登录成功后将其设置为自动同步:
……Button btnAddAccount =(Button)findViewById(R.id.btn_add_account);btnAddAccount.setOnClickListener(newView.OnClickListener() { @Override public voidonClick(View v) { Accountaccount = new Account(getString(R.string.app_name), ACCOUNT_TYPE); mAccountManager.addAccountExplicitly(account, null, null); // 帐户密码和信息这里用null演示 // 自动同步 Bundle bundle= new Bundle(); ContentResolver.setIsSyncable(account, AccountProvider.AUTHORITY, 1); ContentResolver.setSyncAutomatically(account, AccountProvider.AUTHORITY,true); ContentResolver.addPeriodicSync(account, AccountProvider.AUTHORITY,bundle, 30); // 间隔时间为30秒 // 手动同步 //ContentResolver.requestSync(account, AccountProvider.AUTHORITY, bundle); finish(); }});
说明:
账户信息同步其实有两种方式,一种是自动同步,就是在预设定的时间间隔,让Android系统帮我们完成自动同步数据。这里要注意的是,因为Android本身为了考虑同步所带来的消耗和减少唤醒设备的次数,所以这里预设的时间并一定准确,系统其实在内部做了一些同步的算法处理,例如尽量将所有需要同步数据都安排在某个时间点,所以我们预设的那个时间值仅仅是作为一个参考罢了。另一种方式是手动同步,手动同步则是在应用中调用某个方法直接告诉设备,通知系统同步数据。
当我们再次在已添加的帐户列表中点击我们的APP后会变成这样,如图:
更改同步页面
如果您希望在已添加的帐户列表中点击我们的APP后出现一个自定义的页面,那么就得
修改authenticator.xml文件,加入accountPreferences属性:
<?xml version="1.0"encoding="utf-8"?><account-authenticatorxmlns:android="http://schemas.android.com/apk/res/android" android:accountType="project.test.com.myapplication.account.type" android:icon="@mipmap/ic_launcher" android:smallIcon="@mipmap/ic_launcher" android:label="@string/app_name" android:accountPreferences="@xml/account_preferences"/>
account_preferences.xml文件:
<?xml version="1.0"encoding="utf-8"?><PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategoryandroid:title="PreferenceCategory_title" /> <PreferenceScreen android:key="key1" android:title="PreferenceScreen_title" android:summary="PreferenceScreen_summary"> <intent android:action="key1.ACTION" android:targetPackage="key1.package" android:targetClass="key1.class" /> </PreferenceScreen></PreferenceScreen>
我们再次运行APP,然后往已添加的帐户列表中点击我们的APP后便会出现一个中间页面,如图:
拉活机制
对于某些APP来说,当然都希望自己的进程尽量的不被杀死。于是乎,就出现的各种进程保活的方法,其中帐户同步也算为其中的一种。因为系统帐户自动同步会在特定时间时同步APP数据到服务器上去。上面实现也看到,登录帐户和执行同步帐户数据都是通过我们自己的代码来实现,所以在同步数据时会把已退出或完全结束的APP再次拉活起来。笔者在部分手机中验证过我们今天介绍的Demo,发现效果还是行得通的。当然由于国内手机厂商的各种订制和阉割,也会出现某部分手机是行不通的,大家有时间可以自行去尝试。这点也算是Android系统的一个小漏洞。希望大家能善用帐户同步功能,养成良好的开发信仰,尊重用户意愿。因为并不是所有用户都是愿意让不相关的APP任其后台常驻的!
总结
本文只为简单介绍和演示Android帐户同步的实现步骤,大家如若需要更完整的帐户同步实现,有兴趣可以自行去研究一下在Anroid SDK中samples目录下也有一个叫SampleSyncAdapter的帐号与同步的实例。另外上面介绍的Demo源码可以点击这里下载
- Android里帐户同步的实现
- Android 帐户同步机制
- Android 帐户同步机制
- Android里JobScheduler的实现
- Centos里实现web数据同步的四种方式
- 案例研究:Team Foundation Server的帐户信息同步
- 如何理解Android里的同步和异步的区别
- 如何实现自动把域帐户加到本地管理员组里?如果通过脚本实现
- Android App的国际化-代码里实现
- Android App的国际化-代码里实现
- android工程里,配置文件的实现。
- android实现两个listview的同步滚动
- android实现两个listview的同步滚动
- Domino与AD帐户同步并且与AD域实现SSO
- python里使用socket实现时间同步
- Android 实现歌词同步
- android 歌词同步实现
- Android 实现歌词同步
- deploy hadoop cluster in docker using of sequenceiq/hadoop-docker:2.7.0
- qt实现录音功能
- 【面试题】剑指offer16--反转链表
- 窗体部件之QMdiArea
- web 打印时 表格多页 导致缺少线段 看上去页面不完整的处理方案
- Android里帐户同步的实现
- U-boot中的宏定义
- Xcode文件突然显示不全只显示一部分
- mongo
- Exyons4412音频驱动读写流程
- 【Visual Studio】VS常用调试技巧——笔记
- 算法设计与分析(17)-- Swap Nodes in Pairs(难度:Medium)
- 判断括号是否匹配
- 乐驾 -- 7