Android scheduled tasks using SyncAdapter
In this post, we are going to create an sample Android app to demonstrate how to set up a scheduled task to run periodically using SyncAdapter, Services and ContentProvider in Android. It’s much like a cron job in unix. This sample app will show a notification message in the status bar every 60 seconds.
1. activity_main.xml in the res/layout
2. In the AndroidManifest.xml, add these permissions. The parent tag of these will be the root tag, manifest
3. The MainActivity.java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyServiceSyncAdapter.initializeSyncAdapter(getApplicationContext()); } }
4. The MyServiceSyncAdapter.java, this is the heavy lifing for the scheduled task.
public class MyServiceSyncAdapter extends AbstractThreadedSyncAdapter { //TODO change this constant SYNC_INTERVAL to change the sync frequency public static final int SYNC_INTERVAL = 60; //60 * 180; // 60 seconds (1 minute) * 180 = 3 hours public static final int SYNC_FLEXTIME = SYNC_INTERVAL/3; private static final int MOVIE_NOTIFICATION_ID = 3004; public MyServiceSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { Log.i("MyServiceSyncAdapter", "onPerformSync"); //TODO get some data from the internet, api calls, etc. //TODO save the data to database, sqlite, couchbase, etc notifyDataDownloaded(); } /** * Send the notification message to the status bar */ private void notifyDataDownloaded() { Context context = getContext(); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context) .setSmallIcon(android.support.v7.appcompat.R.drawable.notification_template_icon_bg) .setContentTitle("Sync Adapter") .setContentText("New Data Available!"); // Opening the app when the user clicks on the notification. Intent resultIntent = new Intent(context, MainActivity.class); // The stack builder object will contain an artificial back stack for the started Activity. // This ensures that navigating backward from the Activity leads out of your application to the Home screen. TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); mBuilder.setContentIntent(resultPendingIntent); NotificationManager mNotificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager.notify(MOVIE_NOTIFICATION_ID, mBuilder.build()); // MOVIE_NOTIFICATION_ID allows you to update the notification later on. } /** * Helper method to schedule the sync adapter periodic execution */ public static void configurePeriodicSync(Context context, int syncInterval, int flexTime) { Account account = getSyncAccount(context); String authority = context.getString(R.string.content_authority); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // we can enable inexact timers in our periodic sync SyncRequest request = new SyncRequest.Builder() .syncPeriodic(syncInterval, flexTime) .setSyncAdapter(account, authority) .setExtras(new Bundle()).build(); ContentResolver.requestSync(request); } else { ContentResolver.addPeriodicSync(account, authority, new Bundle(), syncInterval); } } /** * Helper method to have the sync adapter sync immediately * @param context The context used to access the account service */ public static void syncImmediately(Context context) { Log.i("MyServiceSyncAdapter", "syncImmediately"); 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); } /** * Helper method to get the fake account to be used with SyncAdapter, or make a new one * if the fake account doesn't exist yet. If we make a new account, we call the * onAccountCreated method so we can initialize things. * * @param context The context used to access the account service * @return a fake account. */ public static Account getSyncAccount(Context context) { AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); // Get an instance of the Android account manager Account newAccount = new Account(context.getString(R.string.app_name), context.getString(R.string.sync_account_type)); // Create the account type and default account // If the password doesn't exist, the account doesn't exist if (accountManager.getPassword(newAccount) == null) { if (!accountManager.addAccountExplicitly(newAccount, "", null)) { Log.e("MyServiceSyncAdapter", "getSyncAccount Failed to create new account."); return null; } onAccountCreated(newAccount, context); } return newAccount; } private static void onAccountCreated(Account newAccount, Context context) { Log.i("MyServiceSyncAdapter", "onAccountCreated"); MyServiceSyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME); ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true); syncImmediately(context); } public static void initializeSyncAdapter(Context context) { Log.d("MyServiceSyncAdapter", "initializeSyncAdapter"); getSyncAccount(context); } }
5. MyServiceSync.java for providing the service to Android to do things you have set up in the SyncAdapter above.
public class MyServiceSync extends Service { private static final Object sSyncAdapterLock = new Object(); private static MyServiceSyncAdapter myServiceSyncAdapter = null; @Override public void onCreate() { super.onCreate(); Log.d("MyServiceSync", "onCreate"); synchronized (sSyncAdapterLock) { if (myServiceSyncAdapter == null) { myServiceSyncAdapter = new MyServiceSyncAdapter(getApplicationContext(), true); } } } @Override public IBinder onBind(Intent intent) { Log.d("MyServiceSync", "onBind"); return myServiceSyncAdapter.getSyncAdapterBinder(); } }
6. MyAuthenticator.java for creating a dummy authenticator for the SyncAdapter to use.
public class MyAuthenticator extends AbstractAccountAuthenticator { public MyAuthenticator(Context context) { super(context); } // No properties to edit. @Override public Bundle editProperties( AccountAuthenticatorResponse r, String s) { throw new UnsupportedOperationException(); } // Because we're not actually adding an account to the device, just return null. @Override public Bundle addAccount( AccountAuthenticatorResponse r, String s, String s2, String[] strings, Bundle bundle) throws NetworkErrorException { return null; } // Ignore attempts to confirm credentials @Override public Bundle confirmCredentials( AccountAuthenticatorResponse r, Account account, Bundle bundle) throws NetworkErrorException { return null; } // Getting an authentication token is not supported @Override public Bundle getAuthToken( AccountAuthenticatorResponse r, Account account, String s, Bundle bundle) throws NetworkErrorException { throw new UnsupportedOperationException(); } // Getting a label for the auth token is not supported @Override public String getAuthTokenLabel(String s) { throw new UnsupportedOperationException(); } // Updating user credentials is not supported @Override public Bundle updateCredentials( AccountAuthenticatorResponse r, Account account, String s, Bundle bundle) throws NetworkErrorException { throw new UnsupportedOperationException(); } // Checking features for the account is not supported @Override public Bundle hasFeatures( AccountAuthenticatorResponse r, Account account, String[] strings) throws NetworkErrorException { throw new UnsupportedOperationException(); } }
7. MyAuthenticatorService.java for providing service for the SyncAdapter to access the authenticator.
public class MyAuthenticatorService extends Service { // Instance field that stores the authenticator object private MyAuthenticator mAuthenticator; @Override public void onCreate() { Log.d("MyAuthenticatorService", "onCreate"); // Create a new authenticator object mAuthenticator = new MyAuthenticator(this); } /* * When the system binds to this Service to make the RPC call * return the authenticator's IBinder. */ @Override public IBinder onBind(Intent intent) { Log.d("MyAuthenticatorService", "onBind"); return mAuthenticator.getIBinder(); } }
8. MyContentProvider.java, a dummy content provider, just to support the sync adapter because your app need a content provider in order for the ContentResolver trigger the SyncAdapter.
public class MyContentProvider extends ContentProvider { @Override public boolean onCreate() { return true; } @Override public String getType(Uri uri) { return null; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { return null; } @Override public Uri insert(Uri uri, ContentValues values) { return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public int bulkInsert(Uri uri, ContentValues[] values) { return 0; } }
9. Add the following in the res/strings.xml
Sync Adapter my.example.com com.sample.app.syncadapter
10. Create a xml directory in res/ folder, and the create the following 2 xml files in it.
authenticator.xml
syncadapter.xml
11. Register the content provider and the services defined above in the AndroidManifest.xml, they should be in the application tag
12. Launch the app and see if it works!
Search within Codexpedia
Search the entire web