Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SyncAdapter always in pending state

I'm currently working on an android app which relies on a SyncAdapter to refresh its content from a server. I basically followed these instructions: https://developer.android.com/training/sync-adapters/creating-sync-adapter.html

This worked perfectly until recently. I know this might sound stupid, but I honestly have no idea how I screwed it up :(


The setup

I have one ContentProvider and therefore one SyncAdapter and one Account for all items i want to sync. I use integer flags to determine which item has to be synced:

public static final int EVENTS = 0x1;
public static final int NEWS = 0x2;
public static final int SUBSTITUTIONS = 0x4;
public static final int TEACHERS = 0x8;
public static final int ALL = 0xF;

So in my onPerformSync I have something like:

ArrayList<ContentProviderOperation> batchList = new ArrayList<>();

int which = extras.getInt(SYNC.ARG, SYNC.ALL);
if((which & SYNC.NEWS) == SYNC.NEWS) { syncNews(provider, batchList, syncResult, 0, 1); }
if((which & SYNC.EVENTS) == SYNC.EVENTS) { syncEvents(provider, batchList, syncResult); }
if((which & SYNC.TEACHERS) == SYNC.TEACHERS) { syncTeachers(provider, batchList, syncResult); }
if((which & SYNC.SUBSTITUTIONS) == SYNC.SUBSTITUTIONS) { syncSubstitutions(provider, batchList, syncResult); }

Log.i(TAG, "Merge solution ready. Applying batch update to database...");
provider.applyBatch(batchList);

Because I also want the user to be able to force a refresh, I use a SwipeRefreshLayout to fire up the sync service:

@Override
public void onRefresh() {
    Log.d(TAG, "Force refresh triggered!");
    SyncUtils.triggerRefresh(SyncAdapter.SYNC.NEWS | SyncAdapter.SYNC.EVENTS);
}

I also want to monitor the sync state, so I register / unregister a SyncStatusObserver in my fragment's onResume / onPause:

private final SyncStatusObserver syncStatusObserver = new SyncStatusObserver() {
    @Override
    public void onStatusChanged(int which) {
        Account account = AuthenticatorService.getAccount(SyncUtils.ACCOUNT_TYPE);
        boolean syncActive = ContentResolver.isSyncActive(account, DataProvider.AUTHORITY);
        boolean syncPending = ContentResolver.isSyncPending(account, DataProvider.AUTHORITY);

        final boolean refresh = syncActive || syncPending;
        Log.d(TAG, "Status change detected. Active: %b, pending: %b, refreshing: %b", syncActive, syncPending, refresh);

        swipeRefreshLayout.post(new Runnable() {
            @Override
            public void run() {
               swipeRefreshLayout.setRefreshing(refresh);
            }
        });
    }
};

The problem

Whenever I start the application, the Refresh layout is indication a sync is active. I logged nearly everything and found out that a sync is in pending state. Whenever I try to force refresh the sync will either

  • Become active, do all the stuff and then go back to pending or
  • Simply not become active and staying in pending mode forever

Here's an example log:

D/HomeFragment﹕ Status change detected. Active: false, pending: true, refreshing: true
D/HomeFragment﹕ Status change detected. Active: false, pending: true, refreshing: true
D/HomeFragment﹕ Status change detected. Active: true, pending: true, refreshing: true
D/HomeFragment﹕ Status change detected. Active: false, pending: true, refreshing: true

As you can see, it will never be Active: false, pending: false to indicate the sync finished. This really grinds my gears.


Some more code

I do the initial setup of the stub account (and the periodic syncs) in my application class:

public static void createSyncAccount(Context context) {
    boolean newAccount = false;
    boolean setupComplete = PreferenceManager
               .getDefaultSharedPreferences(context).getBoolean(PREF_SETUP_COMPLETE, false);

    // Create account, if it's missing. (Either first run, or user has deleted account.)
    Account account = AuthenticatorService.getAccount(ACCOUNT_TYPE);
    AccountManager accountManager =
                (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);

    if (accountManager.addAccountExplicitly(account, null, null)) {    
        // Inform the system that this account supports sync
        ContentResolver.setIsSyncable(account, DataProvider.AUTHORITY, 1);

        // Inform the system that this account is eligible for auto sync when the network is up
        ContentResolver.setSyncAutomatically(account, DataProvider.AUTHORITY, true);

        // Recommend a schedule for automatic synchronization. The system may modify this based
        // on other scheduled syncs and network utilization.
        requestPeriodic(account, SYNC.EVENTS, 172800);
        requestPeriodic(account, SYNC.NEWS, 604800);
        requestPeriodic(account, SYNC.SUBSTITUTIONS, 1800);
        requestPeriodic(account, SYNC.TEACHERS, 2419200);

        newAccount = true;
    }

    // Schedule an initial sync if we detect problems with either our account or our local
    // data has been deleted. (Note that it's possible to clear app data WITHOUT affecting
    // the account list, so wee need to check both.)
    if (newAccount || !setupComplete) {
        triggerRefresh(SYNC.ALL);
        PreferenceManager.getDefaultSharedPreferences(context).edit()
                    .putBoolean(PREF_SETUP_COMPLETE, true).commit();
    }
}

Where requestPeriodic() is the following:

public static void requestPeriodic(Account account, int which, long seconds) {
    Bundle options = new Bundle();
    options.putInt(SYNC.ARG, which);

    ContentResolver.addPeriodicSync(account,
        DataProvider.AUTHORITY, options, seconds);
}

And my triggerRefresh() looks like:

public static void triggerRefresh(int which) {
    Log.d(TAG, "Force refresh triggered for id: %d", which);

    Bundle options = new Bundle();
    options.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
    options.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
    options.putInt(SYNC.ARG, which);

    ContentResolver.requestSync(
            AuthenticatorService.getAccount(ACCOUNT_TYPE),
            DataProvider.AUTHORITY,
            options
    );
}

Has anyone encountered similar problems or an idea of what I made wrong?


Update 1

I tried changing the way I use the SyncStatusObserver. I now get the information from the which flag parameter like so:

private final SyncStatusObserver syncStatusObserver = new SyncStatusObserver() {
    @Override
    public void onStatusChanged(int which) {
        boolean syncActive = (which & ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE) == ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
        boolean syncPending = (which & ContentResolver.SYNC_OBSERVER_TYPE_PENDING) == ContentResolver.SYNC_OBSERVER_TYPE_PENDING;

        boolean refreshing = syncActive || syncPending;
        // update UI...
    }
};

When I do this, the pending state seems to be correct, so its returning false as soon as the adapter is starting the sync process, but now the adapter stays active all the time and I have the same wrong result for boolean refreshing as always. :/

like image 466
tobs Avatar asked Sep 24 '15 19:09

tobs


People also ask

Why is my Kubernetes pod in pending state?

This cannot be programmed into a node as inadequate resources prevent proper programming. If you have a concern if a host port is needed, scheduling of pods depends on the number of nodes in the Kubernetes cluster. Identify the problem immediately. Your options for your Kubernetes pod staying in pending state include the following:

Why is my database in the not synchronizing / recovery pending state?

Additionally, this database may be reported as being in the Not Synchronizing / Recovery Pending or Suspect state in SQL Server Management Studio. When the database is defined in an availability group, the database can not be dropped or restored.

What does suspect pending mean in SQL Server?

Suspect – If a database cannot be recovered during startup of SQL Server, the database is marked as Suspect. Recovery Pending – If the SQL Server knows that database recovery needs to be run but something is preventing it from starting, the Server marks the db in ‘Recovery Pending’ state.

Why is my Pod’s debug status pending?

Continuous debugging depends on the pods’ status. What if the pod remains pending? This indicates you cannot schedule the pod into a node. Find out the reasons through messages from your scheduler. You have insufficient resources because CPU or memory supply has been consumed.


1 Answers

i haved faced a similar issue with my syncAdapter...what i did to solve it was to turn auto sync off...since you explicity trigger the sync process you could delete the existing account set the Content Resolver's setSyncAutomatically to false and run the adapter again...

ContentResolver.setSyncAutomatically(account, CONTENT_AUTHORITY, false);

i also post the SyncAdapter's status changing callbacks where the state of the syncing process is logged

private SyncStatusObserver mSyncStatusObserver = new SyncStatusObserver() {

    @Override
    public void onStatusChanged(int which) {
        Log.e("TAG", "Sync Status " + which);
         runOnUiThread(new Runnable() {
            @Override
            public void run() {

                Account account = GenericAccountService.getAccount(SyncUtils.ACCOUNT_TYPE);
                if (account == null) {
                    // GetAccount() returned an invalid value. This shouldn't happen, but
                    // we'll set the status to "not refreshing".
                    //setRefreshActionButtonState(false);
                    return;
                }

                // Test the ContentResolver to see if the sync adapter is active or pending.
                // Set the state of the refresh button accordingly.
                boolean syncActive = ContentResolver.isSyncActive(
                        account, PlacesProvider.PROVIDER_NAME);
                boolean syncPending = ContentResolver.isSyncPending(
                        account, PlacesProvider.PROVIDER_NAME);


                Log.e("TAG", "SYNC PENDING " + syncPending);
                if (!syncActive && !syncPending){
                    Log.e("TAG", "Sync is finished");
                    //if (syncProgressDialog != null) syncProgressDialog.dismiss();
                   // progressDialog.hide();
                }
                else {

                }
                //setRefreshActionButtonState(syncActive || syncPending);
            }
        });
    }
};

in onResume i register the SyncStatusObserver

  mSyncStatusObserver.onStatusChanged(0);

    final int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING |
            ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
    mSyncObserverHandle = ContentResolver.addStatusChangeListener(mask, mSyncStatusObserver);

and in onStop i remove the listener

  if (mSyncObserverHandle != null) {
        ContentResolver.removeStatusChangeListener(mSyncObserverHandle);
        mSyncObserverHandle = null;
        mSyncStatusObserver = null;
    }
like image 111
tsiro Avatar answered Oct 04 '22 19:10

tsiro