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

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

首先是 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. }
// in Authenticator.java public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) { final Intent intent = new Intent(mContext, AuthenticatorActivity.class); intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, authTokenType); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; }

其中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"/>
// in layout/login_activity.xml <Button android:id="@+id/ok_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:minWidth="100dip" android:text="@string/login_activity_ok_button" 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. }
// AuthenticatorActivity.java中的handleLogin()方法 /** * Handles onClick event on the Submit button. Sends username/password to * the server for authentication. * * @param view The Submit button for which this method is invoked */ public void handleLogin(View view) { if (mRequestNewAccount) { mUsername = mUsernameEdit.getText().toString(); } mPassword = mPasswordEdit.getText().toString(); if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) { mMessage.setText(getMessage()); } else { showProgress(); // Start authenticating... mAuthThread = NetworkUtilities.attemptAuth(mUsername, mPassword, mHandler, AuthenticatorActivity.this); } }

  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. }
// NetworkUtilities中的authenticate()方法通过网络访问具体来实现服务端的验证,sendResult()来使调用结果被AuthenticatorActivity的onAuthenticationResult()调用。 /** * Connects to the Voiper server, authenticates the provided username and * password. * * @param username The user's username * @param password The user's password * @param handler The hander instance from the calling UI thread. * @param context The context of the calling Activity. * @return boolean The boolean result indicating whether the user was * successfully authenticated. */ public static boolean authenticate(String username, String password, Handler handler, final Context context) { final HttpResponse resp; final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair(PARAM_USERNAME, username)); params.add(new BasicNameValuePair(PARAM_PASSWORD, password)); HttpEntity entity = null; try { entity = new UrlEncodedFormEntity(params); } catch (final UnsupportedEncodingException e) { // this should never happen. throw new AssertionError(e); } final HttpPost post = new HttpPost(AUTH_URI); post.addHeader(entity.getContentType()); post.setEntity(entity); maybeCreateHttpClient(); try { resp = mHttpClient.execute(post); if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Successful authentication"); } sendResult(true, handler, context); return true; } else { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Error authenticating" + resp.getStatusLine()); } sendResult(false, handler, context); return false; } } catch (final IOException e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "IOException when getting authtoken", e); } sendResult(false, handler, context); return false; } finally { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAuthtoken completing"); } } } /** * Sends the authentication response from server back to the caller main UI * thread through its handler. * * @param result The boolean holding authentication result * @param handler The main UI thread's handler instance. * @param context The caller Activity's context. */ private static void sendResult(final Boolean result, final Handler handler, final Context context) { if (handler == null || context == null) { return; } handler.post(new Runnable() { public void run() { ((AuthenticatorActivity) context).onAuthenticationResult(result); } }); }

  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. }
// AuthenticatorActivity.java中的onAuthenticationResult,来根据验证结果来选择结束认证或重新尝试。 /** * Called when the authentication process completes (see attemptLogin()). */ public void onAuthenticationResult(boolean result) { Log.i(TAG, "onAuthenticationResult(" + result + ")"); // Hide the progress dialog hideProgress(); if (result) { if (!mConfirmCredentials) { finishLogin(); } else { finishConfirmCredentials(true); } } else { Log.e(TAG, "onAuthenticationResult: failed to authenticate"); if (mRequestNewAccount) { // "Please enter a valid username/password. mMessage .setText(getText(R.string.login_activity_loginfail_text_both)); } else { // "Please enter a valid password." (Used when the // account is already in the database but the password // doesn't work.) mMessage .setText(getText(R.string.login_activity_loginfail_text_pwonly)); } } }

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. }
// SyncAdapter.java中的OnPerformSync方法,主要的同步逻辑 @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { List<User> users; List<Status> statuses; String authtoken = null; try { // use the account manager to request the credentials authtoken = mAccountManager.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE, true /* notifyAuthFailure */); // fetch updates from the sample service over the cloud users = NetworkUtilities.fetchFriendUpdates(account, authtoken, mLastUpdated); // update the last synced date. mLastUpdated = new Date(); // update platform contacts. Log.d(TAG, "Calling contactManager's sync contacts"); ContactManager.syncContacts(mContext, account.name, users); // fetch and update status messages for all the synced users. statuses = NetworkUtilities.fetchFriendStatuses(account, authtoken); ContactManager.insertStatuses(mContext, account.name, statuses); } catch (final AuthenticatorException e) { syncResult.stats.numParseExceptions++; Log.e(TAG, "AuthenticatorException", e); } catch (final OperationCanceledException e) { Log.e(TAG, "OperationCanceledExcetpion", e); } catch (final IOException e) { Log.e(TAG, "IOException", e); syncResult.stats.numIoExceptions++; } catch (final AuthenticationException e) { mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE, authtoken); syncResult.stats.numAuthExceptions++; Log.e(TAG, "AuthenticationException", e); } catch (final ParseException e) { syncResult.stats.numParseExceptions++; Log.e(TAG, "ParseException", e); } catch (final JSONException e) { syncResult.stats.numParseExceptions++; Log.e(TAG, "JSONException", e); } }

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>
<application android:icon="@drawable/icon" android:label="@string/label"> <!-- The authenticator service --> <service android:name=".authenticator.AuthenticationService" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> </service> <service android:name=".syncadapter.SyncService" android:exported="true"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> <meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contacts" /> </service> <activity android:name=".authenticator.AuthenticatorActivity" android:label="@string/ui_activity_title" android:theme="@android:style/Theme.Dialog" android:excludeFromRecents="true" > <!-- No intent-filter here! This activity is only ever launched by someone who explicitly knows the class name --> </activity> </application>

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


以上是从别人那里看到的一篇不错的文章,以下是自己在源码开发过程中的一些总结:

在AccountManagerService.java和RegisteredServicesCache.java的generateServicesMap()方法里会解析所有的功能清单里配置的有关帐户同步的 service。如果没有解析到会将当前加入的帐户信息删除。


帐户信息保存在:data/system/account.db
data/system/sync
data/system/registered-services下面

涉到的service包括:

<service
android:name=".myservices.ContactsSyncAdapterService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_contacts" />
</service>

<service
android:name=".myservices.CalendarSyncAdapterService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_calendar" />
</service>

<service
android:name=".myservices.SyncManager"
android:enabled="true"
>
</service>

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

<service
android:name=".myservices.EasAuthenticatorServiceAlternate"
android:exported="true"
>
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator_alternate"
/>
</service>




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

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

首先是 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. }
// in Authenticator.java public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) { final Intent intent = new Intent(mContext, AuthenticatorActivity.class); intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, authTokenType); intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); final Bundle bundle = new Bundle(); bundle.putParcelable(AccountManager.KEY_INTENT, intent); return bundle; }

其中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"/>
// in layout/login_activity.xml <Button android:id="@+id/ok_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:minWidth="100dip" android:text="@string/login_activity_ok_button" 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. }
// AuthenticatorActivity.java中的handleLogin()方法 /** * Handles onClick event on the Submit button. Sends username/password to * the server for authentication. * * @param view The Submit button for which this method is invoked */ public void handleLogin(View view) { if (mRequestNewAccount) { mUsername = mUsernameEdit.getText().toString(); } mPassword = mPasswordEdit.getText().toString(); if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) { mMessage.setText(getMessage()); } else { showProgress(); // Start authenticating... mAuthThread = NetworkUtilities.attemptAuth(mUsername, mPassword, mHandler, AuthenticatorActivity.this); } }

  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. }
// NetworkUtilities中的authenticate()方法通过网络访问具体来实现服务端的验证,sendResult()来使调用结果被AuthenticatorActivity的onAuthenticationResult()调用。 /** * Connects to the Voiper server, authenticates the provided username and * password. * * @param username The user's username * @param password The user's password * @param handler The hander instance from the calling UI thread. * @param context The context of the calling Activity. * @return boolean The boolean result indicating whether the user was * successfully authenticated. */ public static boolean authenticate(String username, String password, Handler handler, final Context context) { final HttpResponse resp; final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair(PARAM_USERNAME, username)); params.add(new BasicNameValuePair(PARAM_PASSWORD, password)); HttpEntity entity = null; try { entity = new UrlEncodedFormEntity(params); } catch (final UnsupportedEncodingException e) { // this should never happen. throw new AssertionError(e); } final HttpPost post = new HttpPost(AUTH_URI); post.addHeader(entity.getContentType()); post.setEntity(entity); maybeCreateHttpClient(); try { resp = mHttpClient.execute(post); if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Successful authentication"); } sendResult(true, handler, context); return true; } else { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Error authenticating" + resp.getStatusLine()); } sendResult(false, handler, context); return false; } } catch (final IOException e) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "IOException when getting authtoken", e); } sendResult(false, handler, context); return false; } finally { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAuthtoken completing"); } } } /** * Sends the authentication response from server back to the caller main UI * thread through its handler. * * @param result The boolean holding authentication result * @param handler The main UI thread's handler instance. * @param context The caller Activity's context. */ private static void sendResult(final Boolean result, final Handler handler, final Context context) { if (handler == null || context == null) { return; } handler.post(new Runnable() { public void run() { ((AuthenticatorActivity) context).onAuthenticationResult(result); } }); }

  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. }
// AuthenticatorActivity.java中的onAuthenticationResult,来根据验证结果来选择结束认证或重新尝试。 /** * Called when the authentication process completes (see attemptLogin()). */ public void onAuthenticationResult(boolean result) { Log.i(TAG, "onAuthenticationResult(" + result + ")"); // Hide the progress dialog hideProgress(); if (result) { if (!mConfirmCredentials) { finishLogin(); } else { finishConfirmCredentials(true); } } else { Log.e(TAG, "onAuthenticationResult: failed to authenticate"); if (mRequestNewAccount) { // "Please enter a valid username/password. mMessage .setText(getText(R.string.login_activity_loginfail_text_both)); } else { // "Please enter a valid password." (Used when the // account is already in the database but the password // doesn't work.) mMessage .setText(getText(R.string.login_activity_loginfail_text_pwonly)); } } }

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. }
// SyncAdapter.java中的OnPerformSync方法,主要的同步逻辑 @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { List<User> users; List<Status> statuses; String authtoken = null; try { // use the account manager to request the credentials authtoken = mAccountManager.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE, true /* notifyAuthFailure */); // fetch updates from the sample service over the cloud users = NetworkUtilities.fetchFriendUpdates(account, authtoken, mLastUpdated); // update the last synced date. mLastUpdated = new Date(); // update platform contacts. Log.d(TAG, "Calling contactManager's sync contacts"); ContactManager.syncContacts(mContext, account.name, users); // fetch and update status messages for all the synced users. statuses = NetworkUtilities.fetchFriendStatuses(account, authtoken); ContactManager.insertStatuses(mContext, account.name, statuses); } catch (final AuthenticatorException e) { syncResult.stats.numParseExceptions++; Log.e(TAG, "AuthenticatorException", e); } catch (final OperationCanceledException e) { Log.e(TAG, "OperationCanceledExcetpion", e); } catch (final IOException e) { Log.e(TAG, "IOException", e); syncResult.stats.numIoExceptions++; } catch (final AuthenticationException e) { mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE, authtoken); syncResult.stats.numAuthExceptions++; Log.e(TAG, "AuthenticationException", e); } catch (final ParseException e) { syncResult.stats.numParseExceptions++; Log.e(TAG, "ParseException", e); } catch (final JSONException e) { syncResult.stats.numParseExceptions++; Log.e(TAG, "JSONException", e); } }

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>
<application android:icon="@drawable/icon" android:label="@string/label"> <!-- The authenticator service --> <service android:name=".authenticator.AuthenticationService" android:exported="true"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> </service> <service android:name=".syncadapter.SyncService" android:exported="true"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> <meta-data android:name="android.provider.CONTACTS_STRUCTURE" android:resource="@xml/contacts" /> </service> <activity android:name=".authenticator.AuthenticatorActivity" android:label="@string/ui_activity_title" android:theme="@android:style/Theme.Dialog" android:excludeFromRecents="true" > <!-- No intent-filter here! This activity is only ever launched by someone who explicitly knows the class name --> </activity> </application>

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


以上是从别人那里看到的一篇不错的文章,以下是自己在源码开发过程中的一些总结:

在AccountManagerService.java和RegisteredServicesCache.java的generateServicesMap()方法里会解析所有的功能清单里配置的有关帐户同步的 service。如果没有解析到会将当前加入的帐户信息删除。


帐户信息保存在:data/system/account.db
data/system/sync
data/system/registered-services下面

涉到的service包括:

<service
android:name=".myservices.ContactsSyncAdapterService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_contacts" />
</service>

<service
android:name=".myservices.CalendarSyncAdapterService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_calendar" />
</service>

<service
android:name=".myservices.SyncManager"
android:enabled="true"
>
</service>

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

<service
android:name=".myservices.EasAuthenticatorServiceAlternate"
android:exported="true"
>
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator_alternate"
/>
</service>



Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐