Android自定义账户类型和同步适配器模式 Custom Account Type & SyncAdapter

Posted ASleepyCoder

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android自定义账户类型和同步适配器模式 Custom Account Type & SyncAdapter相关的知识,希望对你有一定的参考价值。

自定义账户类型 Custom Account Type

当有多个APP共用一个账号系统的时候,在用户的android设备上创建一个自定义账户用以处理登录认证会方便很多,比如腾讯的QQ,浏览器,应用宝系列,360安全卫士、手机助手系列等都是共用一个账号的,这个账户在系统设置页面的账户管理可以看到。

创建自定义账户可以分三步:

1、创建认证Activity,这个Activity负责和用户交互录入用户账户数据、验证、保存凭证。
2、继承实现AbstractAccountAuthenticator类。
3、创建账户认证的Serivice,。

下面具体介绍如何一步步创建自己的自定义账户:
使用到的权限

<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />

1. 创建LoginActivity类

因为这个Activity要和系统AccountManager交互所以他就有一些一般activity所没有的特定需求。为了方便Android框架提供了一个基础类, AccountAuthenticatorActivity ,通过继承它你可以专注去创建你自己的自定义身份验证。 这个类比较简单,你也可以继承自普通的Activity,把AccountAuthenticatorActivity里面的代码拷贝进去就行。然后就可以和普通Activity一样处理,界面交互完全取决于你自己。

当如果AbstractAccountAuthenticator需要用一个Activity去处理请求,就可以通过传递相应Intent让系统调用这个Activity来处理,关于AbstractAccountAuthenticator后面再讲,先来说说AccountAuthenticatorActivity,查看源码可知,他内部定义了两个私有属性。

private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null;
private Bundle mResultBundle = null;

mAccountAuthenticatorResponse是在onCreate中通过getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE) 获取的。
mResultBundle是需要在finish前手动调用setAccountAuthenticatorResult()方法设置的,作为给启动此activity请求的结果。示例:

Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();

如果没有设置结果或设置为null,在response中将会被当当做error( ERROR_CODE_CANCELED)。

在LoginActivity中使用下面的方法在系统中创建自定义账户

Account account = new Account(name, AccountType);
   accountManager.addAccountExplicitly(account,passwd,null);

其中的AccountType是你自定义的常量,应该与你在manifest.xml中用于账户绑定service里声明的一致,service具体后面详细介绍,如果与声明的不一致会报权限错误。

需要特别注意的是实际使用中不应该把密码明文保存,官方文档是这么说的:

AccountManager 不是一种加密服务。它仅仅按照你传递的内容用”’明文”’来存储。在大部分设备上,这不是问题,因为设备把他们存储在只有根用户才能访问的数据库中。但是在已经被Root过的设备上,通过adb连接可以让任何人读取凭据信息。
因此,你不能直接传递用户的真实密码给AccountManager.addAccountExplicitly()。 相反,你应该存储加密的安全令牌来保障限制攻击者的使用

一般的做法是如果一个设备只能登陆一个账户的话,name用来标识应用程序,而真正的用户名和临时的访问Token使用AccountManger.setUserData()保存在account的额外信息中。

2. 实现AbstractAccountAuthenticator

继承AbstractAccountAuthenticator实现AccountAuthenticator,这个类是和系统通信的具体实现,比如在系统设置的账户管理中新建账户,系统调用的就是这个类中的addAccount方法,通过这个方法可以启动上面创建的认证Activity。
AbstractAccountAuthenticator内部有几个抽象方法,这几个方法分别于AccountManager内部的几个方法一一对应。

//返回一个bundle,这个bundle可以包含一个用于启动编辑账户Properties的Activity的Intent,对应于accountManager.editProperties()
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType)
//返回一个bundle, 包含添加账户Activity的Intent,对应于 accountManager.addAccountExplicitly()
Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)
//检查用户传递过来的凭证 对应于 AccountManager.confirmCredentials()
Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
//获取Token,对应于AccountManager.getAuthToken() 其他进程一般调用这个进行认证
Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
//更新用户凭证 对应于AccountManager.updateCredentials()
Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)
//获取authToken的本地化标签,具体用处我也没搞明白
String getAuthTokenLabel(String authTokenType)
//检查验证account支持的是否支持请求验证的功能
Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)

以上各个方法返回的bundled内容的 Key可以包含下面几种情况:

AccountManager.KEY_INTENT :声明后面要启动的intent
AccountManager.KEY_ERROR_CODE:请求出错的错误码
AccountManager.KEY_ERROR_MESSAGE:请求出错的错误信息
AccountManager.KEY_BOOLEAN_RESULT:请求是否成功
AccountManager.KEY_ACCOUNT_NAME
AccountManager.KEY_ACCOUNT_TYPE
AccountManager.KEY_AUTHTOKEN

等内容或者其他自定义信息。

上面的方法选择必要的方法实现,不需要的功能返回null即可,下面是两个方法实现的例子

@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException 

    final Intent intent = new Intent(mContext, LoginActivity.class);
    intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, authTokenType);
                intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
    final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return bundle;

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException 

    final Bundle result = new Bundle();

    if(authTokenType.equals(AuthTokenType))
        String authToken = "1111111111";//这里应该从服务器获取验证
        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,true);
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountType);
        result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
    else
        result.putInt(AccountManager.KEY_ERROR_CODE,-1);
        result.putString(AccountManager.KEY_ERROR_MESSAGE,"Auth Failed!");
    
    return result;

3. 创建AuthService

这个服务其实是提供给其他的进程使用的,它的Action为android.accounts.AccountAuthenticator,android系统会通过这个Action找到它,并通过它来把我们自己的账号注册到“设置”中,其实这是一个AIDL的使用,它属于跨进程的调用。
它的实现非常简单,在onBind()中返回authenticator.getIBinder()即可。
示例代码:

public class AuthService extends Service 
    private AccountAuthenticator authenticator;
    public AuthService() 
        authenticator = new AccountAuthenticator(this);
    
    @Override
    public IBinder onBind(Intent intent) 
        return authenticator.getIBinder();
    

然后需要在manifest.xml中注册,注册需要遵循一些规则:
1、 action必须注册为android.accounts.AccountAuthenticator,
2、 需要在meta-data中提供具体的的配置信息,这些信息包含在一个xml文件中,
示例如下:

<service ...>
   <intent-filter>
      <action android:name="android.accounts.AccountAuthenticator" />
   </intent-filter>
   <meta-data android:name="android.accounts.AccountAuthenticator"
             android:resource="@xml/authenticator" />
</service>

xml/authenticator.xml的内容如下

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="你的AccountType "
    android:icon="@drawable/icon"
    android:smallIcon="@drawable/miniIcon"
    android:label="@string/label"
    android:accountPreferences="@xml/account_preferences"
 />

其中android:accountPreferences=”@xml/account_preferences”并不是必须的,account_preferences是一个PreferenceScreen的布局文件,可以用来写个跳转到同步设置页面的功能。

至此自定义账户的创建就完成了,在应用中调用AccountManger的相关方法就可以使用了。

同步适配器 SyncAdapter

同步适配器框架(SyncAdapter Framework)是Android提供的一套移动端与服务端数据同步的解决方案,它有以下优点

  • 插件化结构
  • 自动执行
  • 自动检测网络
  • 省电
  • 有账户认证机制

最常见的是用于备份,联系人同步等各种云同步功能。

SyncAdapter依赖于自定义账户、和ContentProvider,即时你的同步功能没有使用账户认证和ContentProvider,也要提供一个虚拟的实现,这是SyncAdapter的必要组件。

下面就来一步步完成一个同步适配器的创建,实现一个SyncAdapter分以下几步:
需要用到的权限

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>

1.创建自定义账户

这个前面已经讲过了,按照前面的完成即可。

2.创建SyncContentProvider

由于ContentProvider不是本文的重点,所以就不详细介绍了,如果不熟的话关于他的详细知识请自行搜索,这里为了演示同步适配器的功能只是把继承自ContentProvider的增删改查方法简单的返回而已。

public class SyncContentProvider extends ContentProvider 
    public SyncContentProvider() 
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) 
        return 0;
    
    @Override
    public String getType(Uri uri) 
        return null;
    
    @Override
    public Uri insert(Uri uri, ContentValues values) 
        return null;
    

    @Override
    public boolean onCreate() 
        return true;
    
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) 
        return null;
    
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) 
        return 0;
    

然后在manifest.xml文件中注册,比普通的provider多了一个android:syncable=”true”的属性

<provider
    android:name=".syncadapter.SyncContentProvider"
    android:authorities="cn.sleepycoder.customaccount.provider"
    android:enabled="true"
    android:exported="true"
    android:syncable="true">
</provider>

其中android:authorities的值后面注册同步服务的时候会用到。

3.创建SyncAdapter类

继承自AbstractThreadedSyncAdapter,并实现其方法。

在构造方法中初始化需要用到的组件,比如初始化一个ContentResolver
除了构造方法外只有一个onPerformSync方法需要实现,这是真正要运行的同步方法,这个方法运行在独立的线程中,其中可以进行联网耗时操作。
这个方法是系统负责调用的,需要注意的是:

  • 如果没有网络,点击手动同步这个方法也不会立即被调用,而是会加入到等待同步的任务中,等到有网络的时候才会运行。
  • 不会有同一个认证账户下的两个相同的同步任务同时存在于系统任务队列的,比如已经有了一个定时的同步任务存在,那么手动调用同步方法也不会运行的。

4.创建用于启动同步任务的服务SyncService

和自定义账户类型一样,它的实现也非常简单,在onBind中返回syncAdapter.getSyncAdapterBinder()即可。
代码示例:

public class SyncService extends Service 
    private static SyncAdapter syncAdapter;
     //用于保证SyncAdapter的单例
    private static final Object sSyncAdapterLock = new Object();
    @Override
    public void onCreate() 
        super.onCreate();
        synchronized (sSyncAdapterLock)
            if(syncAdapter==null) 
                syncAdapter = new SyncAdapter(this, true);
            
        
    
    @Override
    public IBinder onBind(Intent intent) 
        return syncAdapter.getSyncAdapterBinder();
    

在manifest.xml中注册服务:

<service
    android:name=".service.SyncService"
    android:enabled="true"
    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>

其中action和meta-data的name属性都是固定的,meta-data中resource对应的xml文件的写法配置如下:

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="cn.sleepycoder.customaccount"
    android:contentAuthority="cn.sleepycoder.customaccount.provider"
    android:allowParallelSyncs="false"
    android:supportsUploading="false"
    android:isAlwaysSyncable="true"
    android:userVisible="true">
</sync-adapter>
  • android:accountType的值要和自定义账户的一致
  • android:contentAuthority的值要和上面contentProvider注册的android:authorities的值相一致。
  • android:allowParallelSyncs当值为true的时候,如果你的账户类型支持同时多个不同账户存在的话,就可以有多个SyncAdapter的实例,同时运行同步任务,如果不支持多个账户的话设置为false就可以。
  • android:userVisible的作用是指示在在设置的账户管理中,同步开关是否可见。
  • android:isAlwaysSyncable=”true” 系统可以自动同步。

至此同步适配器的配置就完成了,下面讲讲如何调用SyncAdapter的同步功能:
需要调用运行同步适配器的情况一般有下面这四种:

  • 服务端数据改变
    服务端数据改变,推送消息到本地,接收到推送的广播后可以调用
    ContentResolver.requestSync(ACCOUNT, AUTHORITY,bundle);
    其中account是要同步的账户,authority是ContentProvider注册的android:authorities, bundle用来传递一些其他的数据给SyncAdapter
  • 本地数据改变
    本地数据库有改变可以给ContentProvider设置观察者ContentObserver,
    mResolver.registerContentObserver(mUri, true, observer);
    在ContentObserver的监听方法中调用ContentResolver.requestSync(ACCOUNT, AUTHORITY, bundle);
  • 定时同步
    ContentResolver.addPeriodicSync(ACCOUNT,AUTHORITY,Bundle,SYNC_INTERVAL);
  • 用户有同步需要
    原则上不应该由用户进行动同步,但是如果有需求也可以提供交互调用ContentResolver.requestSync(…)进行同步。
    Bundle settingsBundle = new Bundle();
    settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    ettingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
    ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);

Demo下载地址:
http://download.csdn.net/detail/w804518214/9654425

以上是关于Android自定义账户类型和同步适配器模式 Custom Account Type & SyncAdapter的主要内容,如果未能解决你的问题,请参考以下文章

Android AccountManager 账户同步管理简单介绍

Android AccountManager 账户同步管理简单介绍

使用工厂模式创建收件地址ReceiveAddress 订单对象,创建 若干对象 (属性自定义)

使用工厂模式创建收件地址ReceiveAddress 订单对象,创建 若干对象 (属性自定义)

使用工厂模式创建收件地址ReceiveAddress 订单对象,创建 若干对象 (属性自定义)

Android框架设计模式——Adapter Method