- <uses-sdk android:minSdkVersion="5"/>
- <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/></uses-permission>
- <uses-permission android:name="android.permission.ACCOUNT_MANAGER"></uses-permission>
- <uses-permission android:name="android.permission.GET_ACCOUNTS"></uses-permission>
- <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"></uses-permission>
- //取得AccountManager对象
- AccountManager _am = AccountManager.get(this);
- /* 显示出所有账户 */
- private void listAccounts()
- {
- /* 得到指定类型的账户 */
- Account[] accounts = _am.getAccountsByType(getString(R.string.ACCOUNT_TYPE));
- _accountList.setText("账户列表:");
- for (Account account : accounts)
- {
- _accountList.setText(_accountList.getText().toString() + '\n' +
- account.name + " - " + account.type);
- }
- }
- public class SleepyAccountAuthenticatorActivity extends AccountAuthenticatorActivity
- {
- protected void onCreate(Bundle icicle)
- {
- super.onCreate(icicle);
- setContentView(R.layout.new_account);
- final Button done = (Button) findViewById(R.id.new_account_done);
- final EditText server = (EditText) findViewById(R.id.new_account_server);
- final EditText username = (EditText) findViewById(R.id.new_account_username);
- final EditText password = (EditText) findViewById(R.id.new_account_password);
- final Activity self = this;
- done.setOnClickListener(new OnClickListener() {
- public void onClick(View v)
- {
- //Account--指定账户名和账户类型
- Account account=new Account(username.getText().
- toString(), getString(R.string.ACCOUNT_TYPE));
- //服务器数据
- Bundle userdata = new Bundle();
- userdata.putString("SERVER", server.getText().toString());
- //取得AccountManager
- AccountManager am = AccountManager.get(self);
- //添加一个账户
- if (am.addAccountExplicitly(account, password.
- getText().toString(), userdata))
- {
- Bundle result = new Bundle();
- result.putString(AccountManager.KEY_ACCOUNT_NAME, username.getText().toString());
- result.putString(AccountManager.KEY_ACCOUNT_TYPE,getString(R.string.ACCOUNT_TYPE));
- setAccountAuthenticatorResult(result);
- }
- finish();
- }
- });
- }
- }
- <?xml version="1.0" encoding="utf-8"?>
- <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
- android:accountType="com.yarin.AccountType"
- android:icon="@drawable/icon"
- android:smallIcon="@drawable/icon"
- android:label="@string/ACCOUNT_LABEL"
- android:accountPreferences="@xml/account_preferences"
- />
- <service android:name="SleepyAccountsService">
- <intent-filter>
- <action android:name="android.accounts.AccountAuthenticator" ></action>
- </intent-filter>
- <meta-data
- android:name="android.accounts.AccountAuthenticator"
- android:resource="@xml/authenticator">
- </meta-data>
- </service>
- public class SleepyAccountsService extends Service
- {
- private SleepyAccountAuthenticator _saa;
- public IBinder onBind(Intent intent)
- {
- IBinder ret = null;
- if (intent.getAction().equals(android.accounts.AccountManager.
- ACTION_AUTHENTICATOR_INTENT))
- ret = getSleepyAuthenticator().getIBinder();
- return ret;
- }
- private SleepyAccountAuthenticator getSleepyAuthenticator()
- {
- if (_saa == null)
- _saa = new SleepyAccountAuthenticator(this);
- return _saa;
- }
- }
- /* 添加账户 */
- public Bundle addAccount(AccountAuthenticatorResponse response, String
- accountType, String authTokenType, String[] requiredFeatures, Bundle
- options)throws NetworkErrorException
- {
- Log.d(_tag, accountType + " - " + authTokenType);
- Bundle ret = new Bundle();
- Intent intent=new Intent(_context,SleepyAccountAuthenticatorActivity.class);
- intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
- ret.putParcelable(AccountManager.KEY_INTENT, intent);
- return ret;
- }
关于Android帐户管理:允许在Android设备上登录相关帐户,如skype,facebook等,可对日历、联系人的数据进行同步。
想要添加一个自己的帐户,本地端要实现两大部分。1是添加帐户的功能。2是同步的功能。
PS:服务器端忽略,SampleSyncAdapter中有相关的服务器代码,未研究。
PART.1 添加帐户的功能
首先,我们关注AccountManager,它为我们管理帐户提供了接口,详情见API文档。
常用的接口有
addAccount() :添加一个帐户。
addAccountExplicitly(): 直接添加一个帐户到AccountManager。
getAccounts():获取所有帐户。
removeAccount():删除帐户
--进入正题--
我们想添加一个自己的帐户到设备中,所需权限如下:
- <</SPAN>uses-sdk
android:minSdkVersion="5"/> <</SPAN>uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/></</SPAN>uses-permission> <</SPAN>uses-permission android:name="android.permission.ACCOUNT_MANAGER"></</SPAN>uses-permission> <</SPAN>uses-permission android:name="android.permission.GET_ACCOUNTS"></</SPAN>uses-permission> <</SPAN>uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"></</SPAN>uses-permission>
首先需要实现一个AccountAuthenticatorActi
之后,需要添加一个账户服务(Service)和一个验证器(AbstractAccountAuthentic
- <</SPAN>account-authenticator
xmlns:android="http://schemas.android.com/apk/res/android" -
android:accountType="com.example.android.samplesync" -
android:icon="@drawable/icon" -
android:smallIcon="@drawable/icon" -
android:label="@string/label" - />
然后,在AndroidManifest.xml文件中AuthenticationService向AccountManager注册一个账户类型。同时,类似widget的声明方式,在meta-data中要声明一个xml,在这个xml中描述账户的类型,图标,显示名,等等
- <</SPAN>service
-
android:name=".authenticator.AuthenticationService" -
android:exported="true"> -
<</SPAN>intent-filter> -
<</SPAN>action -
android:name="android.accounts.AccountAuthenticator" /> -
</</SPAN>intent-filter> -
<</SPAN>meta-data -
android:name="android.accounts.AccountAuthenticator" -
android:resource="@xml/authenticator" /> - </</SPAN>service>
Service的工作其实很简单,代码如下,主要负责返回一个IBinder 接口。
- public
class AuthenticationService extends Service { -
-
private static final String TAG = "AuthenticationService"; -
private Authenticator mAuthenticator; -
@Override -
public void onCreate() { -
mAuthenticator = new Authenticator(this); -
} -
@Override -
public IBinder onBind(Intent intent) { -
return mAuthenticator.getIBinder(); -
} - }
最后,最重要的是AbstractAccountAuthentic
- @Override
- public
Bundle addAccount(AccountAuthenticatorResp onse response, String accountType, -
String authTokenType, String[] requiredFeatures, Bundle options) { -
Log.v(TAG, "addAccount()"); -
final Intent intent = new Intent(mContext, AuthenticatorActivity.class); -
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); -
final Bundle bundle = new Bundle(); -
bundle.putParcelable(AccountManager.KEY_INTENT, intent); -
return bundle; - }
这个Bundle中都会有个包含登录界面的Activity的Intent,然后通过这个Bundle返回给AccountManager,来启动一个登录界面添加账户。
之后在验证完用户名和密码之后添加帐户,代码如下
- private
void finishLogin(String authToken) { -
-
Log.i(TAG, "finishLogin()"); -
final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE); -
if (mRequestNewAccount) { -
mAccountManager.addAccountExplicitly(account, mPassword, null); -
// Set contacts sync for this account. -
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true); -
} else { -
mAccountManager.setPassword(account, mPassword); -
} -
final Intent intent = new Intent(); -
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername); -
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE); -
setAccountAuthenticatorR esult(intent.getExtras()); -
setResult(RESULT_OK, intent); -
finish(); - }
直接向AccountManager添加一个帐户,并设置让其能够自动同步。
到此为止,你已经成功添加了一个属于自己的帐户,可是仅仅是一个帐户,可是却没有同谷歌帐户一样的选项去选择同步联系人,同步日历等等的选项,那就是接下去需要实现的第二步SyncAdapter。
PART.2 同步的功能
SyncAdapter同样需要一个服务(Service)和一个同步适配器(AbstractThreadedSyncAdap
SyncAdapter的Service 需要在AndroidManifest里面声明一个带有Intent:android.content.SyncAdapter的Service来达到向系统注册一个具有同步功能的账户适配器(sync-adapter)。 同时,类似widget的声明方式,在meta-data中要声明一个xml,在这个xml中描述适配器绑定的账户,所要同步的区域(Authority)(如com.android.contacts,com.android.calendar com.android.email)等信息,一个适配器只能同步一个Authority,若想使一个账户同步多个Authority,可以向系统注册多个绑定同一账户的sync-adapter。
- <</SPAN>service
-
android:name=".syncadapter.SyncService" -
android:exported="true"> -
<</SPAN>intent-filter> -
<</SPAN>action -
android:name="android.content.SyncAdapter" /> -
</</SPAN>intent-filter> -
<</SPAN>meta-data -
android:name="android.content.SyncAdapter" -
android:resource="@xml/syncadapter" /> -
<</SPAN>meta-data -
android:name="android.provider.CONTACTS_STRUCTURE" -
android:resource="@xml/contacts" /> -
</</SPAN>service>
- <</SPAN>sync-adapter
xmlns:android="http://schemas.android.com/apk/res/android" -
android:contentAuthority="com.android.contacts" -
android:accountType="com.example.android.samplesync" -
android:supportsUploading="false" -
android:userVisible="true" - />
而SyncAdapter的Service的功能也很简单,实现基本和帐户服务的Service一样,主要返回一个IBinder。SyncAdapter主要通过实现父类AbstractThreadedSyncAdap
- public
class SyncService extends Service { -
-
private static final Object sSyncAdapterLock = new Object(); -
private static SyncAdapter sSyncAdapter = null; -
-
@Override -
public void onCreate() { -
synchronized (sSyncAdapterLock) { -
if (sSyncAdapter == null) { -
sSyncAdapter = new SyncAdapter(getApplicationContext(), true); -
} -
} -
} -
-
@Override -
public IBinder onBind(Intent intent) { -
return sSyncAdapter.getSyncAdapterBinder(); -
} - }
帐户同步适配器(SyncAdapter)里面最主要的方法代码实现如下, 在此SampleSyncAdapter中,实现的主要是联系人的同步。
- @Override
- public
void onPerformSync(Account account, Bundle extras, String authority, -
ContentProviderClient provider, SyncResult syncResult) { -
-
try { -
// see if we already have a sync-state attached to this account. By handing -
// This value to the server, we can just get the contacts that have -
// been updated on the server-side since our last sync-up -
long lastSyncMarker = getServerSyncMarker(account); -
-
// By default, contacts from a 3rd party provider are hidden in the contacts -
// list. So let's set the flag that causes them to be visible, so that users -
// can actually see these contacts. -
if (lastSyncMarker == 0) { -
ContactManager.setAccountContactsVisibi lity(getContext(), account, true); -
} -
-
List dirtyContacts; -
List updatedContacts; -
-
// Use the account manager to request the AuthToken we'll need -
// to talk to our sample server. If we don't have an AuthToken -
// yet, this could involve a round-trip to the server to request -
// and AuthToken. -
final String authtoken = mAccountManager.blockingGetAuthToken(account, -
Constants.AUTHTOKEN_TYPE, NOTIFY_AUTH_FAILURE); -
-
// Make sure that the sample group exists -
final long groupId = ContactManager.ensureSampleGroupExists(mContext, account); -
-
// Find the local 'dirty' contacts that we need to tell the server about... -
// Find the local users that need to be sync'd to the server... -
dirtyContacts = ContactManager.getDirtyContacts(mContext, account); -
-
// Send the dirty contacts to the server, and retrieve the server-side changes -
updatedContacts = NetworkUtilities.syncContacts(account, authtoken, -
lastSyncMarker, dirtyContacts); -
-
// Update the local contacts database with the changes. updateContacts() -
// returns a syncState value that indicates the high-water-mark for -
// the changes we received. -
Log.d(TAG, "Calling contactManager's sync contacts"); -
long newSyncState = ContactManager.updateContacts(mContext, -
account.name, -
updatedContacts, -
groupId, -
lastSyncMarker); -
-
// This is a demo of how you can update IM-style status messages -
// for contacts on the client. This probably won't apply to -
// 2-way contact sync providers - it's more likely that one-way -
// sync providers (IM clients, social networking apps, etc) would -
// use this feature. -
-
ContactManager.updateStatusMessages(mContext, updatedContacts); -
-
// This is a demo of how you can add stream items for contacts on -
// the client. This probably won't apply to -
// 2-way contact sync providers - it's more likely that one-way -
// sync providers (IM clients, social networking apps, etc) would -
// use this feature. This is only supported in ICS MR1 or above. -
-
if (Build.VERSION.SDK_INT >= -
Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { -
ContactManager.addStreamItems(mContext, updatedContacts, -
account.name, account.type); -
} -
-
// Save off the new sync marker. On our next sync, we only want to receive -
// contacts that have changed since this sync... -
setServerSyncMarker(account, newSyncState); -
-
if (dirtyContacts.size() > 0) { -
ContactManager.clearSyncFlags(mContext, dirtyContacts); -
} -
-
} catch (final AuthenticatorException e) { -
Log.e(TAG, "AuthenticatorException", e); -
syncResult.stats.numParseExceptions++; -
} catch (final OperationCanceledExcepti on e) { -
Log.e(TAG, "OperationCanceledExcetpi on", e); -
} catch (final IOException e) { -
Log.e(TAG, "IOException", e); -
syncResult.stats.numIoExceptions++; -
} catch (final AuthenticationException e) { -
Log.e(TAG, "AuthenticationException", e); -
syncResult.stats.numAuthExceptions++; -
} catch (final ParseException e) { -
Log.e(TAG, "ParseException", e); -
syncResult.stats.numParseExceptions++; -
} catch (final JSONException e) { -
Log.e(TAG, "JSONException", e); -
syncResult.stats.numParseExceptions++; -
} - }
PART.3
到此为止,应该已经成功添加了一个帐户类型,可以有同步联系人的功能。如果我们想添加一个新的帐户,可以进行如下操作:
1、直接通过AccountManager添加,AccountManager.getInstance().addcount,在该接口中传入,账户等信息,系统就会调用那个账户的登录界面。
2、有时可能想调用所有可以同步该应用的账户接口(如日历可以使用Exchange账户和Google账户),或者特定的一组账户,这时可以传入Intent: Settings.ACTION_ADD_ACCOUNT or android.settings.ADD_ACCOUNT_SETTINGS,这个Intent会调用系统Settings的AddAccount的Activity。如果没有其他参数,那么这个activity和从设置->账户与同步进入的activity没有区别。如果使用putExtra添加额外的参数的话,那么就可以启动一组特定的账户了,具体如下:
Extra: (“authorities”,String[])指定一组authority 用字符串数组传入
Extra:(“account_types”,String[])指定一组账户类型,用字符串数组传入
THE LAST:
我研究帐户管理主要是想用于添加一个本地帐户用于存储SIM卡联系人,因此我不需要用到真正的同步功能因此也就没有研究如何搭建同步服务器。 但用于本地帐户的时候发现,如果不联网则不会出现同步联系人的选项,具体原因需要研究源码。
在进行添加帐户的时候,很多人经常会没有实现authenticator.xml 而导致UID 报错的情况,这也是我刚开始的时候遇到的问题。
本文参考 代码SampleSyncAdapter 及
Android同步框架
- 检查网络可用性
- 根据用户设定的选项规划、执行同步
- 重启已经停止的同步
SyncAdapter
AbstractThreadedSyncAdapter抽象类上。对照官方文档中的描述,在应用开发中提供一个自定义的sync adapter,只需要完成下面的几个步骤:
- 扩展AbstractThreadedSyncAdapter抽象类,实现它的onPerformSync()抽象方法
- 在res/xml下创建一个XML文件来描述这个sync adapter
- 在应用中创建一个service,使之处理名为“android.content.SyncAdapter”的动作
详细步骤可见AbstractThreadedSyncAdapter类的参考文档。
这里主要分析一下AbstractThreadedSyncAdapter类本身,也即是sync adapter的共性部分。
看看参与sync adapter调用的类簇:
下面是启动一次同步的序列图:
简要描述一下:
ISyncAdapter
首先,sync adapter的行为通过ISyncAdapter接口来描述:
- /**
- * Interface used to control the sync activity on a SyncAdapter
- * @hide
- */
- oneway interface ISyncAdapter {
- /**
- * Initiate a sync for this account. SyncAdapter-specific parameters may
- * be specified in extras, which is guaranteed to not be null.
- *
- * @param syncContext the ISyncContext used to indicate the progress of the sync. When
- * the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
- * @param authority the authority that should be synced
- * @param account the account that should be synced
- * @param extras SyncAdapter-specific parameters
- */
- void startSync(ISyncContext syncContext, String authority,
- in Account account, in Bundle extras);
- /**
- * Cancel the most recently initiated sync. Due to race conditions, this may arrive
- * after the ISyncContext.onFinished() for that sync was called.
- * @param syncContext the ISyncContext that was passed to {@link #startSync}
- */
- void cancelSync(ISyncContext syncContext);
- /**
- * Initialize the SyncAdapter for this account and authority.
- *
- * @param account the account that should be synced
- * @param authority the authority that should be synced
- */
- void initialize(in Account account, String authority);
- }
AbstractThreadedSyncAdapter类中提供一个ISyncAdapter接口的本地服务:
- private class ISyncAdapterImpl extends ISyncAdapter.Stub {
- public void startSync(ISyncContext syncContext, String authority, Account account,
- Bundle extras) {
- ...
- }
- public void cancelSync(ISyncContext syncContext) {
- ...
- }
- public void initialize(Account account, String authority) throws RemoteException {
- ...
- }
- }
并且定义了向外部提供IBinder实例的方法:
- /**
- * @return a reference to the IBinder of the SyncAdapter service.
- */
- public final IBinder getSyncAdapterBinder() {
- return mISyncAdapterImpl.asBinder();
- }
ISyncAdapter.startSync()
- public void startSync(ISyncContext syncContext, String authority, Account account,
- Bundle extras) {
- ...
- synchronized (mSyncThreadLock) {
- if (!mSyncThreads.containsKey(threadsKey)) {
- ...
- SyncThread syncThread = new SyncThread(
- "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
- syncContextClient, authority, account, extras);
- mSyncThreads.put(threadsKey, syncThread);
- syncThread.start();
- ...
- }
- ...
- }
- ...
- }
其实现方式是通过启动一个独立的线程来发起同步。
SyncThread
- private class SyncThread extends Thread {
- private final SyncContext mSyncContext;
- private final String mAuthority;
- private final Account mAccount;
- private final Bundle mExtras;
- private final Account mThreadsKey;
- ...
- @Override
- public void run() {
- ...
- try {
- ...
- provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
- if (provider != null) {
- AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
- mAuthority, provider, syncResult);
- } else {
- syncResult.databaseError = true;
- }
- }
- ...
- }
- ...
- }
这个独立线程的核心就是调用AbstractThreadedSyncAdapter.onPerformSync()方法来执行同步相关的业务逻辑。
这样,总而言之,外界通过从系统中查找得到特定的Sync adapter service,然后通过绑定到这个service来获取ISyncAdapter服务的代理对象。然后调用代理对象来启动/终止同步操作。代理对象与提供同步实现的应用程序进程进行IPC来发起真正的同步过程。