SyncAdapter Framework

Android's sync adapter framework helps manage and automate data transfers between device storage associated with an account and server storage that requires login access. It also coordinates synchronization operations across different apps & efficient usage of system resources.
 
In addition to the scheduling options provided by AlarmManager, a sync adapter offers significantly more flexibility to use triggers such as "new data" message from the server/device, the user's activity (or inactivity), the time of day, etc.
 
A content provider must be associated with the SyncAdapter for it to function:
  • AndroudManifest and syncadapter.xml tie-up contentAuthority="@string/content_authority" and SunshineSyncService.
  • On ContentResolver.requestSync(getSyncAccount(), R.string.content_authority), SunshineSyncService binds SyncAdapter facilitating calls to onPerformSync().
 

Components:

  • Authenticator: to handle accounts.
  • Content Provider: to store local data.
  • Sync Adapter: to encapsulate your data transfer code.
 
 
    AndroidManifest.xml
        <uses-permission
            android:name="android.permission.READ_SYNC_SETTINGS"/>
            android:name="android.permission.WRITE_SYNC_SETTINGS"/>
            android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>
         <application
             <provider
                android:authorities="@string/content_authority"
                android:name=".data.WeatherProvider"
                android:exported="false"
                android:syncable="true" />
            <!-- SyncAdapter's dummy authentication service -->
            <service android:name=".sync.SunshineAuthenticatorService">
                <intent-filter> <action android:name="android.accounts.AccountAuthenticator" />
            <!-- The SyncAdapter Service -->
            <service android:name=".sync.SunshineSyncService"
                    android:exported="true" >
                <intent-filter>
                    <action android:name="android.content.SyncAdapter" />
                <meta-data android:resource="@xml/syncadapter" />
    // Dummy Authenticator -- All members are Stub / return null / throw exceptions
    public class SunshineAuthenticator extends AbstractAccountAuthenticator {
        public SunshineAuthenticator(Context context) {
            super(context);
        }
    }
    // AuthenticatorService to allow SyncAdapter Framework to access the authenticator.
    public class SunshineAuthenticatorService extends Service {
        public void onCreate() { mAuthenticator = new SunshineAuthenticator(this); }
        public IBinder onBind(Intent intent) { return mAuthenticator.getIBinder(); }
    }

    syncadapter.xml
        <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
            android:contentAuthority="@string/content_authority"
            android:accountType="@string/sync_account_type"
            android:userVisible="false"
            android:supportsUploading="false"
            android:allowParallelSyncs="false"
            android:isAlwaysSyncable="true" />

    public class SunshineSyncService extends Service {
        private static SunshineSyncAdapter sSunshineSyncAdapter = null;
        public void onCreate() { // Create the sync adapter as a singleton.
            synchronized (sSyncAdapterLock) {
                if (sSunshineSyncAdapter == null) {
                    sSunshineSyncAdapter = new SunshineSyncAdapter(getApplicationContext(), true);
                }
            }
        }
        public IBinder onBind(Intent intent) {
            // Get the object that allows external processes to call onPerformSync().
            return sSunshineSyncAdapter.getSyncAdapterBinder();
        }
    }

    public class SunshineSyncAdapter extends AbstractThreadedSyncAdapter {
        public static void syncImmediately(Context context) {
            Bundle bundle = new Bundle();
            bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
            bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
            ContentResolver.requestSync(getSyncAccount(context),
                    context.getString(R.string.content_authority), bundle);
        }
        public static Account getSyncAccount(Context context) {
            // Create the account type and default account
            Account newAccount = new Account(
                    context.getString(R.string.app_name), context.getString(R.string.sync_account_type));
            if ( null == accountManager.getPassword(newAccount) ) {
                if (!accountManager.addAccountExplicitly(newAccount, "", null)) {
                    return null;
                }
            }
            return newAccount;
        }
        public void onPerformSync() {   // Equivalent replacement of SunshineService.onHandleIntent()
            Connect to a server
            Download data from a server and store it in a content provider, or
            Send data to a server
            Handle data conflicts / Determine whether data is up-to-date
            Close connection to the server, Clean up
            contentProvider.bulkInsert();
        }
    }

    updateWeather() {
        SunshineSyncAdapter.syncImmediately(getActivity());
 
 

Run the Sync Adapter Periodically

 
    AndroidManifest and syncadapter.xml:
        <provider android:syncable="true"
        <sync-adapter android:isAlwaysSyncable="true"
 
Note:
  • Ensure all instances of contentAuthority & accountType match in xml and code.
  • <sync-adapter android:userVisible="true"  ==> Shows last sync/update status in Phone Settings -> Accounts.
  • syncIntervalInSec is in Seconds :)
  • Phone Settings -> Background Data should be Enabled for periodic updates to work.
 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // we can enable inexact timers in our periodic sync
            SyncRequest request = new SyncRequest.Builder().
                    syncPeriodic(syncIntervalInSec, flexTime).
                    setSyncAdapter(account, authority).
                    setExtras(new Bundle()).build();
            ContentResolver.requestSync(request);
        } else {
            ContentResolver.addPeriodicSync(account,
                    authority, new Bundle(), syncIntervalInSec);
        }
 
 

No comments:

Post a Comment