Why does ContentResolver.requestSync not trigger a sync?

46,939

Calling requestSync() will only work on an {Account, ContentAuthority} pair that is known to the system. Your app needs to go through a number of steps to tell Android that you are capable of synchronizing a specific kind of content using a specific kind of account. It does this in the AndroidManifest.

1. Notify Android that your application package provides syncing

First off, in AndroidManifest.xml, you have to declare that you have a Sync Service:

<service android:name=".sync.mySyncService" android:exported="true">
   <intent-filter>
      <action android:name="android.content.SyncAdapter" /> 
    </intent-filter>
    <meta-data 
        android:name="android.content.SyncAdapter" 
        android:resource="@xml/sync_myapp" /> 
</service>

The name attribute of the <service> tag is the name of your class to connect up sync... I'll talk to that in a second.

Setting exported true makes it visible to other components (needed so ContentResolver can call it).

The intent filter lets it catch an intent requesting sync. (This Intent comes from ContentResolver when you call ContentResolver.requestSync() or related scheduling methods.)

The <meta-data> tag will be discussed below.

2. Provide Android a service used to find your SyncAdapter

So the class itself... Here's an example:

public class mySyncService extends Service {

    private static mySyncAdapter mSyncAdapter = null;

    public SyncService() {
        super();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (mSyncAdapter == null) {
            mSyncAdapter = new mySyncAdapter(getApplicationContext(), true);
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return mSyncAdapter.getSyncAdapterBinder();
    }
}

Your class must extend Service or one of its subclasses, must implement public IBinder onBind(Intent), and must return a SyncAdapterBinder when that's called... You need a variable of type AbstractThreadedSyncAdapter. So as you can see, that's pretty much everything in that class. The only reason it's there is to provide a Service, that offers a standard interface for Android to query your class as to what your SyncAdapter itself is.

3. Provide a class SyncAdapter to actually perform the sync.

mySyncAdapter is where the real sync logic itself is stored. Its onPerformSync() method gets called when it's time to sync. I figure you already have this in place.

4. Establish a binding between an Account-type and a Content Authority

Looking back again at AndroidManifest, that strange <meta-data> tag in our service is the key piece that establishes the binding between a ContentAuthority and an account. It externally references another xml file (call it whatever you like, something relevant to your app.) Let's look at sync_myapp.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<sync-adapter 
    xmlns:android="http://schemas.android.com/apk/res/android"   
    android:contentAuthority="com.android.contacts"
    android:accountType="com.google" 
    android:userVisible="true" /> 

Okay, so what does this do? It tells Android that the sync adapter we've defined (the class that was called out in the name element of the <service> tag that includes the <meta-data> tag that references this file...) will sync contacts using a com.google style account.

All your contentAuthority strings have to all match, and match with what you're syncing -- This should be a string you define, if you're creating your own database, or you should use some existing device strings if you're syncing known data types (like contacts or calendar events or what have you.) The above ("com.android.contacts") happens to be the ContentAuthority string for contacts type data (surprise, surprise.)

accountType also has to match one of those known account types that are already entered, or it has to match one you're creating (This involves creating a subclass of AccountAuthenticator to get auth on your server... Worth an article, itself.) Again, "com.google" is the defined string identifying... google.com style account credentials (again, this should not be a surprise.)

5. Enable Sync on a given Account / ContentAuthority pair

Finally, sync has to be enabled. You can do this in the Accounts & Sync page in the control panel by going to your app and setting the checkbox next to your app within the matching account. Alternately, you can do it in some setup code in your app:

ContentResolver.setSyncAutomatically(account, AUTHORITY, true);

For sync to occur, your account/authority pair must be enabled to sync (like above) and the overall global sync flag on the system must be set, and the device must have network connectivity.

If your account/authority sync or the global sync are disabled, calling RequestSync() does have an effect -- It sets a flag that sync has been requested, and will be performed as soon as sync is enabled.

Also, per mgv, setting ContentResolver.SYNC_EXTRAS_MANUAL to true in the extras bundle of your requestSync will ask android to force a sync even if global sync is off (be respectful of your user here!)

Finally, you can setup a periodic scheduled sync, again with ContentResolver functions.

6. Consider implications of multiple accounts

It is possible to have more than one account of the same type (two @gmail.com accounts set up on one device or two facebook accounts, or two twitter accounts, etc...) You should consider the application implications of doing that... If you have two accounts, you probably don't want to try to sync both of them into the same database tables. Maybe you need to specify that only one can be active at a time, and flush the tables and resync if you switch accounts. (through a property page that queries what accounts are present). Maybe you create a different database for each account, maybe different tables, maybe a key column in each table. All application specific and worthy of some thought. ContentResolver.setIsSyncable(Account account, String authority, int syncable) might be of interest here. setSyncAutomatically() controls whether an account/authority pair is checked or unchecked, whereas setIsSyncable() provides a way to uncheck and grey out the line so the user can't turn it on. You might set one account Syncable and the other not Syncable (dsabled).

7. Be aware of ContentResolver.notifyChange()

One tricky thing. ContentResolver.notifyChange() is a function used by ContentProviders to notify Android that the local database has been changed. This serves two functions, first, it will cause cursors following that content uri to update, and in turn requery and invalidate and redraw a ListView, etc... It's very magical, the database changes and your ListView just updates automatically. Awesome. Also, when the database changes, Android will request Sync for you, even outside your normal schedule, so that those changes get taken off the device and synced to the server as rapidly as possible. Also awesome.

There's one edge case though. If you pull from the server, and push an update into the ContentProvider, it will dutifully call notifyChange() and android will go, "Oh, database changes, better put them on the server!" (Doh!) Well-written ContentProviders will have some tests to see if the changes came from the network or from the user, and will set the boolean syncToNetwork flag false if so, to prevent this wasteful double-sync. If you're feeding data into a ContentProvider, it behooves you to figure out how to get this working -- Otherwise you'll end up always performing two syncs when only one is needed.

8. Feel happy!

Once you have all this xml metadata in place, and sync enabled, Android will know how to connect everything up for you, and sync should start working. At this point, a lot of things that are nice will just click into place and it will feel a lot like magic. Enjoy!

Share:
46,939

Related videos on Youtube

Ben
Author by

Ben

Updated on July 08, 2022

Comments

  • Ben
    Ben almost 2 years

    I am trying to implement the Content-Provider-Sync Adapter pattern as discussed at Google IO - slide 26. My content provider is working, and my sync works when I trigger it from the Dev Tools Sync Tester application, however when I call ContentResolver.requestSync(account, authority, bundle) from my ContentProvider, my sync is never triggered.

    ContentResolver.requestSync(
            account, 
            AUTHORITY, 
            new Bundle());
    

    Edit -- added manifest snippet My manifest xml contains:

    <service
        android:name=".sync.SyncService"
        android:exported="true">
        <intent-filter>
            <action
                android:name="android.content.SyncAdapter" />
        </intent-filter>
        <meta-data android:name="android.content.SyncAdapter"
        android:resource="@xml/syncadapter" />
    </service>
    

    --Edit

    My syncadapter.xml associated with my sync service contains:

    <?xml version="1.0" encoding="utf-8"?>
    <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"  
        android:contentAuthority="AUTHORITY"
        android:accountType="myaccounttype"
        android:supportsUploading="true"
    />
    

    Not sure what other code would be useful. The account passed to requestSync is of "myaccounttype" and the AUTHORITY passed to the call matches my syc adapter xml.

    Is ContentResolver.requestSync the correct way to request a sync? It looks like the sync tester tool binds directly to the service and calls start sync, but that seems like it defeats the purpose of integrating with the sync architecture.

    If that is the correct way to request a sync then why would the sync tester work, but not my call to ContentResolver.requestSync? Is there something I need to pass in the bundle?

    I am testing in the emulator on devices running 2.1 and 2.2.

    • dangalg
      dangalg almost 9 years
      My problem was that my break point in the sync adapter was not being reached... Then i realised i was trying to debug a service... Hope this helps others like me.
    • Konstantin Schubert
      Konstantin Schubert almost 9 years
      Breakpoints in the sync adapter service will FAIL to trigger. That's because the sync adapter service runs in a separate process. This is what @danglang was hinting at. See also this question: stackoverflow.com/questions/8559458/…
    • Sergey
      Sergey about 7 years
      In my case removing android:process=":sync" from the sync service let the debugger hit the beak points. The sync service itself was working before that, because I could see log messages from onPerformSync method on behalf of another process.
    • Allan Veloso
      Allan Veloso about 5 years
      Another cause is WiFi turned off, so if you are trying to sync with mocked data, check your connection.
  • Ben
    Ben about 13 years
    Thanks for your detailed reply. Unfortunately I've done all of that and it still isn't working. The service is obviously exported (at least somewhat) properly since it shows up in the Sync Tester. It is also associated to the proper account type since the Sync works (and lets me choose the correct account) via the Sync Tester. I have double and triple checked that my authority strings match in the syncadapter.xml and the manifest (for the ContentProvider definition). But the sync requests goes off into the ether and I never get any feedback as to what happened to it.
  • jcwenger
    jcwenger about 13 years
    Settings-> Acocunts and Sync -> account, look at the list inside the details page. You should see your app in the list of things that can be synced. Do you? Is it checked for sync?
  • Ben
    Ben about 13 years
    Hmmm I see my account there but it says sync is off. When I clicked on the account and turned on sync then it immediately performed a sync and now when I trigger a sync from my app it works correctly. Is there something I have to do when creating the account to turn sync on?
  • jcwenger
    jcwenger about 13 years
    ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
  • jcwenger
    jcwenger about 13 years
    No Problem, glad I could help. Again from a style standpoint though, if "AUTHORITY" and "myaccounttype" are the actual strings you're using (And not just samples for copy-past to the site), you definitely need to clean up your naming conventions. Those strings have to be unique across all the device, and you'll run into real trouble if some other programmer lazily makes a package with a matching string for authority and you get a conflict. Cheers!
  • Ben
    Ben about 13 years
    Those are just placeholders for the post. I'm using the standard java package naming convention com.mydomain.a.bunch.of.stuff.
  • mgv
    mgv about 13 years
    You can request a sync even though the global sync settings are turned off. Just add a ContentResolver.SYNC_EXTRAS_MANUAL set to true to the extras Bundle and you will force the sync :)
  • metter
    metter almost 12 years
    Thank you so much! I very much appreciate your effort.
  • Alex Lockwood
    Alex Lockwood over 11 years
    In the SyncService in the Google I/O 2012 (and the SampleSyncAdapter implementation in the SDK), there is some synchronization going on in onCreate... did you forget to mention this?
  • Catalin Morosan
    Catalin Morosan over 11 years
    I'm relying on notifyChange to trigger syncs back to the server but if the global sync setting is off, the sync doesn't happen. Is there something similar to SYNC_EXTRAS_MANUAL to force the sync when using notifyChange()?
  • jcwenger
    jcwenger over 11 years
    @kaciula: I don't know of any, but the device WILL remember that it needs to sync, and it will kick one off as soon as global sync is turned on. You really shouldn't try to trump the user on this one -- Especially as "Global sync off" is one of the key ways to save battery in crtical situations. If you're really worried about the data not getting synced, consider a popup that tells the user WHY the data's not moving, if it's been sitting for a while. That way you can educate users that have accidentally misconfigured their devices and remind power-users in case they forgot.
  • TBieniek
    TBieniek almost 11 years
    @jcwenger: could you get a little more into detail about "6. Consider implications of multiple accounts"? I'm currently writing an app that is supposed to support multiple accounts, uses ContentProviders, a REST API server with auth and now I'm kinda stuck on how to support multiple accounts properly. I did not find any sample code or documentation at all about this stuff yet...
  • Udinic
    Udinic almost 11 years
    Here's a more in-depth information about creating a sync adapter: udinic.wordpress.com/2013/07/24/…
  • android.weasel
    android.weasel over 10 years
    It might be worth adding that if you want to use addPeriodicSync(), it ONLY seems to work if you ALSO setSyncAutomatically() - I added that out of desperation, trying to get SOMETHING working. I know it wasn't part of the original question, but this is such a full answer!
  • Chandra Sekhar
    Chandra Sekhar about 10 years
    Can anybody help me in syncing Exchange ActiveSync contacts to my Android device? Actually it syncs immediately when I update something in "Contacts" folder (the default folder). but when I create some sub folder under "Contacts" and try to update something inside that, the sync doesn't happen.
  • cja
    cja about 10 years
    @android.weasel Please could you see my new question at stackoverflow.com/questions/22206596/… ? Google's docs say that setSyncAutomatically() will result in a sync every few seconds. I just want regular syncs several times an hour
  • Madhu
    Madhu about 10 years
    Is it necessary to implement content provider in sync adapter?
  • jcwenger
    jcwenger about 10 years
    @Brindha, more or less, the answer is yes. You need a ContentProvider in order to declare your Content Authority URI. Strictly speaking, you could declare a do-nothing content provider, bind that empty provider to a Authority URI, and then completely separately perform persistence. You'd then need to do some hooks into that empty provider to call notifyChange(). By this point you're getting really sketchy on architecture. ContentProvider is the preferred design pattern for database persistence on Android. It also ties into Cursors and ContentObservers. It works well, best to stick with it.
  • Madhu
    Madhu about 10 years
    Oh it seems there is lot in implementing sync adapter.. anyway thank you very much for your kind information
  • MRodrigues
    MRodrigues almost 9 years
    I tried to make my sync adapter to work, but still none, I debug it using the ddms and i noted that the syncservice does not start. I double checked all the variables and values. Everything should be working. I'm using a stub authenticator with a dummy account. Any one is having same problem? I can see the dummy acount in the settings for syncing and it marked, Even if I force a "Sync now" nothing happens. Thanks
  • jimmy0251
    jimmy0251 over 8 years
    Test again on Samsung devices!
  • Vikram Gupta
    Vikram Gupta about 8 years
    ContentResolver.setSyncAutomatically(account, AUTHORITY, true); Thank goodness! @mgv that wouldn't work for periodic syncs.
  • Pratik Butani
    Pratik Butani over 7 years
    What can I pass in requestSync(); if I dont want to use account
  • Ravikumar11
    Ravikumar11 over 7 years
    Hi, I have testing on Marshmallow 6.0 and followed above steps but not working. Event is adding in Google Calender application but not Syncing, tested in calendar.google.com with my mail, event is not adding.Please share complete working code
  • KJEjava48
    KJEjava48 over 7 years
    @jcwenger ContentResolver.requestSync(request) is not calling sync adapter onPerformSync() method in my moto g3 phone , but its working in all the other phones that i have tested.Please help me stackoverflow.com/questions/41933786/…
  • KJEjava48
    KJEjava48 about 7 years
    @jcwenger Great answer, but i think you have missed out 1 point to mention, the sync adapter will not function if the device internal storage is almost full.I think the sync will stop functioning if 96 or 97% of memory is used.
  • KJEjava48
    KJEjava48 about 7 years
    @jcwenger Do you know any way so that the sync will work even if the memory is almost full???
  • jcwenger
    jcwenger about 7 years
    @KJEjava48 -- Well I suppose you could always delete some stuff to free up space for the newly synced data to reside in? Or you could buy a new phone? If there's no room to put new data, what's the point of syncing? You're more likely to end up running out of space, syncing half the data you need, and leaving your database incomplete/inconsistent.
  • KJEjava48
    KJEjava48 about 7 years
    @jcwenger Actually i want to sync data from device to server.I don't want to sync it from server to device.So i don't want to make room for syncing,and also my app will work even if the memory is 96 or 97% used.So there will always be new data that needs to be sync to the server.If the memory is 100% full then its ok, i may need to free up some space on that time to use my app.
  • jcwenger
    jcwenger about 7 years
    Most SyncAdapter based apps are pulling data from the cloud to the device, not the other way around. I'd imagine Google disables sync, once you get low enough on storage, in order to protect the user from completely filling storage. Most apps won't handle that case well. I'm sure that at least one other app on your system will fail in unexpected ways once you get that high. Android doesn't WANT to operate at 99%, and it's shutting off sync to protect itself. Quit fighting the system. Clean up your storage, get rid of old data from your app, or buy a device with bigger storage.
  • jcwenger
    jcwenger about 7 years
    I guess the point is, even if you could make your one app handle running at 99% storage full, flawlessly, you're going to make 20 other apps on the device suffer. Don't do that.
  • sabari rangan
    sabari rangan over 5 years
    Setting ".sync.mySyncService" exported to true will it cause any security issues because scanning the apk with MobSF I found " A Service is found to be shared with other apps on the device therefore leaving it accessible to any other application on the device. "