SyncAdapter running animation - how to know if Syn

2019-03-10 03:35发布

问题:

I want to show a ProgressBar in the ActionBar while my SyncAdapter is actively synchronizing content to and from the web.

I have tried using the SyncStatusObserver together with ContentProvider.addStatusChangeListener. However, I cannot check if a SyncAdapter is actively running. I can only check:

  1. SyncAdapter is pending using ContentResolver.isSyncPending
  2. SyncAdapter is pending OR actively working using ContentResolver.isSyncActive

These flags can be combined: !isSyncPending && isSyncActive so that it is possible to check that a SyncAdapter is actively working and does not have any pending work. However, in some cases the SyncAdapter is actively working AND have a second pending request waiting for it.

It seems so simple but I can't find a way around this problem. Having the ProgressBar visible when the SyncAdapter is not running is giving users the impression that the synchronization is very slow. Having it not show the ProgressBar makes the user think nothing is happening.

The above solution in code is shown below. We register the observer in the activity.onResume:

 int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING | ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
 syncHandle = ContentResolver.addStatusChangeListener(mask, syncObserver);

The syncObserver is here defined as:

syncObserver = new SyncStatusObserver()
{
    @Override
    public void onStatusChanged(int which)
    {
        Account account = getSomeAccount();
        boolean syncActive = ContentResolver.isSyncActive(account, CONTENT_AUTHORITY);
        boolean syncPending = ContentResolver.isSyncPending(account, CONTENT_AUTHORITY);
        boolean isSynchronizing = syncActive && !syncPending;
        updateRefreshButtonState();
    }
}

回答1:

I finally found a solution to the problem. The idea is to use the ContentResolver's getCurrentSyncs() or getCurrentSync() methods, whichever is available. The methods below will check if a sync operation is currently working for an account and authority. It requires API level 8 (Froyo = Android 2.2).

private static boolean isSyncActive(Account account, String authority)
{
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
    {
        return isSyncActiveHoneycomb(account, authority);
    } else
    {
        SyncInfo currentSync = ContentResolver.getCurrentSync();
        return currentSync != null && currentSync.account.equals(account) &&
               currentSync.authority.equals(authority);
    }
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private static boolean isSyncActiveHoneycomb(Account account, String authority)
{
    for(SyncInfo syncInfo : ContentResolver.getCurrentSyncs())
    {
        if(syncInfo.account.equals(account) &&
           syncInfo.authority.equals(authority))
        {
            return true;
        }
    }
    return false;
}

An Activity then registers for updates in onResume() and unregisters in onDestroy(). Also, one have to update state manually in onResume() to catch up with current status.

Here is an implementation that does just that. Subclasses should themselves define

  • what account to use (implementing getAccount())
  • what authoritity to use (the field CONTENT_AUTHORITY)
  • how to display sychronization status (implementing updateState(boolean isSynchronizing))

I hope it will help someone in the future.

import android.accounts.Account;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.SyncInfo;
import android.content.SyncStatusObserver;
import android.os.Build;
import android.os.Bundle;

public abstract class SyncActivity extends Activity
{
    private static final String CONTENT_AUTHORITY = "com.example.authority";
    private Object syncHandle;
    private SyncStatusObserver observer;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        observer = new SyncStatusObserver()
        {
            @Override
            public void onStatusChanged(int which)
            {
                runOnUiThread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        Account account = getAccount();
                        boolean isSynchronizing =
                                isSyncActive(account, CONTENT_AUTHORITY);
                        updateState(isSynchronizing);
                    }
                });
            }
        };
    }

    @Override
    protected void onResume()
    {
        super.onResume();

        // Refresh synchronization status
        observer.onStatusChanged(0);

        // Watch for synchronization status changes
        final int mask = ContentResolver.SYNC_OBSERVER_TYPE_PENDING |
                ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
        syncHandle = ContentResolver.addStatusChangeListener(mask, observer);
    }

    @Override
    protected void onPause()
    {
        super.onPause();

        // Remove our synchronization listener if registered
        if (syncHandle != null)
        {
            ContentResolver.removeStatusChangeListener(syncHandle);
            syncHandle = null;
        }
    }

    private static boolean isSyncActive(Account account, String authority)
    {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        {
            return isSyncActiveHoneycomb(account, authority);
        } else
        {
            SyncInfo currentSync = ContentResolver.getCurrentSync();
            return currentSync != null && currentSync.account.equals(account) 
                    && currentSync.authority.equals(authority);
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private static boolean isSyncActiveHoneycomb(Account account,
                                                         String authority)
    {
        for(SyncInfo syncInfo : ContentResolver.getCurrentSyncs())
        {
            if(syncInfo.account.equals(account) &&
                    syncInfo.authority.equals(authority))
            {
                return true;
            }
        }
        return false;
    }

    protected abstract Account getAccount();
    protected abstract void updateState(boolean isSynchronizing);
}