登录 立即注册
安币:

安卓巴士 - 安卓开发 - Android开发 - 安卓 - 移动互联网门户

查看: 1286482|回复: 19

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

[复制链接]

249

主题

278

帖子

17

安币

初级码农

Rank: 1

发表于 2011-10-5 16:21:42 | 显示全部楼层 |阅读模式
装过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.         final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
  17.         params.add(new BasicNameValuePair(PARAM_USERNAME, username));
  18.         params.add(new BasicNameValuePair(PARAM_PASSWORD, password));
  19.         HttpEntity entity = null;
  20.         try {
  21.             entity = new UrlEncodedFormEntity(params);
  22.         } catch (final UnsupportedEncodingException e) {
  23.             // this should never happen.
  24.             throw new AssertionError(e);
  25.         }
  26.         final HttpPost post = new HttpPost(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, "Successful authentication");
  35.                 }
  36.                 sendResult(true, handler, context);
  37.                 return true;
  38.             } else {
  39.                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
  40.                     Log.v(TAG, "Error authenticating" + resp.getStatusLine());
  41.                 }
  42.                 sendResult(false, handler, context);
  43.                 return false;
  44.             }
  45.         } catch (final IOException e) {
  46.             if (Log.isLoggable(TAG, Log.VERBOSE)) {
  47.                 Log.v(TAG, "IOException when getting authtoken", e);
  48.             }
  49.             sendResult(false, handler, context);
  50.             return false;
  51.         } finally {
  52.             if (Log.isLoggable(TAG, Log.VERBOSE)) {
  53.                 Log.v(TAG, "getAuthtoken completing");
  54.             }
  55.         }
  56.     }

  57.     /**
  58.      * Sends the authentication response from server back to the caller main UI
  59.      * thread through its handler.
  60.      *
  61.      * @param result The boolean holding authentication result
  62.      * @param handler The main UI thread's handler instance.
  63.      * @param context The caller Activity's context.
  64.      */
  65.     private static void sendResult(final Boolean result, final Handler handler,
  66.         final Context context) {
  67.         if (handler == null || context == null) {
  68.             return;
  69.         }
  70.         handler.post(new Runnable() {
  71.             public void run() {
  72.                 ((AuthenticatorActivity) context).onAuthenticationResult(result);
  73.             }
  74.         });
  75.     }
复制代码
  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模块,如果你也遇到此问题,希望对你有帮助。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

主题

帖子

安币

游客

发表于 2012-2-15 11:03:58 | 显示全部楼层
没用过这个应用,不懂这是做什么的。能贴几个图吗,介绍下

0

主题

8

帖子

6

安币

初级码农

Rank: 1

发表于 2012-3-27 15:02:47 | 显示全部楼层
想问一下 楼主对background data  和 auto-sync 的理解是怎么样的呢?  比如 我完全关闭了 这两项会对用户有什么影响?如果我打开了第一项 而关闭了第二项, 那又会是什么影响呢?  希望得到你的回答……我最近在研究这个!!!如果可以 QQ交流也可以 谢了460642557

0

主题

107

帖子

17

安币

程序猿

Rank: 2

QQ达人

发表于 2012-4-16 13:06:40 | 显示全部楼层
我今天发现一个很好软件网站 好高兴哦

0

主题

22

帖子

9

安币

初级码农

Rank: 1

发表于 2012-4-17 17:51:06 | 显示全部楼层
顶!!!!!!

0

主题

130

帖子

896

安币

程序猿

Rank: 2

发表于 2012-6-8 11:27:53 | 显示全部楼层
{:Nose:}好贴

0

主题

17

帖子

137

安币

程序猿

Rank: 2

发表于 2012-9-7 16:14:06 | 显示全部楼层
正在研究,不太明白AuthenticationService,Authenticator和AuthenticatorActivity之间的调用关系。
恳请高手们指导一下 。{:Nose:}

1

主题

67

帖子

234

安币

攻城狮

Rank: 3Rank: 3

发表于 2012-9-14 17:37:20 | 显示全部楼层
新手上路啊,大神指点俩招
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

站长推荐

通过邮件订阅最新安卓weekly信息
上一条 /4 下一条

下载安卓巴士客户端

全国最大的安卓开发者社区
联系我们
关闭
合作电话:
15618560077
Email:
805941275@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| 广东互联网违法和不良信息举报中心|中国互联网举报中心|下载客户端|申请友链|手机版|站点统计|安卓巴士 ( 粤ICP备15117877号 )

快速回复 返回顶部 返回列表