Android應用開發筆記(7):構建自己的Android賬戶與內容同步機制,常式SampleSyncAdapter的分析

來源:互聯網
上載者:User

裝過Android版的Facebook、lastfm的同學是否對於這些應用的功能感到驚喜,它們可以定期更新朋友的最新資訊,將最新近況和心情短語整合入連絡人中。這些應用全部是以Android2.0後的賬戶和同步機製為基礎的。Google的常式中給出了名為SampleSyncAdpater的例子,通過分析該例子可以學會Android中的Account驗證、同步Adapter的使用。

 

詳細例子代碼可以看sdk samples中提供的源碼,現在拿2.2中的版本來簡要說明。

 

 

首先是 class Authenticator extends AbstractAccountAuthenticator ,該類是賬戶認證類,開啟手機的Setting裡,有Account&Sync 一項,Authenticator就是實現其中的帳號功能的類。

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

其中addAccount方法用來定義需要增加帳號時的操作,如調用AuthenticatorActivity來進行帳號的添加認證。

在AuthenticatorActivity.java中定義了handleLogin(),此方法由login_activity.xml中的android:onClick="handleLogin"定義與ui中的okbutton的關聯。

// in layout/login_activity.xml<br /><Button<br /> android:id="@+id/ok_button"<br /> android:layout_width="wrap_content"<br /> android:layout_height="wrap_content"<br /> android:layout_gravity="center_horizontal"<br /> android:minWidth="100dip"<br /> android:text="@string/login_activity_ok_button"<br /> 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中再次嘗實驗證。

// AuthenticatorActivity.java中的handleLogin()方法<br /> /**<br /> * Handles onClick event on the Submit button. Sends username/password to<br /> * the server for authentication.<br /> *<br /> * @param view The Submit button for which this method is invoked<br /> */<br /> public void handleLogin(View view) {<br /> if (mRequestNewAccount) {<br /> mUsername = mUsernameEdit.getText().toString();<br /> }<br /> mPassword = mPasswordEdit.getText().toString();<br /> if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) {<br /> mMessage.setText(getMessage());<br /> } else {<br /> showProgress();<br /> // Start authenticating...<br /> mAuthThread =<br /> NetworkUtilities.attemptAuth(mUsername, mPassword, mHandler,<br /> AuthenticatorActivity.this);<br /> }<br /> }

 

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

 

// AuthenticatorActivity.java中的onAuthenticationResult,來根據驗證結果來選擇結束認證或重新嘗試。<br /> /**<br /> * Called when the authentication process completes (see attemptLogin()).<br /> */<br /> public void onAuthenticationResult(boolean result) {<br /> Log.i(TAG, "onAuthenticationResult(" + result + ")");<br /> // Hide the progress dialog<br /> hideProgress();<br /> if (result) {<br /> if (!mConfirmCredentials) {<br /> finishLogin();<br /> } else {<br /> finishConfirmCredentials(true);<br /> }<br /> } else {<br /> Log.e(TAG, "onAuthenticationResult: failed to authenticate");<br /> if (mRequestNewAccount) {<br /> // "Please enter a valid username/password.<br /> mMessage<br /> .setText(getText(R.string.login_activity_loginfail_text_both));<br /> } else {<br /> // "Please enter a valid password." (Used when the<br /> // account is already in the database but the password<br /> // doesn't work.)<br /> mMessage<br /> .setText(getText(R.string.login_activity_loginfail_text_pwonly));<br /> }<br /> }<br /> }

 

Account的驗證完畢後,就產生了帳號,可以開始使用同步功能了。同步的主要邏輯在public class SyncAdapter extends AbstractThreadedSyncAdapter中實現。

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

onPerformSync中的執行流程中,使用NetworkUtilities中的fetchFriendUpdates和fetchFriendStatuses來訪問服務端的連絡人更新,並使用了常式中自己封裝的ContactManager來讀取、更新連絡人資訊。

 

那Account和SyncAdapter及其Service和xml定義之間是如何關聯的呢? AndroidManifest.xml中定義了AccountAuthenticator,SyncAdapter及對應的Service和xml定義的關聯。

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

 

更詳細的代碼細節和執行流程,可以去把SDK中的SampleSyncAdapter代碼運行起來體會一下,不過要實現整個流程,必須搭建連絡人的伺服器端,常式中在目錄samplesyncadapter_server中也提供了簡單的server端python代碼,需要搭建在google app engine上。搭建過程遇到一些問題,由於對python不熟我弄了幾天才解決好搭建成功,其中遇到的一個model moudle找不到的問題需要你在model中建立一個__init__.py的空檔案,來說明是一個python模組,如果你也遇到此問題,希望對你有協助。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.