Android特色开发之账户管理

来源:互联网 发布:excel从网页导入数据 编辑:程序博客网 时间:2024/05/17 07:46

链接:

基础篇

http://yarin.blog.51cto.com/1130898/479032

进阶篇

http://blog.csdn.net/u010002056/article/details/9210157


本文节选于机械工业出版社推出的《Android应用开发揭秘》一书,作者为杨丰盛。本书内容全面,详细讲解了Android框架、Android组件、用户界面开发、游戏开发、数据存储、多媒体开发和网络开发等基础知识,而且还深入阐述了传感器、语音识别、桌面组件开发、Android游戏引擎设计、Android应用优化、OpenGL等高级知识。另外,本书还全面介绍了如何利用原生的C/C++(NDK)和Python、Lua等脚本语言(Android Scripting Environment)来开发Android应用,并以迭代的方式重现了各种常用的Android应用和经典Android游戏的开发全过程。

9.5  账户管理

Android2.0中加入了一个新的包android.accounts,该包主要包括了集中式的账户管理API,用以安全地存储和访问认证的令牌和密码,比如,我们的手机存在多个账户,每个账户下面都有不同的信息,甚至每个账户都可以与不同的服务器之间进行数据同步(例如,手机账户中的联系人可以是一个Gmail账户中的通讯录,可联网进行同步更新)。下面首先来看看android.accounts包提供了哪些功能,如表9-3所示。
表9-3  android.accounts包的功能描述

光看这些介绍,也许会难以理解,下面我们结合一个示例程序来学习android.accounts包中各功能的使用。该示例实现了账户添加功能,可以添加多个账户来集中管理,程序运行界面如图9-27所示,点击“新建账户”按钮后,就可以添加账户的相关信息,如图9-28所示。程序的具体实现请参见本书所附代码:第9章\Examples_09_08。
该示例中一共新建了4个账户,因此在退出程序、点击新建联系人时,会出现如图9-29所示的界面来提示用户选择在哪一个账户中创建联系人,这样使得每个账户独立隔开,又统一管理,非常方便。

图9-29  新建联系人

由于在该示例中对用户账户信息进行了操作,因此首先要确保在AndroidManifest.xml文件中对操作权限进行声明,以及确定API等级为5,代码如下:

  1. <uses-sdk android:minSdkVersion="5"/> 
  2. <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/></uses-permission> 
  3. <uses-permission android:name="android.permission.ACCOUNT_MANAGER"></uses-permission> 
  4. <uses-permission android:name="android.permission.GET_ACCOUNTS"></uses-permission> 
  5. <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"></uses-permission>  

该示例的UI界面布局设计很简单,大家可以参考本书第4章的内容。首先来看一下如图9-27所示的Activity类的实现,我们需要通过AccountManager类的get方法来取得AccountManager对象,代码如下:

  1. //取得AccountManager对象  
  2. AccountManager  _am = AccountManager.get(this);   

在AccountManager中提供了很多方法来供我们管理这些账户,常用方法如表9-4所示。
表9-4  AccountManager中的常用方法

通过这些方法就可以很轻松地操作这些账户数据,比如,将指定类型的账户信息全部列出来,代码如下:

  1. /* 显示出所有账户 */ 
  2. private void listAccounts()  
  3. {  
  4. /* 得到指定类型的账户 */ 
  5. Account[] accounts = _am.getAccountsByType(getString(R.string.ACCOUNT_TYPE));  
  6. _accountList.setText("账户列表:");  
  7. for (Account account : accounts)  
  8. {  
  9.     _accountList.setText(_accountList.getText().toString() + '\n' +   
  10.     account.name + " - " + account.type);  
  11. }  
  12. }  

下面我们重点来学习如何将账户信息添加到账户管理器中。首先,需要实现一个AccountAuthenticatorActivity类来供用户输入账户信息,即AbstractAccountAuthenticator的一个Activity,如代码清单9-15所示。
代码清单9-15  第9章\Examples_09_08\src\com\yarin\android\Examples_09_08\auth\SleepyAccount- AuthenticatorActivity.java

  1. public class SleepyAccountAuthenticatorActivity extends AccountAuthenticatorActivity  
  2. {  
  3. protected void onCreate(Bundle icicle)  
  4. {  
  5.     super.onCreate(icicle);  
  6.     setContentView(R.layout.new_account);  
  7.     final Button done = (Button) findViewById(R.id.new_account_done);  
  8.     final EditText server = (EditText) findViewById(R.id.new_account_server);  
  9.     final EditText username = (EditText) findViewById(R.id.new_account_username);  
  10.     final EditText password = (EditText) findViewById(R.id.new_account_password);  
  11.     final Activity self = this;  
  12.     done.setOnClickListener(new OnClickListener() {  
  13.         public void onClick(View v)  
  14.         {  
  15.             //Account--指定账户名和账户类型  
  16.             Account account=new Account(username.getText().  
  17.             toString(), getString(R.string.ACCOUNT_TYPE));  
  18.             //服务器数据  
  19.             Bundle userdata = new Bundle();   
  20.             userdata.putString("SERVER", server.getText().toString());  
  21.             //取得AccountManager  
  22.             AccountManager am = AccountManager.get(self);  
  23.             //添加一个账户  
  24.             if (am.addAccountExplicitly(account, password.  
  25.             getText().toString(), userdata))  
  26.             {  
  27.                 Bundle result = new Bundle();  
  28.                 result.putString(AccountManager.KEY_ACCOUNT_NAME, username.getText().toString());  
  29.                 result.putString(AccountManager.KEY_ACCOUNT_TYPE,getString(R.string.ACCOUNT_TYPE));  
  30.                 setAccountAuthenticatorResult(result);  
  31.             }  
  32.             finish();  
  33.         }  
  34.     });  
  35. }  
  36. }    

在上述代码清单中,我们先通过账户名及其类型构建一个Account对象,然后将服务器数据通过Bundle方式加入进来,最后通过AccountManager的addAccountExplicitly方法向账户管理器中添加一个账户信息。
接下来需要添加一个账户服务(Service)和一个验证器(AbstractAccountAuthenticator)。
首先,构建一个authenticator.xml,如代码清单9-16所示。
代码清单9-16  第9章\Examples_09_08\res\xml\ authenticator.xml

  1. <?xml version="1.0" encoding="utf-8"?> 
  2.  <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" 
  3.  android:accountType="com.yarin.AccountType" 
  4.  android:icon="@drawable/icon" 
  5.  android:smallIcon="@drawable/icon" 
  6.  android:label="@string/ACCOUNT_LABEL" 
  7.  android:accountPreferences="@xml/account_preferences" 
  8.  />  

然后,在AndroidManifest.xml文件中开启一个账户管理服务,加入如下代码:

  1. <service android:name="SleepyAccountsService"> 
  2. <intent-filter> 
  3. <action android:name="android.accounts.AccountAuthenticator" ></action> 
  4. </intent-filter> 
  5. <meta-data   
  6. android:name="android.accounts.AccountAuthenticator" 
  7. android:resource="@xml/authenticator"> 
  8. </meta-data> 
  9. </service>   

账户服务类的实现很简单,就是在intent.getAction()的动作为android.accounts. AccountManager. ACTION_AUTHENTICATOR_INTENT时,通过AccountAuthenticator的getIBinder方法返回一个IBinder,如代码清单9-17所示。
代码清单9-17  第9章\Examples_09_08\src\com\yarin\android\Examples_09_08\ SleepyAccounts-Service.java

  1. public class SleepyAccountsService extends Service  
  2. {  
  3. private SleepyAccountAuthenticator  _saa;  
  4. public IBinder onBind(Intent intent)  
  5. {  
  6.     IBinder ret = null;  
  7.     if (intent.getAction().equals(android.accounts.AccountManager.  
  8.     ACTION_AUTHENTICATOR_INTENT))  
  9.         ret = getSleepyAuthenticator().getIBinder();  
  10.     return ret;  
  11. }  
  12. private SleepyAccountAuthenticator getSleepyAuthenticator()  
  13. {  
  14.     if (_saa == null)  
  15.         _saa = new SleepyAccountAuthenticator(this);  
  16.     return _saa;  
  17. }  

最后,最重要的是AbstractAccountAuthenticator类的实现,因为在添加、操作账户信息时会通过AbstractAccountAuthenticator实现异步调用。下面是实现的addAccount方法,如代码清单9-18所示。
代码清单9-18  addAccount方法

  1. /* 添加账户 */ 
  2. public Bundle addAccount(AccountAuthenticatorResponse response, String   
  3. accountType, String authTokenType, String[] requiredFeatures, Bundle   
  4. options)throws NetworkErrorException  
  5. {  
  6. Log.d(_tag, accountType + " - " + authTokenType);  
  7. Bundle ret = new Bundle();  
  8. Intent intent=new Intent(_context,SleepyAccountAuthenticatorActivity.class);  
  9. intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);  
  10. ret.putParcelable(AccountManager.KEY_INTENT, intent);  
  11. return ret;  

有关账户管理的内容,可能比较难以理解,建议大家一定要结合本节的示例程序进行学习,同时可以参考Android 2.0 SDK文档。

9.6  小结

本章内容之间的联系不是很紧密,都是一些Android中的特色功能,也正是这些功能吸引了不少开发者和用户。这些特色功能主要包括:Android中传感器的使用、语音识别技术、Google Map API在Android中的使用和出色的桌面组件开发,最后学习了Android中账户管理功能的简单实现。这些功能在日常生活中运用得也比较广泛,比如GPS导航、路径规划等,希望大家着重理解本章的内容,开发出具有创意的应用。

本文出自 “yarin's blog™” 博客,请务必保留此出处http://yarin.blog.51cto.com/1130898/479032




Android 帐户管理 小记

分类: Android 2012-11-23 11:31 845人阅读 评论(1) 收藏 举报

关于Android帐户管理:允许在Android设备上登录相关帐户,如skype,facebook等,可对日历、联系人的数据进行同步。

想要添加一个自己的帐户,本地端要实现两大部分。1是添加帐户的功能。2是同步的功能。

PS:服务器端忽略,SampleSyncAdapter中有相关的服务器代码,未研究。

 

PART.1 添加帐户的功能

首先,我们关注AccountManager,它为我们管理帐户提供了接口,详情见API文档。

常用的接口有

addAccount() :添加一个帐户。

addAccountExplicitly(): 直接添加一个帐户到AccountManager。

getAccounts():获取所有帐户。

removeAccount():删除帐户

--进入正题--

我们想添加一个自己的帐户到设备中,所需权限如下:

[html] view plaincopyprint?
  1. <</SPAN>uses-sdk android:minSdkVersion="5"/> <</SPAN>uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/></</SPAN>uses-permission> <</SPAN>uses-permission android:name="android.permission.ACCOUNT_MANAGER"></</SPAN>uses-permission> <</SPAN>uses-permission android:name="android.permission.GET_ACCOUNTS"></</SPAN>uses-permission> <</SPAN>uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"></</SPAN>uses-permission>   

 

首先需要实现一个AccountAuthenticatorActivity 的类来供用户实现输入。此输入界面即你在setting中点击添加帐户,选择完相应的类型之后跳出来的对话框。

之后,需要添加一个账户服务(Service)和一个验证器(AbstractAccountAuthenticator)。我们首先需要构建一个authenticator.xml

[html] view plaincopyprint?
  1. <</SPAN>account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:accountType="com.example.android.samplesync"  
  3.     android:icon="@drawable/icon"  
  4.     android:smallIcon="@drawable/icon"  
  5.     android:label="@string/label"  
  6. />  


然后,在AndroidManifest.xml文件中AuthenticationService向AccountManager注册一个账户类型。同时,类似widget的声明方式,在meta-data中要声明一个xml,在这个xml中描述账户的类型,图标,显示名,等等 。 这样我们可以在setting中的添加帐户中添加一个自己的帐户类型。

[html] view plaincopyprint?
  1. <</SPAN>service  
  2.     android:name=".authenticator.AuthenticationService"  
  3.     android:exported="true">  
  4.     <</SPAN>intent-filter>  
  5.         <</SPAN>action  
  6.             android:name="android.accounts.AccountAuthenticator" />  
  7.     </</SPAN>intent-filter>  
  8.     <</SPAN>meta-data  
  9.         android:name="android.accounts.AccountAuthenticator"  
  10.         android:resource="@xml/authenticator" />  
  11. </</SPAN>service>  

 

Service的工作其实很简单,代码如下,主要负责返回一个IBinder 接口。

[java] view plaincopyprint?
  1. public class AuthenticationService extends Service  
  2.   
  3.     private static final String TAG "AuthenticationService" 
  4.     private Authenticator mAuthenticator;  
  5.     @Override  
  6.     public void onCreate()  
  7.          mAuthenticator new Authenticator(this);  
  8.      
  9.     @Override  
  10.     public IBinder onBind(Intent intent)  
  11.         return mAuthenticator.getIBinder();  
  12.      
  13.  



最后,最重要的是AbstractAccountAuthenticator类的实现,因为在添加、操作账户信息时会通过AbstractAccountAuthenticator实现异步调用。下面是实现的addAccount方法

[java] view plaincopyprint?
  1. @Override  
  2. public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,  
  3.         String authTokenType, String[] requiredFeatures, Bundle options)  
  4.     Log.v(TAG, "addAccount()");  
  5.     final Intent intent new Intent(mContext, AuthenticatorActivity.class);  
  6.     intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);  
  7.     final Bundle bundle new Bundle();  
  8.     bundle.putParcelable(AccountManager.KEY_INTENT, intent);  
  9.     return bundle;  
  10.  


这个Bundle中都会有个包含登录界面的Activity的Intent,然后通过这个Bundle返回给AccountManager,来启动一个登录界面添加账户。

 

之后在验证完用户名和密码之后添加帐户,代码如下

[java] view plaincopyprint?
  1. private void finishLogin(String authToken)  
  2.   
  3.     Log.i(TAG, "finishLogin()");  
  4.     final Account account new Account(mUsername, Constants.ACCOUNT_TYPE);  
  5.     if (mRequestNewAccount)  
  6.         mAccountManager.addAccountExplicitly(account, mPassword, null);  
  7.         // Set contacts sync for this account.   
  8.         ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);  
  9.     else  
  10.         mAccountManager.setPassword(account, mPassword);  
  11.      
  12.     final Intent intent new Intent();  
  13.     intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);  
  14.     intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);  
  15.     setAccountAuthenticatorResult(intent.getExtras());  
  16.     setResult(RESULT_OK, intent);  
  17.     finish();  
  18.  


直接向AccountManager添加一个帐户,并设置让其能够自动同步。

到此为止,你已经成功添加了一个属于自己的帐户,可是仅仅是一个帐户,可是却没有同谷歌帐户一样的选项去选择同步联系人,同步日历等等的选项,那就是接下去需要实现的第二步SyncAdapter。

 

PART.2 同步的功能

SyncAdapter同样需要一个服务(Service)和一个同步适配器(AbstractThreadedSyncAdapter)。

SyncAdapter的Service 需要在AndroidManifest里面声明一个带有Intent:android.content.SyncAdapter的Service来达到向系统注册一个具有同步功能的账户适配器(sync-adapter)。 同时,类似widget的声明方式,在meta-data中要声明一个xml,在这个xml中描述适配器绑定的账户,所要同步的区域(Authority)(如com.android.contacts,com.android.calendar com.android.email)等信息,一个适配器只能同步一个Authority,若想使一个账户同步多个Authority,可以向系统注册多个绑定同一账户的sync-adapter。

[html] view plaincopyprint?
  1. <</SPAN>service  
  2.             android:name=".syncadapter.SyncService"  
  3.             android:exported="true">  
  4.             <</SPAN>intent-filter>  
  5.                 <</SPAN>action  
  6.                     android:name="android.content.SyncAdapter" />  
  7.             </</SPAN>intent-filter>  
  8.             <</SPAN>meta-data  
  9.                 android:name="android.content.SyncAdapter"  
  10.                 android:resource="@xml/syncadapter" />  
  11.             <</SPAN>meta-data  
  12.                 android:name="android.provider.CONTACTS_STRUCTURE"  
  13.                 android:resource="@xml/contacts" />  
  14.         </</SPAN>service>  
[html] view plaincopyprint?
  1. <</SPAN>sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:contentAuthority="com.android.contacts"  
  3.     android:accountType="com.example.android.samplesync"  
  4.     android:supportsUploading="false"  
  5.     android:userVisible="true"  
  6. />  

而SyncAdapter的Service的功能也很简单,实现基本和帐户服务的Service一样,主要返回一个IBinder。SyncAdapter主要通过实现父类AbstractThreadedSyncAdapter 的onPerformSync()的方法来实现帐户同步功能。
[java] view plaincopyprint?
  1. public class SyncService extends Service  
  2.   
  3.     private static final Object sSyncAdapterLock new Object();  
  4.     private static SyncAdapter sSyncAdapter null 
  5.   
  6.     @Override  
  7.     public void onCreate()  
  8.         synchronized (sSyncAdapterLock)  
  9.             if (sSyncAdapter == null 
  10.                 sSyncAdapter new SyncAdapter(getApplicationContext(), true);  
  11.              
  12.          
  13.      
  14.   
  15.     @Override  
  16.     public IBinder onBind(Intent intent)  
  17.         return sSyncAdapter.getSyncAdapterBinder();  
  18.      
  19.  


帐户同步适配器(SyncAdapter)里面最主要的方法代码实现如下, 在此SampleSyncAdapter中,实现的主要是联系人的同步。

[java] view plaincopyprint?
  1. @Override  
  2. public void onPerformSync(Account account, Bundle extras, String authority,  
  3.     ContentProviderClient provider, SyncResult syncResult)  
  4.   
  5.     try  
  6.         // see if we already have sync-state attached to this account. By handing   
  7.         // This value to the server, we can just get the contacts that have   
  8.         // been updated on the server-side since our last sync-up   
  9.         long lastSyncMarker getServerSyncMarker(account);  
  10.   
  11.         // By default, contacts from 3rd party provider are hidden in the contacts   
  12.         // list. So let's set the flag that causes them to be visible, so that users   
  13.         // can actually see these contacts.   
  14.         if (lastSyncMarker == 0 
  15.             ContactManager.setAccountContactsVisibility(getContext(), account, true);  
  16.          
  17.   
  18.         List dirtyContacts;  
  19.         List updatedContacts;  
  20.   
  21.         // Use the account manager to request the AuthToken we'll need   
  22.         // to talk to our sample server.  If we don't have an AuthToken   
  23.         // yet, this could involve round-trip to the server to request   
  24.         // and AuthToken.   
  25.         final String authtoken mAccountManager.blockingGetAuthToken(account,  
  26.                 Constants.AUTHTOKEN_TYPE, NOTIFY_AUTH_FAILURE);  
  27.   
  28.         // Make sure that the sample group exists   
  29.         final long groupId ContactManager.ensureSampleGroupExists(mContext, account);  
  30.   
  31.         // Find the local 'dirty' contacts that we need to tell the server about...   
  32.         // Find the local users that need to be sync'd to the server...   
  33.         dirtyContacts ContactManager.getDirtyContacts(mContext, account);  
  34.   
  35.         // Send the dirty contacts to the server, and retrieve the server-side changes   
  36.         updatedContacts NetworkUtilities.syncContacts(account, authtoken,  
  37.                 lastSyncMarker, dirtyContacts);  
  38.   
  39.         // Update the local contacts database with the changes. updateContacts()   
  40.         // returns syncState value that indicates the high-water-mark for   
  41.         // the changes we received.   
  42.         Log.d(TAG, "Calling contactManager's sync contacts");  
  43.         long newSyncState ContactManager.updateContacts(mContext,  
  44.                 account.name,  
  45.                 updatedContacts,  
  46.                 groupId,  
  47.                 lastSyncMarker);  
  48.   
  49.         // This is demo of how you can update IM-style status messages   
  50.         // for contacts on the client. This probably won't apply to   
  51.         // 2-way contact sync providers it's more likely that one-way   
  52.         // sync providers (IM clients, social networking apps, etc) would   
  53.         // use this feature.   
  54.   
  55.         ContactManager.updateStatusMessages(mContext, updatedContacts);  
  56.   
  57.         // This is demo of how you can add stream items for contacts on   
  58.         // the client. This probably won't apply to   
  59.         // 2-way contact sync providers it's more likely that one-way   
  60.         // sync providers (IM clients, social networking apps, etc) would   
  61.         // use this feature. This is only supported in ICS MR1 or above.   
  62.   
  63.         if (Build.VERSION.SDK_INT >=  
  64.                 Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)  
  65.             ContactManager.addStreamItems(mContext, updatedContacts,  
  66.                 account.name, account.type);  
  67.          
  68.   
  69.         // Save off the new sync marker. On our next sync, we only want to receive   
  70.         // contacts that have changed since this sync...   
  71.         setServerSyncMarker(account, newSyncState);  
  72.   
  73.         if (dirtyContacts.size() 0 
  74.             ContactManager.clearSyncFlags(mContext, dirtyContacts);  
  75.          
  76.   
  77.     catch (final AuthenticatorException e)  
  78.         Log.e(TAG, "AuthenticatorException"e);  
  79.         syncResult.stats.numParseExceptions++;  
  80.     catch (final OperationCanceledException e)  
  81.         Log.e(TAG, "OperationCanceledExcetpion"e);  
  82.     catch (final IOException e)  
  83.         Log.e(TAG, "IOException"e);  
  84.         syncResult.stats.numIoExceptions++;  
  85.     catch (final AuthenticationException e)  
  86.         Log.e(TAG, "AuthenticationException"e);  
  87.         syncResult.stats.numAuthExceptions++;  
  88.     catch (final ParseException e)  
  89.         Log.e(TAG, "ParseException"e);  
  90.         syncResult.stats.numParseExceptions++;  
  91.     catch (final JSONException e)  
  92.         Log.e(TAG, "JSONException"e);  
  93.         syncResult.stats.numParseExceptions++;  
  94.      
  95.  


PART.3

到此为止,应该已经成功添加了一个帐户类型,可以有同步联系人的功能。如果我们想添加一个新的帐户,可以进行如下操作:

1、直接通过AccountManager添加,AccountManager.getInstance().addcount,在该接口中传入,账户等信息,系统就会调用那个账户的登录界面。

2、有时可能想调用所有可以同步该应用的账户接口(如日历可以使用Exchange账户和Google账户),或者特定的一组账户,这时可以传入Intent: Settings.ACTION_ADD_ACCOUNT or android.settings.ADD_ACCOUNT_SETTINGS,这个Intent会调用系统Settings的AddAccount的Activity。如果没有其他参数,那么这个activity和从设置->账户与同步进入的activity没有区别。如果使用putExtra添加额外的参数的话,那么就可以启动一组特定的账户了,具体如下:

Extra: (“authorities”,String[])指定一组authority 用字符串数组传入

Extra:(“account_types”,String[])指定一组账户类型,用字符串数组传入


THE LAST:

我研究帐户管理主要是想用于添加一个本地帐户用于存储SIM卡联系人,因此我不需要用到真正的同步功能因此也就没有研究如何搭建同步服务器。 但用于本地帐户的时候发现,如果不联网则不会出现同步联系人的选项,具体原因需要研究源码。

在进行添加帐户的时候,很多人经常会没有实现authenticator.xml 而导致UID 报错的情况,这也是我刚开始的时候遇到的问题。

本文参考 代码SampleSyncAdapter 及 http://blog.csdn.net/wutianyin222/article/details/7911858


Android同步框架

同步(synchronization)允许用户将远程数据下载到新的设备上,同时将设备上的帐户数据上传到远端。同步还保证用户能够看到最新的数据。
开发者自然可以通过自己的方式来设计实现同步机制。但是Android系统还是提供了一个可插拔的同步框架。这个框架自动化的执行以下任务:
  • 检查网络可用性
  • 根据用户设定的选项规划、执行同步
  • 重启已经停止的同步
开发者需要向这个框架提供自己定义的同步适配器(Sync adapter)插件。一个sync adapter唯一的与某个servive/content provider相关联。但是后者反过来可以对应多个sync adapter。

SyncAdapter

首先,Android framework尽可能的封装了数据同步相关的共性操作,而将针对特定服务的同步逻辑的具体实现留给了应用程序。这一点具体体现在Android framework提供的

AbstractThreadedSyncAdapter抽象类上。对照官方文档中的描述,在应用开发中提供一个自定义的sync adapter,只需要完成下面的几个步骤:

  • 扩展AbstractThreadedSyncAdapter抽象类,实现它的onPerformSync()抽象方法
  • 在res/xml下创建一个XML文件来描述这个sync adapter
  • 在应用中创建一个service,使之处理名为“android.content.SyncAdapter”的动作

详细步骤可见AbstractThreadedSyncAdapter类的参考文档。

这里主要分析一下AbstractThreadedSyncAdapter类本身,也即是sync adapter的共性部分。

看看参与sync adapter调用的类簇:

下面是启动一次同步的序列图:

简要描述一下:

ISyncAdapter

首先,sync adapter的行为通过ISyncAdapter接口来描述:

[java] view plaincopy
  1. /** 
  2.  * Interface used to control the sync activity on a SyncAdapter 
  3.  * @hide 
  4.  */  
  5. oneway interface ISyncAdapter {  
  6.     /** 
  7.      * Initiate a sync for this account. SyncAdapter-specific parameters may 
  8.      * be specified in extras, which is guaranteed to not be null. 
  9.      * 
  10.      * @param syncContext the ISyncContext used to indicate the progress of the sync. When 
  11.      *   the sync is finished (successfully or not) ISyncContext.onFinished() must be called. 
  12.      * @param authority the authority that should be synced 
  13.      * @param account the account that should be synced 
  14.      * @param extras SyncAdapter-specific parameters 
  15.      */  
  16.     void startSync(ISyncContext syncContext, String authority,  
  17.       in Account account, in Bundle extras);  
  18.   
  19.     /** 
  20.      * Cancel the most recently initiated sync. Due to race conditions, this may arrive 
  21.      * after the ISyncContext.onFinished() for that sync was called. 
  22.      * @param syncContext the ISyncContext that was passed to {@link #startSync} 
  23.      */  
  24.     void cancelSync(ISyncContext syncContext);  
  25.   
  26.     /** 
  27.      * Initialize the SyncAdapter for this account and authority. 
  28.      * 
  29.      * @param account the account that should be synced 
  30.      * @param authority the authority that should be synced 
  31.      */  
  32.     void initialize(in Account account, String authority);  
  33. }  

AbstractThreadedSyncAdapter类中提供一个ISyncAdapter接口的本地服务:

[java] view plaincopy
  1. private class ISyncAdapterImpl extends ISyncAdapter.Stub {  
  2.     public void startSync(ISyncContext syncContext, String authority, Account account,  
  3.             Bundle extras) {  
  4.         ...  
  5.     }  
  6.   
  7.     public void cancelSync(ISyncContext syncContext) {  
  8.         ...  
  9.     }  
  10.   
  11.     public void initialize(Account account, String authority) throws RemoteException {  
  12.         ...  
  13.     }  
  14. }  

并且定义了向外部提供IBinder实例的方法:

[java] view plaincopy
  1. /** 
  2.  * @return a reference to the IBinder of the SyncAdapter service. 
  3.  */  
  4. public final IBinder getSyncAdapterBinder() {  
  5.     return mISyncAdapterImpl.asBinder();  
  6. }  

ISyncAdapter.startSync()

[java] view plaincopy
  1. public void startSync(ISyncContext syncContext, String authority, Account account,  
  2.         Bundle extras) {  
  3.     ...  
  4.     synchronized (mSyncThreadLock) {  
  5.         if (!mSyncThreads.containsKey(threadsKey)) {  
  6.             ...  
  7.             SyncThread syncThread = new SyncThread(  
  8.                     "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),  
  9.                     syncContextClient, authority, account, extras);  
  10.             mSyncThreads.put(threadsKey, syncThread);  
  11.             syncThread.start();  
  12.             ...  
  13.         }  
  14.         ...  
  15.     }  
  16.     ...  
  17. }  

其实现方式是通过启动一个独立的线程来发起同步。


SyncThread

[java] view plaincopy
  1. private class SyncThread extends Thread {  
  2.     private final SyncContext mSyncContext;  
  3.     private final String mAuthority;  
  4.     private final Account mAccount;  
  5.     private final Bundle mExtras;  
  6.     private final Account mThreadsKey;  
  7.     ...  
  8.   
  9.     @Override  
  10.     public void run() {  
  11.         ...  
  12.         try {  
  13.             ...  
  14.             provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);  
  15.             if (provider != null) {  
  16.                 AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,  
  17.                         mAuthority, provider, syncResult);  
  18.             } else {  
  19.                 syncResult.databaseError = true;  
  20.             }  
  21.         }   
  22.         ...  
  23.     }  
  24.     ...  
  25. }  

这个独立线程的核心就是调用AbstractThreadedSyncAdapter.onPerformSync()方法来执行同步相关的业务逻辑。

这样,总而言之,外界通过从系统中查找得到特定的Sync adapter service,然后通过绑定到这个service来获取ISyncAdapter服务的代理对象。然后调用代理对象来启动/终止同步操作。代理对象与提供同步实现的应用程序进程进行IPC来发起真正的同步过程。 



0 0
原创粉丝点击