装过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 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的关联。

// 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中再次尝试验证。

// 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); } }

// 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); } }); }

// 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中实现。

// 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定义的关联。

<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模块,如果你也遇到此问题,希望对你有帮助。

更多相关文章

  1. Android(安卓)屏蔽Power键 Home键
  2. Android(安卓)获取所有联系人(两种方法)
  3. Android中广告渠道SDK接入方法
  4. Android(安卓)8.0 以上获取设备序列号, Android(安卓)4.4 - Andro
  5. Android(安卓)ViewPager 如何判断当前页面是从前一页还是后一页
  6. Android(安卓)实现自定义圆环
  7. Android(安卓)仪表进度条 自定义View
  8. Android打造通用的下拉刷新组件
  9. 解决:eclipse导入android时工程下没有R文件的问题,以及style.xml文

随机推荐

  1. 磁盘显示函数不正确怎么恢复?
  2. 最新版Spark 3 HelloWorld
  3. 国际智能制造产业生态圈联盟
  4. DataGrip激活码(亲测有效),最新2021年DataGr
  5. Selenium3自动化测试【15】元素定位之Cla
  6. 练习2-4 温度转换 (5分)
  7. 15:VMware Horizon View 8.0-配置RDS服务器
  8. MySQL5.6 myisam表修复
  9. Linux内核文件详解
  10. 17:VMware Horizon View 8.0-创建应用程序