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源码可以点击这里下载

 

原创粉丝点击