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 :(
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);
}
});
}
};
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
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.
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?
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. :/
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:
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.
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.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With