Android SyncAdapter在我的设备上无法同步Samsung S6 Edge Plus Nougat(定期在API< = 23上同步)

时间:2017-06-06 08:21:20

标签: android android-service android-syncadapter

使用SyncAdapter时遇到问题。关于它的最奇怪的事情是它早先工作,但现在同步只有在我手动调用时才有效。

它也不适用于模拟器(API 24)

以下是我的同步适配器代码:

 public class SmogAppSyncAdapter extends AbstractThreadedSyncAdapter {

private static final String LOG_TAG = SmogAppSyncAdapter.class.getSimpleName();

public static final int SYNC_INTERVAL =  60; // 60 * 60 = 1h to the nearest 20min.
public static final int SYNC_FLEXTIME = SYNC_INTERVAL / 3;
public static final int POLLUTION_DISTANCE = 10000; //preferred distance between prefs location and nearest measurement point
private static final int POLLUTION_NOTIFICATION_ID = 0;

private ContentResolver mContentResolver;
private SharedPreferences prefs;
private Context syncContext;
private int prefsPollutionLevel;
private double prefsHomeLocationLatitude;
private double prefsHomeLocationLongitude;
private boolean prefsNewMessageNotification;
private int currentApiPollutionLevel;
private Float currentApiPollutionLevelLatitude;
private Float currentApiPollutionLevelLongitude;


/**
 * Set up the sync adapter
 */
SmogAppSyncAdapter(Context context, boolean autoInitialize) {
    super(context, autoInitialize);
    /*
     * If your app uses a content resolver, get an instance of it
     * from the incoming Context
     */
    mContentResolver = context.getContentResolver();
    prefs = PreferenceManager.getDefaultSharedPreferences(context);
    syncContext = context;
    prefsHomeLocationLatitude = prefs.getFloat(syncContext.getResources().getString(R.string.pref_key_home_latitude), 0f);
    prefsHomeLocationLongitude = prefs.getFloat(syncContext.getResources().getString(R.string.pref_key_home_longitude), 0f);
    prefsNewMessageNotification = prefs.getBoolean(syncContext.getResources().getString(R.string.pref_key_notification_new_message), true);
    prefsPollutionLevel = Integer.valueOf(prefs.getString(syncContext.getResources().getString(R.string.pref_key_pollution_level_list), "0"));
}

@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {

    // fetching remote data and insert some stuff

    Log.d(LOG_TAG, "onPerformSync was called");

}

/**
 * Helper method to schedule the sync adapter periodic execution
 */
private 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
 */
private static void syncImmediately(Context context) {
    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) {
    Log.d(LOG_TAG, "getSyncAccount");
    // Get an instance of the Android account manager
    AccountManager accountManager =
            (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);

    // Create the account type and default account
    Account newAccount = new Account(
            context.getString(R.string.app_name), context.getString(R.string.sync_account_type));

    // If the password doesn't exist, the account doesn't exist
    if (null == accountManager.getPassword(newAccount)) {

    /*
     * Add the account and account type, no password or user data
     * If successful, return the Account object, otherwise report an error.
     */
        if (!accountManager.addAccountExplicitly(newAccount, "", null)) {
            Log.d(LOG_TAG, "return null");
            return null;
        }
        /*
         * If you don't set android:syncable="true" in
         * in your <provider> element in the manifest,
         * then call ContentResolver.setIsSyncable(account, AUTHORITY, 1)
         * here.
         */

        onAccountCreated(newAccount, context);
    }
    else {
        Log.d(LOG_TAG, "If the password doesn't exist, the account doesn't exist");
    }
    Log.d(LOG_TAG, "Account name: " + newAccount.name);
    return newAccount;
}

private static void onAccountCreated(Account newAccount, Context context) {
    Log.d(LOG_TAG, "onAccountCreated");
    /*
     * Since we've created an account
     */
    SmogAppSyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME);

    /*
     * Without calling setSyncAutomatically, our periodic sync will not be enabled.
     */
    ContentResolver.setIsSyncable(newAccount, context.getString(R.string.content_authority), 1);
    ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true);

    /*
     * Finally, let's do a sync to get things started
     */
  //  syncImmediately(context);
}

public static void initializeSyncAdapter(Context context) {
    Log.d(LOG_TAG, "inside initializeSyncAdapter");
    getSyncAccount(context);
}

}

我的服务:

public class SmogAppSyncService extends Service {

private static SmogAppSyncAdapter sSyncAdapter = null;
private static final Object sSyncAdapterLock = new Object();


@Override
public void onCreate() {
    synchronized (sSyncAdapterLock) {
        sSyncAdapter = new SmogAppSyncAdapter(getApplicationContext(), true);
    }
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return sSyncAdapter.getSyncAdapterBinder();
}

}

在我的清单中,我添加了以下内容:

 <service android:name=".services.sync.SmogAppAuthenticatorService">
        <intent-filter>
            <action android:name="android.accounts.AccountAuthenticator" />
        </intent-filter>

        <meta-data
            android:name="android.accounts.AccountAuthenticator"
            android:resource="@xml/authenticator" />
    </service>
    <service
        android:name=".services.sync.SmogAppSyncService"
        android:exported="true"
        android:process=":sync">
        <intent-filter>
            <action android:name="android.content.SyncAdapter" />
        </intent-filter>

        <meta-data
            android:name="android.content.SyncAdapter"
            android:resource="@xml/syncadapter" />
    </service>

和权限:

    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />

以下是我的其他xml文件:

<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="@string/content_authority"
android:accountType="@string/sync_account_type"
android:userVisible="false"
android:supportsUploading="false"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true" />

Authetnicator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/sync_account_type"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:smallIcon="@mipmap/ic_launcher" />

如果有帮助,我可以提供更多细节。我真的坚持这个问题并在stackoverflow中检查了一些答案。对我没有帮助。有没有办法让这项工作?周期性同步适用于仿真器,但不适用于实际设备。

更新:我已经阅读了可能是原因的打盹模式,但它不是我的情况,或者我配置错了。 基本上打盹模式及其电池优化可以禁用设备上的一些后台任务。

1 个答案:

答案 0 :(得分:14)

来自SyncRequest.Builder#syncPeriodic(long, long) javadoc:

 /**
     * Build a periodic sync.
     ...
     * @param pollFrequency the amount of time in seconds that you wish
     *            to elapse between periodic syncs. A minimum period of 1 hour is enforced.
     ...
     */
    public Builder syncPeriodic(long pollFrequency, long beforeSeconds) {
        ...
    }

请注意,它声明强制执行最小周期性同步超时1小时。这可能是你的问题。

但是从什么时候开始?我以前没有听说过这么长的超时。让我们深入研究它。

我运行了以下命令:

$ cd ~/aosp/frameworks/base
$ find ./ -name SyncRequest.java | xargs git blame | grep "A minimum period of 1 hour is enforced"

得到这个结果:

e96c3b7eff52 (Shreyas Basarge  2016-01-29 19:25:51 +0000 310)          *            to elapse between periodic syncs. A minimum period of 1 hour is enforced.

看起来提交是从2016年1月开始的。这解释了为什么它适用于API 19但不适用于25。

我进一步验证了此提交添加了将最小超时从60秒增加到1小时的代码。

然后你可能会问为什么谷歌开发人员会改变许多应用程序所依赖的工作API的语义而没有向开发人员发出适当的通知?答案和往常一样 - 因为他们可以。