如何构建Android Sync Provider:Part1

 

原英文:http://www.c99.org/2010/01/23/writing-an-android-sync-provider-part-1/

翻译能力有限,如有错误,敬请指出。ThankYou!

翻译人: CJT

个人博客地址:http://blog.csdn.net/nndtdx

 

Android2.0 SDK带来的一个好东西就是你可以写一个普通的同步供应商程序,并将其与系统的联系薄,日历等集成。唯一的问题是相关的文档十分的少。还有一个糟糕的问题在于,如果你在某一个地方出了错,Android系统就会崩溃重启。面临如此挑战,我依靠这稀少的文档,以及很少的帖子,还有Android地带的一些代码去建立了一个同步程序----Last.fm 。你想要知道怎么去建立你自己的同步程序么?读下去吧

账户验证

第一个令人疑惑的问题就是账户验证问题,你可以从这个地方了解到更多的信息。http://developer.android.com/reference/android/accounts/AbstractAccountAuthenticator.html

这里定义了该账户如何在“账号&同步”设置中出现的。一个账号的的验证需要3部分来实现:1. 一个从onBind方法返回AbstractAccountAuthenticator 子类的一个服务2. 一个Activityt提供用户输入他们的凭据(账号秘密信息),一个xml文件去描述账号信息展示给用户时( an xml file describing how your account should look when displayed to the user.)同时,你也需要在android.mainfest.xml中添加android.permission.AUTHENTICATE_ACCOUNTS权限

服务

验证服务程序期望从一个onBind方法中返回一个AbstractAccountAuthenticator 的子类。如果你坚持不这么做的话,带来的后果就是当你向系统添加一个账号时,android会将会崩溃并且重启。所幸实现AbstractAccountAuthenticator 并不是一件困难的事情,我们只需要实现其中的addAccount方法即可。该方法返回一个Intent,系统将会用他来为用户展示一个登陆框。该如下的实现将会运行我们的service“fm.last.android.sync.LOGIN”.用户等登录完毕后,,会有一个AccountAuthenticatorResponse 对象传出,我们可以用来将其回传给系统。

AccountAuthenticatorService.java

import   fm . last . android . LastFm;
import   android . accounts . AbstractAccountAuthenticator;
import   android . accounts . Account;
import   android . accounts . AccountAuthenticatorResponse;
import   android . accounts . AccountManager;
import   android . accounts . NetworkErrorException;
import   android . app . Service;
import   android . content . Context;
import   android . content . Intent;
10 import   android . os . Bundle;
11 import   android . os . IBinder;
12 import   android . util . Log;
13  
14 /* *
15   *   Authenticator   service   that   returns   a   subclass   of   AbstractAccountAuthenticator   in   onBind()
16   */
17 public   class   AccountAuthenticatorService   extends   Service   {
18   private   static   final   String   TAG   =   " AccountAuthenticatorService " ;
19   private   static   AccountAuthenticatorImpl   sAccountAuthenticator   =   null ;
20  
21   public   AccountAuthenticatorService()   {
22     super ();
23   }
24  
25   public   IBinder   onBind(Intent   intent)   {
26     IBinder   ret   =   null ;
27     if   (intent . getAction() . equals(android . accounts . AccountManager . ACTION_AUTHENTICATOR_INTENT))
28       ret   =   getAuthenticator() . getIBinder();
29     return   ret;
30   }
31  
32   private   AccountAuthenticatorImpl   getAuthenticator()   {
33     if   (sAccountAuthenticator   = =   null )
34       sAccountAuthenticator   =   new   AccountAuthenticatorImpl( this );
35     return   sAccountAuthenticator;
36   }
37  
38   private   static   class   AccountAuthenticatorImpl   extends   AbstractAccountAuthenticator   {
39     private   Context   mContext;
40  
41     public   AccountAuthenticatorImpl(Context   context)   {
42       super (context);
43       mContext   =   context;
44     }
45  
46     /*
47       *     The   user   has   requested   to   add   a   new   account   to   the   system.     We   return   an   intent   that   will   launch   our   login   screen   if   the   user   has   not   logged   in   yet,
48       *     otherwise   our   activity   will   just   pass   the   user's   credentials   on   to   the   account   manager.
49       */
50     @Override
51     public   Bundle   addAccount(AccountAuthenticatorResponse   response,   String   accountType,   String   authTokenType,   String[]   requiredFeatures,   Bundle   options)
52         throws   NetworkErrorException   {
53       Bundle   reply   =   new   Bundle();
54      
55       Intent   i   =   new   Intent(mContext,   LastFm . class );
56       i . setAction( " fm.last.android.sync.LOGIN " );
57       i . putExtra(AccountManager . KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,   response);
58       reply . putParcelable(AccountManager . KEY_INTENT,   i);
59      
60       return   reply;
61     }
62  
63     @Override
64     public   Bundle   confirmCredentials(AccountAuthenticatorResponse   response,   Account   account,   Bundle   options)   {
65       return   null ;
66     }
67  
68     @Override
69     public   Bundle   editProperties(AccountAuthenticatorResponse   response,   String   accountType)   {
70       return   null ;
71     }
72  
73     @Override
74     public   Bundle   getAuthToken(AccountAuthenticatorResponse   response,   Account   account,   String   authTokenType,   Bundle   options)   throws   NetworkErrorException   {
75       return   null ;
76     }
77  
78     @Override
79     public   String   getAuthTokenLabel(String   authTokenType)   {
80       return   null ;
81     }
82  
83     @Override
84     public   Bundle   hasFeatures(AccountAuthenticatorResponse   response,   Account   account,   String[]   features)   throws   NetworkErrorException   {
85       return   null ;
86     }  
87     @Override
88     public   Bundle   updateCredentials(AccountAuthenticatorResponse   response,   Account   account,   String   authTokenType,   Bundle   options)   {
89       return   null ;
90     }
91   }
92 }
93

该验证服务需要在Android.Mainfest.xml中使用元数据标签定义一下。如下

Snippet from AndroidManifest.xml

1 < service android:name = " AccountAuthenticatorService "
2 android:exported = " true " android:process = " :auth " >
3   < intent-filter >
4     < action android:name = " android.accounts.AccountAuthenticator "   / >
5   < /intent-filter >
6   < meta-data android:name = " android.accounts.AccountAuthenticator "
7   android:resource = " @xml/authenticator "   / >
8 < /service >
9

Xml文件

账号的xml文件中定义了当应用程序与你的账号互动的时候,应用程序将会看到的东西(是不是向国内其他应用程序使用QQ一些应用一样,首先会有一个授权,询问用户当前的应用程序可以访问你的用户的哪些信息?------我的理解)其中包含了用户可读的名字,你所定义的系统账号类型,图标,对一个包含当用户修改账号时可以看到的PreferenceScreens 的xml文件。

authenticator.xml

1 < account-authenticator xmlns:android = " http://schemas.android.com/apk/res/android "
2       android:accountType = " fm.last.android.account "
3       android:icon = " @drawable/icon "
4       android:smallIcon = " @drawable/icon "
5       android:label = " @string/app_name "
6       android:accountPreferences = " @xml/account_preferences " / >
7

account_preferences.xml

< PreferenceScreen
  xmlns:android = " http://schemas.android.com/apk/res/android " >
        < PreferenceCategory
                      android:title = " General   Settings "   / >
 
        < PreferenceScreen
              android:key = " account_settings "
              android:title = " Account   Settings "
              android:summary = " Sync   frequency,   notifications,   etc. " >
10                 < intent
11                       android:action = " fm.last.android.activity.Preferences.ACCOUNT_SETUP "
12                       android:targetPackage = " fm.last.android "
13                       android:targetClass = " fm.last.android.activity.Preferences "   / >
14         < /PreferenceScreen >
15 < /PreferenceScreen >
16

集成(putting it all together)

现在我们可以准备开始测试了。Android 账号的设置部分并不能完好的捕捉异常。如果有地方出错了,设备会重启。最好的测试方式是运行模拟器后,运行DevTools,点击AcountsTester

clip_p_w_picpath001

你会看到一个新的账号类型将会同系统内置的“Corporate”类型的账号一样,被添加到列表中。尽管从下拉列表中选择你的账号,然后点击增加按钮,那么,将会呈现一个你所做的登录框。经过验证后 ,你的账号将会出现在按钮下边的列表中。在这一点,使用系统的“Account&sync”设置去移除和修改账户应该是安全的。

clip_p_w_picpath002 clip_p_w_picpath003

准备好“Data & synchronization”的章节了么?让我们开始第二个章节吧。

可供参考的实现源代码你可以从这里下载https://github.com/c99koder/lastfm-android/。(在GNUGeneralPublicLicense主题下边)另外一个单独的实例程序你可以从Apache License 2.0主题下得到https://github.com/c99koder/AndroidSyncProviderDemo。Google同样也有一个他们的示例的同步程序在Androiddeveloperportal http://developer.android.com/resources/samples/SampleSyncAdapter/index.html。该应用程序比我(原作者)的会稍微完整一些。