分享

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

 windli笔记 2011-08-08

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

 

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

 

 

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

  1. // in Authenticator.java   
  2.     public Bundle addAccount(AccountAuthenticatorResponse response,  
  3.         String accountType, String authTokenType, String[] requiredFeatures,  
  4.         Bundle options) {  
  5.         final Intent intent = new Intent(mContext, AuthenticatorActivity.class);  
  6.         intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,  
  7.             authTokenType);  
  8.         intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,  
  9.             response);  
  10.         final Bundle bundle = new Bundle();  
  11.         bundle.putParcelable(AccountManager.KEY_INTENT, intent);  
  12.         return bundle;  
  13.     }  

 

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

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

  1. // in layout/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.      * Handles onClick event on the Submit button. Sends username/password to 
  4.      * the server for authentication. 
  5.      *  
  6.      * @param view The Submit button for which this method is invoked 
  7.      */  
  8.     public void handleLogin(View view) {  
  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.             // Start authenticating...   
  18.             mAuthThread =  
  19.                 NetworkUtilities.attemptAuth(mUsername, mPassword, mHandler,  
  20.                     AuthenticatorActivity.this);  
  21.         }  
  22.     }  

 

 

  1. // NetworkUtilities中的authenticate()方法通过网络访问具体来实现服务端的验证,sendResult()来使调用结果被AuthenticatorActivity的onAuthenticationResult()调用。   
  2.     /** 
  3.      * Connects to the Voiper server, authenticates the provided username and 
  4.      * password. 
  5.      *  
  6.      * @param username The user's username 
  7.      * @param password The user's password 
  8.      * @param handler The hander instance from the calling UI thread. 
  9.      * @param context The context of the calling Activity. 
  10.      * @return boolean The boolean result indicating whether the user was 
  11.      *         successfully authenticated. 
  12.      */  
  13.     public static boolean authenticate(String username, String password,  
  14.         Handler handler, final Context context) {  
  15.         final HttpResponse resp;  
  16.   
  17.         final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();  
  18.         params.add(new BasicNameValuePair(PARAM_USERNAME, username));  
  19.         params.add(new BasicNameValuePair(PARAM_PASSWORD, password));  
  20.         HttpEntity entity = null;  
  21.         try {  
  22.             entity = new UrlEncodedFormEntity(params);  
  23.         } catch (final UnsupportedEncodingException e) {  
  24.             // this should never happen.   
  25.             throw new AssertionError(e);  
  26.         }  
  27.         final HttpPost post = new HttpPost(AUTH_URI);  
  28.         post.addHeader(entity.getContentType());  
  29.         post.setEntity(entity);  
  30.         maybeCreateHttpClient();  
  31.   
  32.         try {  
  33.             resp = mHttpClient.execute(post);  
  34.             if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  
  35.                 if (Log.isLoggable(TAG, Log.VERBOSE)) {  
  36.                     Log.v(TAG, "Successful authentication");  
  37.                 }  
  38.                 sendResult(true, handler, context);  
  39.                 return true;  
  40.             } else {  
  41.                 if (Log.isLoggable(TAG, Log.VERBOSE)) {  
  42.                     Log.v(TAG, "Error authenticating" + resp.getStatusLine());  
  43.                 }  
  44.                 sendResult(false, handler, context);  
  45.                 return false;  
  46.             }  
  47.         } catch (final IOException e) {  
  48.             if (Log.isLoggable(TAG, Log.VERBOSE)) {  
  49.                 Log.v(TAG, "IOException when getting authtoken", e);  
  50.             }  
  51.             sendResult(false, handler, context);  
  52.             return false;  
  53.         } finally {  
  54.             if (Log.isLoggable(TAG, Log.VERBOSE)) {  
  55.                 Log.v(TAG, "getAuthtoken completing");  
  56.             }  
  57.         }  
  58.     }  
  59.   
  60.     /** 
  61.      * Sends the authentication response from server back to the caller main UI 
  62.      * thread through its handler. 
  63.      *  
  64.      * @param result The boolean holding authentication result 
  65.      * @param handler The main UI thread's handler instance. 
  66.      * @param context The caller Activity's context. 
  67.      */  
  68.     private static void sendResult(final Boolean result, final Handler handler,  
  69.         final Context context) {  
  70.         if (handler == null || context == null) {  
  71.             return;  
  72.         }  
  73.         handler.post(new Runnable() {  
  74.             public void run() {  
  75.                 ((AuthenticatorActivity) context).onAuthenticationResult(result);  
  76.             }  
  77.         });  
  78.     }  

 

 

  1. // AuthenticatorActivity.java中的onAuthenticationResult,来根据验证结果来选择结束认证或重新尝试。   
  2.     /** 
  3.      * Called when the authentication process completes (see attemptLogin()). 
  4.      */  
  5.     public void onAuthenticationResult(boolean result) {  
  6.         Log.i(TAG, "onAuthenticationResult(" + result + ")");  
  7.         // Hide the progress dialog   
  8.         hideProgress();  
  9.         if (result) {  
  10.             if (!mConfirmCredentials) {  
  11.                 finishLogin();  
  12.             } else {  
  13.                 finishConfirmCredentials(true);  
  14.             }  
  15.         } else {  
  16.             Log.e(TAG, "onAuthenticationResult: failed to authenticate");  
  17.             if (mRequestNewAccount) {  
  18.                 // "Please enter a valid username/password.   
  19.                 mMessage  
  20.                     .setText(getText(R.string.login_activity_loginfail_text_both));  
  21.             } else {  
  22.                 // "Please enter a valid password." (Used when the   
  23.                 // account is already in the database but the password   
  24.                 // doesn't work.)   
  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.     public void onPerformSync(Account account, Bundle extras, String authority,  
  4.         ContentProviderClient provider, SyncResult syncResult) {  
  5.         List<User> users;  
  6.         List<Status> statuses;  
  7.         String authtoken = null;  
  8.          try {  
  9.              // use the account manager to request the credentials   
  10.              authtoken =  
  11.                 mAccountManager.blockingGetAuthToken(account,  
  12.                     Constants.AUTHTOKEN_TYPE, true /* notifyAuthFailure */);  
  13.              // fetch updates from the sample service over the cloud   
  14.              users =  
  15.                 NetworkUtilities.fetchFriendUpdates(account, authtoken,  
  16.                     mLastUpdated);  
  17.             // update the last synced date.   
  18.             mLastUpdated = new Date();  
  19.             // update platform contacts.   
  20.             Log.d(TAG, "Calling contactManager's sync contacts");  
  21.             ContactManager.syncContacts(mContext, account.name, users);  
  22.             // fetch and update status messages for all the synced users.   
  23.             statuses = NetworkUtilities.fetchFriendStatuses(account, authtoken);  
  24.             ContactManager.insertStatuses(mContext, account.name, statuses);  
  25.         } catch (final AuthenticatorException e) {  
  26.             syncResult.stats.numParseExceptions++;  
  27.             Log.e(TAG, "AuthenticatorException", e);  
  28.         } catch (final OperationCanceledException e) {  
  29.             Log.e(TAG, "OperationCanceledExcetpion", e);  
  30.         } catch (final IOException e) {  
  31.             Log.e(TAG, "IOException", e);  
  32.             syncResult.stats.numIoExceptions++;  
  33.         } catch (final AuthenticationException e) {  
  34.             mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,  
  35.                 authtoken);  
  36.             syncResult.stats.numAuthExceptions++;  
  37.             Log.e(TAG, "AuthenticationException", e);  
  38.         } catch (final ParseException e) {  
  39.             syncResult.stats.numParseExceptions++;  
  40.             Log.e(TAG, "ParseException", e);  
  41.         } catch (final JSONException e) {  
  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.     <!-- The authenticator service -->  
  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.             No intent-filter here! This activity is only ever launched by  
  38.             someone who explicitly knows the class name  
  39.         -->  
  40.     </activity>  
  41. </application>  

 

 

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

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多