构建自己的Android账户与内容同步机制,例程SampleSyncAdapter的分析

装过Android版的Facebook、lastfm的同学是否对于这些应用的功能感到惊喜,它们可以定期更新朋友的最新信息,将最新近况和心情短语集成入联系人中。这些应用全部是以Android2.0后的账户和同步机制为基础的。Google的例程中给出了名为SampleSyncAdpater的例子,通过分析该例子可以学会Android中的Account验证、同步Adapter的使用。

详细例子代码可以看sdk samples中提供的源码,现在拿2.2中的版本来简要说明。

构建自己的Android账户与内容同步机制,例程SampleSyncAdapter的分析

首先是 class Authenticator extends AbstractAccountAuthenticator ,该类是账户认证类,打开手机的Setting里,有Account&Sync 一项,Authenticator就是实现其中的账号功能的类。

  1. //inAuthenticator.java
  2. publicBundleaddAccount(AccountAuthenticatorResponseresponse,
  3. StringaccountType,StringauthTokenType,String[]requiredFeatures,
  4. Bundleoptions){
  5. finalIntentintent=newIntent(mContext,AuthenticatorActivity.class);
  6. intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
  7. authTokenType);
  8. intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
  9. response);
  10. finalBundlebundle=newBundle();
  11. bundle.putParcelable(AccountManager.KEY_INTENT,intent);
  12. returnbundle;
  13. }

其中addAccount方法用来定义需要增加账号时的操作,如调用AuthenticatorActivity来进行账号的添加认证。

在AuthenticatorActivity.java中定义了handleLogin(),此方法由login_activity.xml中的android:onClick="handleLogin"定义与ui中的okbutton的关联。

  1. //inlayout/login_activity.xml
  2. <Button
  3. android:id="@+id/ok_button"
  4. android:layout_width="wrap_content"
  5. android:layout_height="wrap_content"
  6. android:layout_gravity="center_horizontal"
  7. android:minWidth="100dip"
  8. android:text="@string/login_activity_ok_button"
  9. android:onClick="handleLogin"/>

handleLogin()将ui中的用户名和密码取得,并创建一个试图认证的线程,通过网络去服务端验证。

NetworkUtilities.java中的 public static boolean authenticate(String username, String password, Handler handler, final Context context)方法展示了通过网络验证的具体流程。得到服务端验证结果后,在sendResult()中通过handler.post调用来实现onAuthenticationResult()在AuthenticatorActivity中的运行。onAuthenticationResult()判断验证通过则结束AuthenticatorActivity,否则报出用户名密码错,让用户在AuthenticatorActivity中再次尝试验证。

  1. //AuthenticatorActivity.java中的handleLogin()方法
  2. /**
  3. *HandlesonClickeventontheSubmitbutton.Sendsusername/passwordto
  4. *theserverforauthentication.
  5. *
  6. *@paramviewTheSubmitbuttonforwhichthismethodisinvoked
  7. */
  8. publicvoidhandleLogin(Viewview){
  9. if(mRequestNewAccount){
  10. mUsername=mUsernameEdit.getText().toString();
  11. }
  12. mPassword=mPasswordEdit.getText().toString();
  13. if(TextUtils.isEmpty(mUsername)||TextUtils.isEmpty(mPassword)){
  14. mMessage.setText(getMessage());
  15. }else{
  16. showProgress();
  17. //Startauthenticating...
  18. mAuthThread=
  19. NetworkUtilities.attemptAuth(mUsername,mPassword,mHandler,
  20. AuthenticatorActivity.this);
  21. }
  22. }

  1. //NetworkUtilities中的authenticate()方法通过网络访问具体来实现服务端的验证,sendResult()来使调用结果被AuthenticatorActivity的onAuthenticationResult()调用。
  2. /**
  3. *ConnectstotheVoiperserver,authenticatestheprovidedusernameand
  4. *password.
  5. *
  6. *@paramusernameTheuser'susername
  7. *@parampasswordTheuser'spassword
  8. *@paramhandlerThehanderinstancefromthecallingUIthread.
  9. *@paramcontextThecontextofthecallingActivity.
  10. *@returnbooleanThebooleanresultindicatingwhethertheuserwas
  11. *successfullyauthenticated.
  12. */
  13. publicstaticbooleanauthenticate(Stringusername,Stringpassword,
  14. Handlerhandler,finalContextcontext){
  15. finalHttpResponseresp;
  16. finalArrayList<NameValuePair>params=newArrayList<NameValuePair>();
  17. params.add(newBasicNameValuePair(PARAM_USERNAME,username));
  18. params.add(newBasicNameValuePair(PARAM_PASSWORD,password));
  19. HttpEntityentity=null;
  20. try{
  21. entity=newUrlEncodedFormEntity(params);
  22. }catch(finalUnsupportedEncodingExceptione){
  23. //thisshouldneverhappen.
  24. thrownewAssertionError(e);
  25. }
  26. finalHttpPostpost=newHttpPost(AUTH_URI);
  27. post.addHeader(entity.getContentType());
  28. post.setEntity(entity);
  29. maybeCreateHttpClient();
  30. try{
  31. resp=mHttpClient.execute(post);
  32. if(resp.getStatusLine().getStatusCode()==HttpStatus.SC_OK){
  33. if(Log.isLoggable(TAG,Log.VERBOSE)){
  34. Log.v(TAG,"Successfulauthentication");
  35. }
  36. sendResult(true,handler,context);
  37. returntrue;
  38. }else{
  39. if(Log.isLoggable(TAG,Log.VERBOSE)){
  40. Log.v(TAG,"Errorauthenticating"+resp.getStatusLine());
  41. }
  42. sendResult(false,handler,context);
  43. returnfalse;
  44. }
  45. }catch(finalIOExceptione){
  46. if(Log.isLoggable(TAG,Log.VERBOSE)){
  47. Log.v(TAG,"IOExceptionwhengettingauthtoken",e);
  48. }
  49. sendResult(false,handler,context);
  50. returnfalse;
  51. }finally{
  52. if(Log.isLoggable(TAG,Log.VERBOSE)){
  53. Log.v(TAG,"getAuthtokencompleting");
  54. }
  55. }
  56. }
  57. /**
  58. *SendstheauthenticationresponsefromserverbacktothecallermainUI
  59. *threadthroughitshandler.
  60. *
  61. *@paramresultThebooleanholdingauthenticationresult
  62. *@paramhandlerThemainUIthread'shandlerinstance.
  63. *@paramcontextThecallerActivity'scontext.
  64. */
  65. privatestaticvoidsendResult(finalBooleanresult,finalHandlerhandler,
  66. finalContextcontext){
  67. if(handler==null||context==null){
  68. return;
  69. }
  70. handler.post(newRunnable(){
  71. publicvoidrun(){
  72. ((AuthenticatorActivity)context).onAuthenticationResult(result);
  73. }
  74. });
  75. }

  1. //AuthenticatorActivity.java中的onAuthenticationResult,来根据验证结果来选择结束认证或重新尝试。
  2. /**
  3. *Calledwhentheauthenticationprocesscompletes(seeattemptLogin()).
  4. */
  5. publicvoidonAuthenticationResult(booleanresult){
  6. Log.i(TAG,"onAuthenticationResult("+result+")");
  7. //Hidetheprogressdialog
  8. hideProgress();
  9. if(result){
  10. if(!mConfirmCredentials){
  11. finishLogin();
  12. }else{
  13. finishConfirmCredentials(true);
  14. }
  15. }else{
  16. Log.e(TAG,"onAuthenticationResult:failedtoauthenticate");
  17. if(mRequestNewAccount){
  18. //"Pleaseenteravalidusername/password.
  19. mMessage
  20. .setText(getText(R.string.login_activity_loginfail_text_both));
  21. }else{
  22. //"Pleaseenteravalidpassword."(Usedwhenthe
  23. //accountisalreadyinthedatabasebutthepassword
  24. //doesn'twork.)
  25. mMessage
  26. .setText(getText(R.string.login_activity_loginfail_text_pwonly));
  27. }
  28. }
  29. }

Account的验证完毕后,就生成了账号,可以开始使用同步功能了。同步的主要逻辑在public class SyncAdapter extends AbstractThreadedSyncAdapter中实现。

  1. //SyncAdapter.java中的OnPerformSync方法,主要的同步逻辑
  2. @Override
  3. publicvoidonPerformSync(Accountaccount,Bundleextras,Stringauthority,
  4. ContentProviderClientprovider,SyncResultsyncResult){
  5. List<User>users;
  6. List<Status>statuses;
  7. Stringauthtoken=null;
  8. try{
  9. //usetheaccountmanagertorequestthecredentials
  10. authtoken=
  11. mAccountManager.blockingGetAuthToken(account,
  12. Constants.AUTHTOKEN_TYPE,true/*notifyAuthFailure*/);
  13. //fetchupdatesfromthesampleserviceoverthecloud
  14. users=
  15. NetworkUtilities.fetchFriendUpdates(account,authtoken,
  16. mLastUpdated);
  17. //updatethelastsynceddate.
  18. mLastUpdated=newDate();
  19. //updateplatformcontacts.
  20. Log.d(TAG,"CallingcontactManager'ssynccontacts");
  21. ContactManager.syncContacts(mContext,account.name,users);
  22. //fetchandupdatestatusmessagesforallthesyncedusers.
  23. statuses=NetworkUtilities.fetchFriendStatuses(account,authtoken);
  24. ContactManager.insertStatuses(mContext,account.name,statuses);
  25. }catch(finalAuthenticatorExceptione){
  26. syncResult.stats.numParseExceptions++;
  27. Log.e(TAG,"AuthenticatorException",e);
  28. }catch(finalOperationCanceledExceptione){
  29. Log.e(TAG,"OperationCanceledExcetpion",e);
  30. }catch(finalIOExceptione){
  31. Log.e(TAG,"IOException",e);
  32. syncResult.stats.numIoExceptions++;
  33. }catch(finalAuthenticationExceptione){
  34. mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,
  35. authtoken);
  36. syncResult.stats.numAuthExceptions++;
  37. Log.e(TAG,"AuthenticationException",e);
  38. }catch(finalParseExceptione){
  39. syncResult.stats.numParseExceptions++;
  40. Log.e(TAG,"ParseException",e);
  41. }catch(finalJSONExceptione){
  42. syncResult.stats.numParseExceptions++;
  43. Log.e(TAG,"JSONException",e);
  44. }
  45. }

onPerformSync中的执行流程中,使用NetworkUtilities中的fetchFriendUpdates和fetchFriendStatuses来访问服务端的联系人更新,并使用了例程中自己封装的ContactManager来读取、更新联系人信息。

那Account和SyncAdapter及其Service和xml定义之间是如何关联的呢? AndroidManifest.xml中定义了AccountAuthenticator,SyncAdapter及对应的Service和xml定义的关联。

  1. <application
  2. android:icon="@drawable/icon"
  3. android:label="@string/label">
  4. <!--Theauthenticatorservice-->
  5. <service
  6. android:name=".authenticator.AuthenticationService"
  7. android:exported="true">
  8. <intent-filter>
  9. <action
  10. android:name="android.accounts.AccountAuthenticator"/>
  11. </intent-filter>
  12. <meta-data
  13. android:name="android.accounts.AccountAuthenticator"
  14. android:resource="@xml/authenticator"/>
  15. </service>
  16. <service
  17. android:name=".syncadapter.SyncService"
  18. android:exported="true">
  19. <intent-filter>
  20. <action
  21. android:name="android.content.SyncAdapter"/>
  22. </intent-filter>
  23. <meta-data
  24. android:name="android.content.SyncAdapter"
  25. android:resource="@xml/syncadapter"/>
  26. <meta-data
  27. android:name="android.provider.CONTACTS_STRUCTURE"
  28. android:resource="@xml/contacts"/>
  29. </service>
  30. <activity
  31. android:name=".authenticator.AuthenticatorActivity"
  32. android:label="@string/ui_activity_title"
  33. android:theme="@android:style/Theme.Dialog"
  34. android:excludeFromRecents="true"
  35. >
  36. <!--
  37. Nointent-filterhere!Thisactivityisonlyeverlaunchedby
  38. someonewhoexplicitlyknowstheclassname
  39. -->
  40. </activity>
  41. </application>

更详细的代码细节和执行流程,可以去把SDK中的SampleSyncAdapter代码运行起来体会一下,不过要实现整个流程,必须搭建联系人的服务器端,例程中在目录samplesyncadapter_server中也提供了简单的server端python代码,需要搭建在google app engine上。搭建过程遇到一些问题,由于对python不熟我弄了几天才解决好搭建成功,其中遇到的一个model moudle找不到的问题需要你在model中新建一个__init__.py的空文件,来说明是一个python模块,如果你也遇到此问题,希望对你有帮助。


转自:http://blog.****.net/xjanker2/article/details/6146289