开发者

How do I implement an Account on Android without a SyncAdapter

I am implementing a login system for an Android application utilizing the built-in accounts system (with the AccountManager APIs).

All is well and good on Android 2.2+, but on Android 2.1 not including a SyncAdapter causes reboots in the account settings screen (see http://code.google.com/p/android/issues/detail?id=5009 and AccountManager without a SyncAdapter?)

To get around this I implemented a stub SyncAdapter, which just returns null from IBinder onBind(Intent intent), and added the relevant stuff to the manifest. This resolves the reboot issue on Android 2.1.

However it introduces a further problem: after an account is added the Android system will, sometime later, initiate an account sync. Although no errors occur (indeed my SyncAdapter does nothing, it has no way to cause errors unless by returning null), the sync icon stays stuck in the notification bar at the top. This results in the Android sync system maint开发者_开发问答aining a permanent wake-lock, preventing the device from sleeping.

The account does not list any syncable components in the account settings screen (under the 'Data and synchronization' header), and always displays 'Sync is off' for the sync status in the list of accounts (even while the sync icon is visible in the notifications bar). Disabling account syncing does not remove the problem. Removing the account stops the problem.

My guess is I should not be returning null. Should I be returning a basic implementation of ThreadedSyncAdapter? Any help getting an account system without an associated sync working properly on 2.1 and 2.2+ is much appreciated.


Since this is the only question I've seen related to this problem, here's a >year late answer. I also came across the permanent wake-lock problem due to the android system syncing my custom account automatically.

The best way to handle this, which requires minimum code and actually makes it so the account never syncs unless specifically called to sync in code:

ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);

Now this requires that the moment you create your account you call this static method. Whereas the first parameter being the account to set this setting for, the second parameter being the used contentprovider's authority, and the third being the integer that when set to a positive number enables syncing, when set to 0 disables syncing and when set to anything else makes it unknown. The authority to use can be found inside your "sync_something.xml" under the contentAuthority attribute, which is used by your SyncAdapter :

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.contacts" 
android:accountType="com.myapp.account"/> <!-- This being your own account type-->

The above xml file is specified inside the service part of your AndroidManifest.xml:

<service android:name=".DummySyncAdapterService"
        exported="true"
        android:process=":contacts">
        <intent-filter>
            <action android:name="android.content.SyncAdapter" />
        </intent-filter>
        <meta-data android:name="android.content.SyncAdapter" 
            android:resource="@xml/sync_something" /> <!--This points to your SyncAdapter XML-->
    </service>

This is the code snippet I use to create my custom account inside my LoginActivity:

Account account = new Account("John Doe", "com.myapp.account");
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 0);
AccountManager am = AccountManager.get(LoginActivity.this);
boolean accountCreated = am.addAccountExplicitly(account, "Password", null);
Bundle extras = LoginActivity.this.getIntent().getExtras();
if(extras != null){
    if (accountCreated) { 
        AccountAuthenticatorResponse response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
        Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, "John Doe");
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, "com.myapp.account");
        response.onResult(result);
    }
}

The great part of this is that when the system tries to sync the service, it checks if the service is syncable first, if it is set to false it cancels the syncing. Now you don't have to create your own ContentProvider nor does your ContentProvider get shown under Data and Synchronization. However you do need to have a stub implementation of AbstractThreadedSyncAdapter which returns an IBinder inside it's onBind method. And last but not least it makes it so that an user can't enable syncing or use the "Sync Now" button for this account unless you've added the functionality inside your app.


I sort of solved my own problem: you cannot return null from the onBind method of your service - you must return the IBinder of an AbstractThreadedSyncAdapter.

This has the undesired effect of adding an entry into the Data and Synchronization section of the account settings page, even though my implementation of AbstractThreadedSyncAdapter does nothing; I was hoping to avoid this.

To summarize, in order to make use of the accounts system in Android you must:

  • Implement a service that has an IntentFilter for android.content.SyncAdapter.
  • This service must return the IBinder of an AbstractThreadedSyncAdapter implementation from it's onBind method.
  • This then necessitates that you have a ContentProvider (can just be a stub implementation) that is referred to as the contentAuthority in your SyncAdapter XML file.
  • This has the down-side that your ContentProvider is listed under the Data and Synchronization header on your account settings page.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜