Do not ANR when calling SyncService.enqueue during sync

This commit is contained in:
ByteHamster 2020-05-03 11:58:11 +02:00
parent 2386684b7f
commit d962c5dab3
1 changed files with 57 additions and 24 deletions

View File

@ -41,6 +41,8 @@ import de.danoeh.antennapod.core.sync.model.SyncServiceException;
import de.danoeh.antennapod.core.sync.model.UploadChangesResponse; import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
import de.danoeh.antennapod.core.util.URLChecker; import de.danoeh.antennapod.core.util.URLChecker;
import de.danoeh.antennapod.core.util.gui.NotificationUtils; import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray; import org.json.JSONArray;
@ -50,6 +52,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class SyncService extends Worker { public class SyncService extends Worker {
private static final String PREF_NAME = "SyncService"; private static final String PREF_NAME = "SyncService";
@ -62,7 +65,7 @@ public class SyncService extends Worker {
private static final String PREF_LAST_SYNC_ATTEMPT_SUCCESS = "last_sync_attempt_success"; private static final String PREF_LAST_SYNC_ATTEMPT_SUCCESS = "last_sync_attempt_success";
private static final String TAG = "SyncService"; private static final String TAG = "SyncService";
private static final String WORK_ID_SYNC = "SyncServiceWorkId"; private static final String WORK_ID_SYNC = "SyncServiceWorkId";
private static final Object lock = new Object(); private static final ReentrantLock lock = new ReentrantLock();
private ISyncService syncServiceImpl; private ISyncService syncServiceImpl;
@ -100,7 +103,7 @@ public class SyncService extends Worker {
} }
public static void clearQueue(Context context) { public static void clearQueue(Context context) {
synchronized (lock) { executeLockedAsync(() ->
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
.putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0) .putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0)
.putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0) .putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0)
@ -108,15 +111,14 @@ public class SyncService extends Worker {
.putString(PREF_QUEUED_EPISODE_ACTIONS, "[]") .putString(PREF_QUEUED_EPISODE_ACTIONS, "[]")
.putString(PREF_QUEUED_FEEDS_ADDED, "[]") .putString(PREF_QUEUED_FEEDS_ADDED, "[]")
.putString(PREF_QUEUED_FEEDS_REMOVED, "[]") .putString(PREF_QUEUED_FEEDS_REMOVED, "[]")
.apply(); .apply());
}
} }
public static void enqueueFeedAdded(Context context, String downloadUrl) { public static void enqueueFeedAdded(Context context, String downloadUrl) {
if (!GpodnetPreferences.loggedIn()) { if (!GpodnetPreferences.loggedIn()) {
return; return;
} }
synchronized (lock) { executeLockedAsync(() -> {
try { try {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
String json = prefs.getString(PREF_QUEUED_FEEDS_ADDED, "[]"); String json = prefs.getString(PREF_QUEUED_FEEDS_ADDED, "[]");
@ -126,15 +128,15 @@ public class SyncService extends Worker {
} catch (JSONException e) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }
}
sync(context); sync(context);
});
} }
public static void enqueueFeedRemoved(Context context, String downloadUrl) { public static void enqueueFeedRemoved(Context context, String downloadUrl) {
if (!GpodnetPreferences.loggedIn()) { if (!GpodnetPreferences.loggedIn()) {
return; return;
} }
synchronized (lock) { executeLockedAsync(() -> {
try { try {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
String json = prefs.getString(PREF_QUEUED_FEEDS_REMOVED, "[]"); String json = prefs.getString(PREF_QUEUED_FEEDS_REMOVED, "[]");
@ -144,15 +146,15 @@ public class SyncService extends Worker {
} catch (JSONException e) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }
}
sync(context); sync(context);
});
} }
public static void enqueueEpisodeAction(Context context, EpisodeAction action) { public static void enqueueEpisodeAction(Context context, EpisodeAction action) {
if (!GpodnetPreferences.loggedIn()) { if (!GpodnetPreferences.loggedIn()) {
return; return;
} }
synchronized (lock) { executeLockedAsync(() -> {
try { try {
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
String json = prefs.getString(PREF_QUEUED_EPISODE_ACTIONS, "[]"); String json = prefs.getString(PREF_QUEUED_EPISODE_ACTIONS, "[]");
@ -162,8 +164,8 @@ public class SyncService extends Worker {
} catch (JSONException e) { } catch (JSONException e) {
e.printStackTrace(); e.printStackTrace();
} }
}
sync(context); sync(context);
});
} }
public static void sync(Context context) { public static void sync(Context context) {
@ -181,19 +183,20 @@ public class SyncService extends Worker {
} }
public static void fullSync(Context context) { public static void fullSync(Context context) {
synchronized (lock) { executeLockedAsync(() -> {
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
.putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0) .putLong(PREF_LAST_SUBSCRIPTION_SYNC_TIMESTAMP, 0)
.putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0) .putLong(PREF_LAST_EPISODE_ACTIONS_SYNC_TIMESTAMP, 0)
.putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0) .putLong(PREF_LAST_SYNC_ATTEMPT_TIMESTAMP, 0)
.apply(); .apply();
}
OneTimeWorkRequest workRequest = getWorkRequest() OneTimeWorkRequest workRequest = getWorkRequest()
.setInitialDelay(0L, TimeUnit.SECONDS) .setInitialDelay(0L, TimeUnit.SECONDS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.build(); .build();
WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest); WorkManager.getInstance(context).enqueueUniqueWork(WORK_ID_SYNC, ExistingWorkPolicy.REPLACE, workRequest);
EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_started)); EventBus.getDefault().postSticky(new SyncServiceEvent(R.string.sync_status_started));
});
} }
private static OneTimeWorkRequest.Builder getWorkRequest() { private static OneTimeWorkRequest.Builder getWorkRequest() {
@ -209,6 +212,30 @@ public class SyncService extends Worker {
.setInitialDelay(5L, TimeUnit.SECONDS); // Give it some time, so other actions can be queued .setInitialDelay(5L, TimeUnit.SECONDS); // Give it some time, so other actions can be queued
} }
/**
* Take the lock and execute runnable (to prevent changes to preferences being lost when enqueueing while sync is
* in progress). If the lock is free, the runnable is directly executed in the calling thread to prevent overhead.
*/
private static void executeLockedAsync(Runnable runnable) {
if (lock.tryLock()) {
try {
runnable.run();
} finally {
lock.unlock();
}
} else {
Completable.fromRunnable(() -> {
lock.lock();
try {
runnable.run();
} finally {
lock.unlock();
}
}).subscribeOn(Schedulers.io())
.subscribe();
}
}
public static boolean isLastSyncSuccessful(Context context) { public static boolean isLastSyncSuccessful(Context context) {
return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) return context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
.getBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, false); .getBoolean(PREF_LAST_SYNC_ATTEMPT_SUCCESS, false);
@ -304,7 +331,8 @@ public class SyncService extends Worker {
Log.d(TAG, "Added: " + StringUtils.join(queuedAddedFeeds, ", ")); Log.d(TAG, "Added: " + StringUtils.join(queuedAddedFeeds, ", "));
Log.d(TAG, "Removed: " + StringUtils.join(queuedRemovedFeeds, ", ")); Log.d(TAG, "Removed: " + StringUtils.join(queuedRemovedFeeds, ", "));
synchronized (lock) { lock.lock();
try {
UploadChangesResponse uploadResponse = syncServiceImpl UploadChangesResponse uploadResponse = syncServiceImpl
.uploadSubscriptionChanges(queuedAddedFeeds, queuedRemovedFeeds); .uploadSubscriptionChanges(queuedAddedFeeds, queuedRemovedFeeds);
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
@ -312,6 +340,8 @@ public class SyncService extends Worker {
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
.putString(PREF_QUEUED_FEEDS_REMOVED, "[]").apply(); .putString(PREF_QUEUED_FEEDS_REMOVED, "[]").apply();
newTimeStamp = uploadResponse.timestamp; newTimeStamp = uploadResponse.timestamp;
} finally {
lock.unlock();
} }
} }
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
@ -349,7 +379,8 @@ public class SyncService extends Worker {
} }
} }
if (queuedEpisodeActions.size() > 0) { if (queuedEpisodeActions.size() > 0) {
synchronized (lock) { lock.lock();
try {
Log.d(TAG, "Uploading " + queuedEpisodeActions.size() + " actions: " Log.d(TAG, "Uploading " + queuedEpisodeActions.size() + " actions: "
+ StringUtils.join(queuedEpisodeActions, ", ")); + StringUtils.join(queuedEpisodeActions, ", "));
UploadChangesResponse postResponse = syncServiceImpl.uploadEpisodeActions(queuedEpisodeActions); UploadChangesResponse postResponse = syncServiceImpl.uploadEpisodeActions(queuedEpisodeActions);
@ -357,6 +388,8 @@ public class SyncService extends Worker {
Log.d(TAG, "Upload episode response: " + postResponse); Log.d(TAG, "Upload episode response: " + postResponse);
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
.putString(PREF_QUEUED_EPISODE_ACTIONS, "[]").apply(); .putString(PREF_QUEUED_EPISODE_ACTIONS, "[]").apply();
} finally {
lock.unlock();
} }
} }
getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() getApplicationContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()