mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-31 21:41:33 +00:00
Rework sync service as JobService and bump target API to 26.
This commit is contained in:
parent
162f08f9bf
commit
a09e1e7662
14 changed files with 284 additions and 333 deletions
|
@ -1,12 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.newsblur"
|
package="com.newsblur"
|
||||||
android:versionCode="154"
|
android:versionCode="159"
|
||||||
android:versionName="7.1.0" >
|
android:versionName="8.0.0" >
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="21"
|
android:minSdkVersion="21"
|
||||||
android:targetSdkVersion="24" />
|
android:targetSdkVersion="26" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
@ -137,7 +137,9 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.SocialFeedReading"/>
|
android:name=".activity.SocialFeedReading"/>
|
||||||
|
|
||||||
<service android:name=".service.NBSyncService" />
|
<service
|
||||||
|
android:name=".service.NBSyncService"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
<receiver android:name=".service.BootReceiver">
|
<receiver android:name=".service.BootReceiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@ -145,8 +147,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.ServiceScheduleReceiver" />
|
|
||||||
|
|
||||||
<receiver android:name=".util.NotifyDismissReceiver" android:exported="false" />
|
<receiver android:name=".util.NotifyDismissReceiver" android:exported="false" />
|
||||||
<receiver android:name=".util.NotifySaveReceiver" android:exported="false" />
|
<receiver android:name=".util.NotifySaveReceiver" android:exported="false" />
|
||||||
<receiver android:name=".util.NotifyMarkreadReceiver" android:exported="false" />
|
<receiver android:name=".util.NotifyMarkreadReceiver" android:exported="false" />
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.os.Bundle;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.newsblur.service.NBSyncService;
|
|
||||||
import com.newsblur.util.FeedUtils;
|
import com.newsblur.util.FeedUtils;
|
||||||
import com.newsblur.util.PrefsUtils;
|
import com.newsblur.util.PrefsUtils;
|
||||||
import com.newsblur.util.PrefConstants.ThemeValue;
|
import com.newsblur.util.PrefConstants.ThemeValue;
|
||||||
|
@ -118,8 +117,7 @@ public class NbActivity extends FragmentActivity {
|
||||||
* Pokes the sync service to perform any pending sync actions.
|
* Pokes the sync service to perform any pending sync actions.
|
||||||
*/
|
*/
|
||||||
protected void triggerSync() {
|
protected void triggerSync() {
|
||||||
Intent i = new Intent(this, NBSyncService.class);
|
FeedUtils.triggerSync(this);
|
||||||
startService(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -80,7 +80,6 @@ public class LoginAsDialogFragment extends DialogFragment {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Boolean result) {
|
protected void onPostExecute(Boolean result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
NBSyncService.resumeFromInterrupt();
|
|
||||||
Intent startMain = new Intent(activity, Main.class);
|
Intent startMain = new Intent(activity, Main.class);
|
||||||
activity.startActivity(startMain);
|
activity.startActivity(startMain);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
import com.newsblur.service.NBSyncService;
|
import com.newsblur.util.FeedUtils;
|
||||||
|
|
||||||
public class NbFragment extends Fragment {
|
public class NbFragment extends Fragment {
|
||||||
|
|
||||||
|
@ -14,8 +14,7 @@ public class NbFragment extends Fragment {
|
||||||
protected void triggerSync() {
|
protected void triggerSync() {
|
||||||
Activity a = getActivity();
|
Activity a = getActivity();
|
||||||
if (a != null) {
|
if (a != null) {
|
||||||
Intent i = new Intent(a, NBSyncService.class);
|
FeedUtils.triggerSync(a);
|
||||||
a.startService(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package com.newsblur.service;
|
package com.newsblur.service;
|
||||||
|
|
||||||
import android.app.AlarmManager;
|
import android.app.job.JobInfo;
|
||||||
import android.app.PendingIntent;
|
import android.app.job.JobScheduler;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
import com.newsblur.service.NBSyncService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* First receiver in the chain that starts with the device. Simply schedules another broadcast
|
* First receiver in the chain that starts with the device. Simply schedules another broadcast
|
||||||
|
@ -17,20 +18,18 @@ public class BootReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
Log.d(this.getClass().getName(), "triggering sync service from device boot");
|
com.newsblur.util.Log.d(this, "triggering sync service from device boot");
|
||||||
scheduleSyncService(context);
|
scheduleSyncService(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void scheduleSyncService(Context context) {
|
public static void scheduleSyncService(Context context) {
|
||||||
Log.d(BootReceiver.class.getName(), "scheduling sync service");
|
com.newsblur.util.Log.d(BootReceiver.class.getName(), "scheduling sync service");
|
||||||
|
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(context, NBSyncService.class));
|
||||||
// wake up to check if a sync is needed less often than necessary to compensate for execution time
|
builder.setPeriodic(AppConstants.BG_SERVICE_CYCLE_MILLIS);
|
||||||
long interval = AppConstants.BG_SERVICE_CYCLE_MILLIS;
|
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
|
||||||
|
builder.setPersisted(true);
|
||||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
JobScheduler sched = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
|
||||||
Intent i = new Intent(context, ServiceScheduleReceiver.class);
|
sched.schedule(builder.build());
|
||||||
PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
||||||
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, interval, interval, pi);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import com.newsblur.util.PrefsUtils;
|
||||||
|
|
||||||
public class CleanupService extends SubService {
|
public class CleanupService extends SubService {
|
||||||
|
|
||||||
private static volatile boolean Running = false;
|
public static boolean activelyRunning = false;
|
||||||
|
|
||||||
public CleanupService(NBSyncService parent) {
|
public CleanupService(NBSyncService parent) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
@ -14,16 +14,17 @@ public class CleanupService extends SubService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exec() {
|
protected void exec() {
|
||||||
gotWork();
|
|
||||||
|
|
||||||
if (PrefsUtils.isTimeToCleanup(parent)) {
|
if (!PrefsUtils.isTimeToCleanup(parent)) return;
|
||||||
com.newsblur.util.Log.d(this.getClass().getName(), "cleaning up old stories");
|
|
||||||
parent.dbHelper.cleanupVeryOldStories();
|
activelyRunning = true;
|
||||||
if (!PrefsUtils.isKeepOldStories(parent)) {
|
|
||||||
parent.dbHelper.cleanupReadStories();
|
com.newsblur.util.Log.d(this.getClass().getName(), "cleaning up old stories");
|
||||||
}
|
parent.dbHelper.cleanupVeryOldStories();
|
||||||
PrefsUtils.updateLastCleanupTime(parent);
|
if (!PrefsUtils.isKeepOldStories(parent)) {
|
||||||
|
parent.dbHelper.cleanupReadStories();
|
||||||
}
|
}
|
||||||
|
PrefsUtils.updateLastCleanupTime(parent);
|
||||||
|
|
||||||
com.newsblur.util.Log.d(this.getClass().getName(), "cleaning up old story texts");
|
com.newsblur.util.Log.d(this.getClass().getName(), "cleaning up old story texts");
|
||||||
parent.dbHelper.cleanupStoryText();
|
parent.dbHelper.cleanupStoryText();
|
||||||
|
@ -42,19 +43,9 @@ public class CleanupService extends SubService {
|
||||||
com.newsblur.util.Log.d(this.getClass().getName(), "cleaning up thumbnail cache");
|
com.newsblur.util.Log.d(this.getClass().getName(), "cleaning up thumbnail cache");
|
||||||
FileCache thumbCache = FileCache.asThumbnailCache(parent);
|
FileCache thumbCache = FileCache.asThumbnailCache(parent);
|
||||||
thumbCache.cleanupUnusedAndOld(parent.dbHelper.getAllStoryThumbnails(), PrefsUtils.getMaxCachedAgeMillis(parent));
|
thumbCache.cleanupUnusedAndOld(parent.dbHelper.getAllStoryThumbnails(), PrefsUtils.getMaxCachedAgeMillis(parent));
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean running() {
|
activelyRunning = false;
|
||||||
return Running;
|
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
protected void setRunning(boolean running) {
|
|
||||||
Running = running;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected boolean isRunning() {
|
|
||||||
return Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.util.Set;
|
||||||
|
|
||||||
public class ImagePrefetchService extends SubService {
|
public class ImagePrefetchService extends SubService {
|
||||||
|
|
||||||
private static volatile boolean Running = false;
|
public static boolean activelyRunning = false;
|
||||||
|
|
||||||
FileCache storyImageCache;
|
FileCache storyImageCache;
|
||||||
FileCache thumbnailCache;
|
FileCache thumbnailCache;
|
||||||
|
@ -33,77 +33,77 @@ public class ImagePrefetchService extends SubService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exec() {
|
protected void exec() {
|
||||||
if (!PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
activelyRunning = true;
|
||||||
if (!PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
try {
|
||||||
|
if (!PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
||||||
|
if (!PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
||||||
|
|
||||||
gotWork();
|
while (StoryImageQueue.size() > 0) {
|
||||||
|
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
||||||
|
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
||||||
|
|
||||||
while (StoryImageQueue.size() > 0) {
|
startExpensiveCycle();
|
||||||
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
com.newsblur.util.Log.d(this, "story images to prefetch: " + StoryImageQueue.size());
|
||||||
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
// on each batch, re-query the DB for images associated with yet-unread stories
|
||||||
|
// this is a bit expensive, but we are running totally async at a really low priority
|
||||||
startExpensiveCycle();
|
Set<String> unreadImages = parent.dbHelper.getAllStoryImages();
|
||||||
com.newsblur.util.Log.d(this, "story images to prefetch: " + StoryImageQueue.size());
|
Set<String> fetchedImages = new HashSet<String>();
|
||||||
// on each batch, re-query the DB for images associated with yet-unread stories
|
Set<String> batch = new HashSet<String>(AppConstants.IMAGE_PREFETCH_BATCH_SIZE);
|
||||||
// this is a bit expensive, but we are running totally async at a really low priority
|
batchloop: for (String url : StoryImageQueue) {
|
||||||
Set<String> unreadImages = parent.dbHelper.getAllStoryImages();
|
batch.add(url);
|
||||||
Set<String> fetchedImages = new HashSet<String>();
|
if (batch.size() >= AppConstants.IMAGE_PREFETCH_BATCH_SIZE) break batchloop;
|
||||||
Set<String> batch = new HashSet<String>(AppConstants.IMAGE_PREFETCH_BATCH_SIZE);
|
|
||||||
batchloop: for (String url : StoryImageQueue) {
|
|
||||||
batch.add(url);
|
|
||||||
if (batch.size() >= AppConstants.IMAGE_PREFETCH_BATCH_SIZE) break batchloop;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
fetchloop: for (String url : batch) {
|
|
||||||
if (parent.stopSync()) break fetchloop;
|
|
||||||
// dont fetch the image if the associated story was marked read before we got to it
|
|
||||||
if (unreadImages.contains(url)) {
|
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "prefetching image: " + url);
|
|
||||||
storyImageCache.cacheFile(url);
|
|
||||||
}
|
|
||||||
fetchedImages.add(url);
|
|
||||||
}
|
}
|
||||||
} finally {
|
try {
|
||||||
StoryImageQueue.removeAll(fetchedImages);
|
fetchloop: for (String url : batch) {
|
||||||
com.newsblur.util.Log.d(this, "story images fetched: " + fetchedImages.size());
|
if (parent.stopSync()) break fetchloop;
|
||||||
gotWork();
|
// dont fetch the image if the associated story was marked read before we got to it
|
||||||
}
|
if (unreadImages.contains(url)) {
|
||||||
}
|
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "prefetching image: " + url);
|
||||||
|
storyImageCache.cacheFile(url);
|
||||||
if (parent.stopSync()) return;
|
}
|
||||||
|
fetchedImages.add(url);
|
||||||
while (ThumbnailQueue.size() > 0) {
|
|
||||||
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
|
||||||
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
|
||||||
|
|
||||||
startExpensiveCycle();
|
|
||||||
com.newsblur.util.Log.d(this, "story thumbs to prefetch: " + StoryImageQueue.size());
|
|
||||||
// on each batch, re-query the DB for images associated with yet-unread stories
|
|
||||||
// this is a bit expensive, but we are running totally async at a really low priority
|
|
||||||
Set<String> unreadImages = parent.dbHelper.getAllStoryThumbnails();
|
|
||||||
Set<String> fetchedImages = new HashSet<String>();
|
|
||||||
Set<String> batch = new HashSet<String>(AppConstants.IMAGE_PREFETCH_BATCH_SIZE);
|
|
||||||
batchloop: for (String url : ThumbnailQueue) {
|
|
||||||
batch.add(url);
|
|
||||||
if (batch.size() >= AppConstants.IMAGE_PREFETCH_BATCH_SIZE) break batchloop;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
fetchloop: for (String url : batch) {
|
|
||||||
if (parent.stopSync()) break fetchloop;
|
|
||||||
// dont fetch the image if the associated story was marked read before we got to it
|
|
||||||
if (unreadImages.contains(url)) {
|
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "prefetching thumbnail: " + url);
|
|
||||||
thumbnailCache.cacheFile(url);
|
|
||||||
}
|
}
|
||||||
fetchedImages.add(url);
|
} finally {
|
||||||
|
StoryImageQueue.removeAll(fetchedImages);
|
||||||
|
com.newsblur.util.Log.d(this, "story images fetched: " + fetchedImages.size());
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
ThumbnailQueue.removeAll(fetchedImages);
|
|
||||||
com.newsblur.util.Log.d(this, "story thumbs fetched: " + fetchedImages.size());
|
|
||||||
gotWork();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parent.stopSync()) return;
|
||||||
|
|
||||||
|
while (ThumbnailQueue.size() > 0) {
|
||||||
|
if (! PrefsUtils.isImagePrefetchEnabled(parent)) return;
|
||||||
|
if (! PrefsUtils.isBackgroundNetworkAllowed(parent)) return;
|
||||||
|
|
||||||
|
startExpensiveCycle();
|
||||||
|
com.newsblur.util.Log.d(this, "story thumbs to prefetch: " + StoryImageQueue.size());
|
||||||
|
// on each batch, re-query the DB for images associated with yet-unread stories
|
||||||
|
// this is a bit expensive, but we are running totally async at a really low priority
|
||||||
|
Set<String> unreadImages = parent.dbHelper.getAllStoryThumbnails();
|
||||||
|
Set<String> fetchedImages = new HashSet<String>();
|
||||||
|
Set<String> batch = new HashSet<String>(AppConstants.IMAGE_PREFETCH_BATCH_SIZE);
|
||||||
|
batchloop: for (String url : ThumbnailQueue) {
|
||||||
|
batch.add(url);
|
||||||
|
if (batch.size() >= AppConstants.IMAGE_PREFETCH_BATCH_SIZE) break batchloop;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
fetchloop: for (String url : batch) {
|
||||||
|
if (parent.stopSync()) break fetchloop;
|
||||||
|
// dont fetch the image if the associated story was marked read before we got to it
|
||||||
|
if (unreadImages.contains(url)) {
|
||||||
|
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "prefetching thumbnail: " + url);
|
||||||
|
thumbnailCache.cacheFile(url);
|
||||||
|
}
|
||||||
|
fetchedImages.add(url);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
ThumbnailQueue.removeAll(fetchedImages);
|
||||||
|
com.newsblur.util.Log.d(this, "story thumbs fetched: " + fetchedImages.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
activelyRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addUrl(String url) {
|
public void addUrl(String url) {
|
||||||
|
@ -118,27 +118,10 @@ public class ImagePrefetchService extends SubService {
|
||||||
return (StoryImageQueue.size() + ThumbnailQueue.size());
|
return (StoryImageQueue.size() + ThumbnailQueue.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean haveWork() {
|
|
||||||
return (getPendingCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
public static void clear() {
|
||||||
StoryImageQueue.clear();
|
StoryImageQueue.clear();
|
||||||
ThumbnailQueue.clear();
|
ThumbnailQueue.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean running() {
|
|
||||||
return Running;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void setRunning(boolean running) {
|
|
||||||
Running = running;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected boolean isRunning() {
|
|
||||||
return Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package com.newsblur.service;
|
package com.newsblur.service;
|
||||||
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
|
import android.app.job.JobParameters;
|
||||||
|
import android.app.job.JobService;
|
||||||
import android.content.ComponentCallbacks2;
|
import android.content.ComponentCallbacks2;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -9,7 +11,6 @@ import android.database.Cursor;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.newsblur.R;
|
import com.newsblur.R;
|
||||||
import com.newsblur.activity.NbActivity;
|
import com.newsblur.activity.NbActivity;
|
||||||
|
@ -31,6 +32,7 @@ import com.newsblur.util.AppConstants;
|
||||||
import com.newsblur.util.DefaultFeedView;
|
import com.newsblur.util.DefaultFeedView;
|
||||||
import com.newsblur.util.FeedSet;
|
import com.newsblur.util.FeedSet;
|
||||||
import com.newsblur.util.FileCache;
|
import com.newsblur.util.FileCache;
|
||||||
|
import com.newsblur.util.Log;
|
||||||
import com.newsblur.util.NetworkUtils;
|
import com.newsblur.util.NetworkUtils;
|
||||||
import com.newsblur.util.NotificationUtils;
|
import com.newsblur.util.NotificationUtils;
|
||||||
import com.newsblur.util.PrefsUtils;
|
import com.newsblur.util.PrefsUtils;
|
||||||
|
@ -66,9 +68,9 @@ import java.util.concurrent.TimeUnit;
|
||||||
* after sync operations are performed. Activities can then refresh views and
|
* after sync operations are performed. Activities can then refresh views and
|
||||||
* query this class to see if progress indicators should be active.
|
* query this class to see if progress indicators should be active.
|
||||||
*/
|
*/
|
||||||
public class NBSyncService extends Service {
|
public class NBSyncService extends JobService {
|
||||||
|
|
||||||
private static final Object WAKELOCK_MUTEX = new Object();
|
private static final Object COMPLETION_CALLBACKS_MUTEX = new Object();
|
||||||
private static final Object PENDING_FEED_MUTEX = new Object();
|
private static final Object PENDING_FEED_MUTEX = new Object();
|
||||||
|
|
||||||
private volatile static boolean ActionsRunning = false;
|
private volatile static boolean ActionsRunning = false;
|
||||||
|
@ -88,7 +90,6 @@ public class NBSyncService extends Service {
|
||||||
public volatile static Boolean isPremium = null;
|
public volatile static Boolean isPremium = null;
|
||||||
public volatile static Boolean isStaff = null;
|
public volatile static Boolean isStaff = null;
|
||||||
|
|
||||||
private volatile static boolean isMemoryLow = false;
|
|
||||||
private static long lastFeedCount = 0L;
|
private static long lastFeedCount = 0L;
|
||||||
private static long lastFFConnMillis = 0L;
|
private static long lastFFConnMillis = 0L;
|
||||||
private static long lastFFReadMillis = 0L;
|
private static long lastFFReadMillis = 0L;
|
||||||
|
@ -130,16 +131,18 @@ public class NBSyncService extends Service {
|
||||||
Set<String> disabledFeedIds = new HashSet<String>();
|
Set<String> disabledFeedIds = new HashSet<String>();
|
||||||
|
|
||||||
private ExecutorService primaryExecutor;
|
private ExecutorService primaryExecutor;
|
||||||
|
private List<Integer> outstandingStartIds = new ArrayList<Integer>();
|
||||||
|
private List<JobParameters> outstandingStartParams = new ArrayList<JobParameters>();
|
||||||
|
private boolean mainSyncRunning = false;
|
||||||
CleanupService cleanupService;
|
CleanupService cleanupService;
|
||||||
OriginalTextService originalTextService;
|
OriginalTextService originalTextService;
|
||||||
UnreadsService unreadsService;
|
UnreadsService unreadsService;
|
||||||
ImagePrefetchService imagePrefetchService;
|
ImagePrefetchService imagePrefetchService;
|
||||||
|
private boolean forceHalted = false;
|
||||||
|
|
||||||
PowerManager.WakeLock wl = null;
|
|
||||||
APIManager apiManager;
|
APIManager apiManager;
|
||||||
BlurDatabaseHelper dbHelper;
|
BlurDatabaseHelper dbHelper;
|
||||||
FileCache iconCache;
|
FileCache iconCache;
|
||||||
private int lastStartIdCompleted = -1;
|
|
||||||
|
|
||||||
/** The time of the last hard API failure we encountered. Used to implement back-off so that the sync
|
/** The time of the last hard API failure we encountered. Used to implement back-off so that the sync
|
||||||
service doesn't spin in the background chewing up battery when the API is unavailable. */
|
service doesn't spin in the background chewing up battery when the API is unavailable. */
|
||||||
|
@ -152,10 +155,6 @@ public class NBSyncService extends Service {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
com.newsblur.util.Log.d(this, "onCreate");
|
com.newsblur.util.Log.d(this, "onCreate");
|
||||||
HaltNow = false;
|
HaltNow = false;
|
||||||
PowerManager pm = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
|
|
||||||
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getSimpleName());
|
|
||||||
wl.setReferenceCounted(true);
|
|
||||||
|
|
||||||
primaryExecutor = Executors.newFixedThreadPool(1);
|
primaryExecutor = Executors.newFixedThreadPool(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +163,7 @@ public class NBSyncService extends Service {
|
||||||
* parts of construction in onCreate, but save them for when we are in our own thread.
|
* parts of construction in onCreate, but save them for when we are in our own thread.
|
||||||
*/
|
*/
|
||||||
private void finishConstruction() {
|
private void finishConstruction() {
|
||||||
if (apiManager == null) {
|
if ((apiManager == null) || (dbHelper == null)) {
|
||||||
apiManager = new APIManager(this);
|
apiManager = new APIManager(this);
|
||||||
dbHelper = new BlurDatabaseHelper(this);
|
dbHelper = new BlurDatabaseHelper(this);
|
||||||
iconCache = FileCache.asIconCache(this);
|
iconCache = FileCache.asIconCache(this);
|
||||||
|
@ -177,42 +176,89 @@ public class NBSyncService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called serially, once per "start" of the service. This serves as a wakeup call
|
* Kickoff hook for when we are started via Context.startService()
|
||||||
* that the service should check for outstanding work.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, final int startId) {
|
public int onStartCommand(Intent intent, int flags, final int startId) {
|
||||||
|
com.newsblur.util.Log.d(this, "onStartCommand");
|
||||||
// only perform a sync if the app is actually running or background syncs are enabled
|
// only perform a sync if the app is actually running or background syncs are enabled
|
||||||
if ((NbActivity.getActiveActivityCount() > 0) || PrefsUtils.isBackgroundNeeded(this)) {
|
if ((NbActivity.getActiveActivityCount() > 0) || PrefsUtils.isBackgroundNeeded(this)) {
|
||||||
|
HaltNow = false;
|
||||||
// Services actually get invoked on the main system thread, and are not
|
// Services actually get invoked on the main system thread, and are not
|
||||||
// allowed to do tangible work. We spawn a thread to do so.
|
// allowed to do tangible work. We spawn a thread to do so.
|
||||||
Runnable r = new Runnable() {
|
Runnable r = new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
doSync(startId);
|
mainSyncRunning = true;
|
||||||
|
doSync();
|
||||||
|
mainSyncRunning = false;
|
||||||
|
// record the startId so when the sync thread and all sub-service threads finish,
|
||||||
|
// we can report that this invocation completed.
|
||||||
|
synchronized (COMPLETION_CALLBACKS_MUTEX) {outstandingStartIds.add(startId);}
|
||||||
|
checkCompletion();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
primaryExecutor.execute(r);
|
primaryExecutor.execute(r);
|
||||||
} else {
|
} else {
|
||||||
com.newsblur.util.Log.d(this, "Skipping sync: app not active and background sync not enabled.");
|
com.newsblur.util.Log.i(this, "Skipping sync: app not active and background sync not enabled.");
|
||||||
stopSelf(startId);
|
synchronized (COMPLETION_CALLBACKS_MUTEX) {outstandingStartIds.add(startId);}
|
||||||
|
checkCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
// indicate to the system that the service should be alive when started, but
|
// indicate to the system that the service should be alive when started, but
|
||||||
// needn't necessarily persist under memory pressure
|
// needn't necessarily persist under memory pressure
|
||||||
return Service.START_NOT_STICKY;
|
return Service.START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kickoff hook for when we are started via a JobScheduler
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onStartJob(final JobParameters params) {
|
||||||
|
com.newsblur.util.Log.d(this, "onStartJob");
|
||||||
|
// only perform a sync if the app is actually running or background syncs are enabled
|
||||||
|
if ((NbActivity.getActiveActivityCount() > 0) || PrefsUtils.isBackgroundNeeded(this)) {
|
||||||
|
HaltNow = false;
|
||||||
|
// Services actually get invoked on the main system thread, and are not
|
||||||
|
// allowed to do tangible work. We spawn a thread to do so.
|
||||||
|
Runnable r = new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
mainSyncRunning = true;
|
||||||
|
doSync();
|
||||||
|
mainSyncRunning = false;
|
||||||
|
// record the JobParams so when the sync thread and all sub-service threads finish,
|
||||||
|
// we can report that this invocation completed.
|
||||||
|
synchronized (COMPLETION_CALLBACKS_MUTEX) {outstandingStartParams.add(params);}
|
||||||
|
checkCompletion();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
primaryExecutor.execute(r);
|
||||||
|
} else {
|
||||||
|
com.newsblur.util.Log.d(this, "Skipping sync: app not active and background sync not enabled.");
|
||||||
|
synchronized (COMPLETION_CALLBACKS_MUTEX) {outstandingStartParams.add(params);}
|
||||||
|
checkCompletion();
|
||||||
|
}
|
||||||
|
return true; // indicate that we are async
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onStopJob(JobParameters params) {
|
||||||
|
com.newsblur.util.Log.d(this, "onStopJob");
|
||||||
|
HaltNow = true;
|
||||||
|
// return false to indicate that we don't necessarily need re-invocation ahead of schedule.
|
||||||
|
// background syncs can pick up where the last one left off and forground syncs aren't
|
||||||
|
// run via cancellable JobScheduler invocations.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do the actual work of syncing.
|
* Do the actual work of syncing.
|
||||||
*/
|
*/
|
||||||
private synchronized void doSync(final int startId) {
|
private synchronized void doSync() {
|
||||||
try {
|
try {
|
||||||
if (HaltNow) return;
|
if (HaltNow) return;
|
||||||
|
|
||||||
incrementRunningChild();
|
|
||||||
finishConstruction();
|
finishConstruction();
|
||||||
|
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "starting primary sync");
|
Log.d(this, "starting primary sync");
|
||||||
|
|
||||||
if (NbActivity.getActiveActivityCount() < 1) {
|
if (NbActivity.getActiveActivityCount() < 1) {
|
||||||
// if the UI isn't running, politely run at background priority
|
// if the UI isn't running, politely run at background priority
|
||||||
|
@ -250,16 +296,16 @@ public class NBSyncService extends Service {
|
||||||
NbActivity.updateAllActivities(NbActivity.UPDATE_DB_READY);
|
NbActivity.updateAllActivities(NbActivity.UPDATE_DB_READY);
|
||||||
|
|
||||||
// async text requests might have been queued up and are being waiting on by the live UI. give them priority
|
// async text requests might have been queued up and are being waiting on by the live UI. give them priority
|
||||||
originalTextService.start(startId);
|
originalTextService.start();
|
||||||
|
|
||||||
// first: catch up
|
// first: catch up
|
||||||
syncActions();
|
syncActions();
|
||||||
|
|
||||||
// if MD is stale, sync it first so unreads don't get backwards with story unread state
|
// if MD is stale, sync it first so unreads don't get backwards with story unread state
|
||||||
syncMetadata(startId);
|
syncMetadata();
|
||||||
|
|
||||||
// handle fetching of stories that are actively being requested by the live UI
|
// handle fetching of stories that are actively being requested by the live UI
|
||||||
syncPendingFeedStories(startId);
|
syncPendingFeedStories();
|
||||||
|
|
||||||
// re-apply the local state of any actions executed before local UI interaction
|
// re-apply the local state of any actions executed before local UI interaction
|
||||||
finishActions();
|
finishActions();
|
||||||
|
@ -268,20 +314,18 @@ public class NBSyncService extends Service {
|
||||||
checkRecounts();
|
checkRecounts();
|
||||||
|
|
||||||
// async story and image prefetch are lower priority and don't affect active reading, do them last
|
// async story and image prefetch are lower priority and don't affect active reading, do them last
|
||||||
unreadsService.start(startId);
|
unreadsService.start();
|
||||||
imagePrefetchService.start(startId);
|
imagePrefetchService.start();
|
||||||
|
|
||||||
// almost all notifications will be pushed after the unreadsService gets new stories, but double-check
|
// almost all notifications will be pushed after the unreadsService gets new stories, but double-check
|
||||||
// here in case some made it through the feed sync loop first
|
// here in case some made it through the feed sync loop first
|
||||||
pushNotifications();
|
pushNotifications();
|
||||||
|
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "finishing primary sync");
|
Log.d(this, "finishing primary sync");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
com.newsblur.util.Log.e(this.getClass().getName(), "Sync error.", e);
|
com.newsblur.util.Log.e(this.getClass().getName(), "Sync error.", e);
|
||||||
} finally {
|
}
|
||||||
decrementRunningChild(startId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -397,7 +441,7 @@ public class NBSyncService extends Service {
|
||||||
if (HaltNow) return;
|
if (HaltNow) return;
|
||||||
if (FollowupActions.size() < 1) return;
|
if (FollowupActions.size() < 1) return;
|
||||||
|
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "double-checking " + FollowupActions.size() + " actions");
|
Log.d(this, "double-checking " + FollowupActions.size() + " actions");
|
||||||
int impactFlags = 0;
|
int impactFlags = 0;
|
||||||
for (ReadingAction ra : FollowupActions) {
|
for (ReadingAction ra : FollowupActions) {
|
||||||
int impact = ra.doLocal(dbHelper, true);
|
int impact = ra.doLocal(dbHelper, true);
|
||||||
|
@ -420,7 +464,7 @@ public class NBSyncService extends Service {
|
||||||
* The very first step of a sync - get the feed/folder list, unread counts, and
|
* The very first step of a sync - get the feed/folder list, unread counts, and
|
||||||
* unread hashes. Doing this resets pagination on the server!
|
* unread hashes. Doing this resets pagination on the server!
|
||||||
*/
|
*/
|
||||||
private void syncMetadata(int startId) {
|
private void syncMetadata() {
|
||||||
if (stopSync()) return;
|
if (stopSync()) return;
|
||||||
if (backoffBackgroundCalls()) return;
|
if (backoffBackgroundCalls()) return;
|
||||||
int untriedActions = dbHelper.getUntriedActionCount();
|
int untriedActions = dbHelper.getUntriedActionCount();
|
||||||
|
@ -559,8 +603,8 @@ public class NBSyncService extends Service {
|
||||||
com.newsblur.util.Log.i(this.getClass().getName(), "got feed list: " + getSpeedInfo());
|
com.newsblur.util.Log.i(this.getClass().getName(), "got feed list: " + getSpeedInfo());
|
||||||
|
|
||||||
UnreadsService.doMetadata();
|
UnreadsService.doMetadata();
|
||||||
unreadsService.start(startId);
|
unreadsService.start();
|
||||||
cleanupService.start(startId);
|
cleanupService.start();
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
FFSyncRunning = false;
|
FFSyncRunning = false;
|
||||||
|
@ -654,7 +698,7 @@ public class NBSyncService extends Service {
|
||||||
/**
|
/**
|
||||||
* Fetch stories needed because the user is actively viewing a feed or folder.
|
* Fetch stories needed because the user is actively viewing a feed or folder.
|
||||||
*/
|
*/
|
||||||
private void syncPendingFeedStories(int startId) {
|
private void syncPendingFeedStories() {
|
||||||
// track whether we actually tried to handle the feedset and found we had nothing
|
// track whether we actually tried to handle the feedset and found we had nothing
|
||||||
// more to do, in which case we will clear it
|
// more to do, in which case we will clear it
|
||||||
boolean finished = false;
|
boolean finished = false;
|
||||||
|
@ -679,6 +723,7 @@ public class NBSyncService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fs == null) {
|
if (fs == null) {
|
||||||
|
com.newsblur.util.Log.d(this.getClass().getName(), "No feed set to sync");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -731,7 +776,7 @@ public class NBSyncService extends Service {
|
||||||
finishActions();
|
finishActions();
|
||||||
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY | NbActivity.UPDATE_STATUS);
|
NbActivity.updateAllActivities(NbActivity.UPDATE_STORY | NbActivity.UPDATE_STATUS);
|
||||||
|
|
||||||
prefetchOriginalText(apiResponse, startId);
|
prefetchOriginalText(apiResponse);
|
||||||
|
|
||||||
FeedPagesSeen.put(fs, pageNumber);
|
FeedPagesSeen.put(fs, pageNumber);
|
||||||
totalStoriesSeen += apiResponse.stories.length;
|
totalStoriesSeen += apiResponse.stories.length;
|
||||||
|
@ -843,7 +888,7 @@ public class NBSyncService extends Service {
|
||||||
dbHelper.insertStories(apiResponse, false);
|
dbHelper.insertStories(apiResponse, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void prefetchOriginalText(StoriesResponse apiResponse, int startId) {
|
void prefetchOriginalText(StoriesResponse apiResponse) {
|
||||||
storyloop: for (Story story : apiResponse.stories) {
|
storyloop: for (Story story : apiResponse.stories) {
|
||||||
// only prefetch for unreads, so we don't grind to cache when the user scrolls
|
// only prefetch for unreads, so we don't grind to cache when the user scrolls
|
||||||
// through old read stories
|
// through old read stories
|
||||||
|
@ -856,10 +901,10 @@ public class NBSyncService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
originalTextService.startConditional(startId);
|
originalTextService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void prefetchImages(StoriesResponse apiResponse, int startId) {
|
void prefetchImages(StoriesResponse apiResponse) {
|
||||||
storyloop: for (Story story : apiResponse.stories) {
|
storyloop: for (Story story : apiResponse.stories) {
|
||||||
// only prefetch for unreads, so we don't grind to cache when the user scrolls
|
// only prefetch for unreads, so we don't grind to cache when the user scrolls
|
||||||
// through old read stories
|
// through old read stories
|
||||||
|
@ -874,7 +919,7 @@ public class NBSyncService extends Service {
|
||||||
imagePrefetchService.addThumbnailUrl(story.thumbnailUrl);
|
imagePrefetchService.addThumbnailUrl(story.thumbnailUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
imagePrefetchService.startConditional(startId);
|
imagePrefetchService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushNotifications() {
|
void pushNotifications() {
|
||||||
|
@ -892,27 +937,28 @@ public class NBSyncService extends Service {
|
||||||
closeQuietly(cUnread);
|
closeQuietly(cUnread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void incrementRunningChild() {
|
/**
|
||||||
synchronized (WAKELOCK_MUTEX) {
|
* Check to see if all async sync tasks have completed, indicating that sync can me marked as
|
||||||
wl.acquire();
|
* complete. Call this any time any individual sync task finishes.
|
||||||
}
|
*/
|
||||||
}
|
void checkCompletion() {
|
||||||
|
//Log.d(this, "checking completion");
|
||||||
void decrementRunningChild(int startId) {
|
if (mainSyncRunning) return;
|
||||||
synchronized (WAKELOCK_MUTEX) {
|
if ((cleanupService != null) && cleanupService.isRunning()) return;
|
||||||
if (wl == null) return;
|
if ((originalTextService != null) && originalTextService.isRunning()) return;
|
||||||
if (wl.isHeld()) {
|
if ((unreadsService != null) && unreadsService.isRunning()) return;
|
||||||
wl.release();
|
if ((imagePrefetchService != null) && imagePrefetchService.isRunning()) return;
|
||||||
|
Log.d(this, "confirmed completion");
|
||||||
|
// iff all threads have finished, mark all received work as completed
|
||||||
|
synchronized (COMPLETION_CALLBACKS_MUTEX) {
|
||||||
|
for (JobParameters params : outstandingStartParams) {
|
||||||
|
jobFinished(params, forceHalted);
|
||||||
}
|
}
|
||||||
// our wakelock reference counts. only stop the service if it is in the background and if
|
for (Integer startId : outstandingStartIds) {
|
||||||
// we are the last thread to release the lock.
|
stopSelf(startId);
|
||||||
if (!wl.isHeld()) {
|
|
||||||
if (NbActivity.getActiveActivityCount() < 1) {
|
|
||||||
stopSelf(startId);
|
|
||||||
}
|
|
||||||
lastStartIdCompleted = startId;
|
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "wakelock depleted");
|
|
||||||
}
|
}
|
||||||
|
outstandingStartIds.clear();
|
||||||
|
outstandingStartParams.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -945,18 +991,6 @@ public class NBSyncService extends Service {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onTrimMemory (int level) {
|
|
||||||
if (level > ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
|
|
||||||
isMemoryLow = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is also called when the UI is hidden, so double check if we need to
|
|
||||||
// stop
|
|
||||||
if ( (lastStartIdCompleted != -1) && (NbActivity.getActiveActivityCount() < 1)) {
|
|
||||||
stopSelf(lastStartIdCompleted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the main feed/folder list sync running and blocking?
|
* Is the main feed/folder list sync running and blocking?
|
||||||
*/
|
*/
|
||||||
|
@ -994,14 +1028,14 @@ public class NBSyncService extends Service {
|
||||||
if (OfflineNow) return context.getResources().getString(R.string.sync_status_offline);
|
if (OfflineNow) return context.getResources().getString(R.string.sync_status_offline);
|
||||||
if (HousekeepingRunning) return context.getResources().getString(R.string.sync_status_housekeeping);
|
if (HousekeepingRunning) return context.getResources().getString(R.string.sync_status_housekeeping);
|
||||||
if (FFSyncRunning) return context.getResources().getString(R.string.sync_status_ffsync);
|
if (FFSyncRunning) return context.getResources().getString(R.string.sync_status_ffsync);
|
||||||
if (CleanupService.running()) return context.getResources().getString(R.string.sync_status_cleanup);
|
if (CleanupService.activelyRunning) return context.getResources().getString(R.string.sync_status_cleanup);
|
||||||
if (brief && !AppConstants.VERBOSE_LOG) return null;
|
if (brief && !AppConstants.VERBOSE_LOG) return null;
|
||||||
if (ActionsRunning) return String.format(context.getResources().getString(R.string.sync_status_actions), lastActionCount);
|
if (ActionsRunning) return String.format(context.getResources().getString(R.string.sync_status_actions), lastActionCount);
|
||||||
if (RecountsRunning) return context.getResources().getString(R.string.sync_status_recounts);
|
if (RecountsRunning) return context.getResources().getString(R.string.sync_status_recounts);
|
||||||
if (StorySyncRunning) return context.getResources().getString(R.string.sync_status_stories);
|
if (StorySyncRunning) return context.getResources().getString(R.string.sync_status_stories);
|
||||||
if (UnreadsService.running()) return String.format(context.getResources().getString(R.string.sync_status_unreads), UnreadsService.getPendingCount());
|
if (UnreadsService.activelyRunning) return String.format(context.getResources().getString(R.string.sync_status_unreads), UnreadsService.getPendingCount());
|
||||||
if (OriginalTextService.running()) return String.format(context.getResources().getString(R.string.sync_status_text), OriginalTextService.getPendingCount());
|
if (OriginalTextService.activelyRunning) return String.format(context.getResources().getString(R.string.sync_status_text), OriginalTextService.getPendingCount());
|
||||||
if (ImagePrefetchService.running()) return String.format(context.getResources().getString(R.string.sync_status_images), ImagePrefetchService.getPendingCount());
|
if (ImagePrefetchService.activelyRunning) return String.format(context.getResources().getString(R.string.sync_status_images), ImagePrefetchService.getPendingCount());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1047,7 +1081,7 @@ public class NBSyncService extends Service {
|
||||||
PendingFeed = fs;
|
PendingFeed = fs;
|
||||||
PendingFeedTarget = desiredStoryCount;
|
PendingFeedTarget = desiredStoryCount;
|
||||||
|
|
||||||
//if (AppConstants.VERBOSE_LOG) Log.d(NBSyncService.class.getName(), "callerhas: " + callerSeen + " have:" + alreadySeen + " want:" + desiredStoryCount + " pending:" + alreadyPending);
|
//Log.d(NBSyncService.class.getName(), "callerhas: " + callerSeen + " have:" + alreadySeen + " want:" + desiredStoryCount + " pending:" + alreadyPending);
|
||||||
|
|
||||||
if (!fs.equals(LastFeedSet)) {
|
if (!fs.equals(LastFeedSet)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1144,15 +1178,15 @@ public class NBSyncService extends Service {
|
||||||
ImagePrefetchService.clear();
|
ImagePrefetchService.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void resumeFromInterrupt() {
|
|
||||||
HaltNow = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
try {
|
try {
|
||||||
com.newsblur.util.Log.d(this, "onDestroy - stopping execution");
|
com.newsblur.util.Log.d(this, "onDestroy");
|
||||||
HaltNow = true;
|
synchronized (COMPLETION_CALLBACKS_MUTEX) {
|
||||||
|
if ((outstandingStartIds.size() > 0) || (outstandingStartParams.size() > 0)) {
|
||||||
|
com.newsblur.util.Log.w(this, "Service scheduler destroyed before all jobs marked done?");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (cleanupService != null) cleanupService.shutdown();
|
if (cleanupService != null) cleanupService.shutdown();
|
||||||
if (unreadsService != null) unreadsService.shutdown();
|
if (unreadsService != null) unreadsService.shutdown();
|
||||||
if (originalTextService != null) originalTextService.shutdown();
|
if (originalTextService != null) originalTextService.shutdown();
|
||||||
|
@ -1166,21 +1200,15 @@ public class NBSyncService extends Service {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dbHelper != null) dbHelper.close();
|
if (dbHelper != null) {
|
||||||
com.newsblur.util.Log.d(this, "onDestroy - execution halted");
|
dbHelper.close();
|
||||||
super.onDestroy();
|
dbHelper = null;
|
||||||
|
}
|
||||||
|
com.newsblur.util.Log.d(this, "onDestroy done");
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
com.newsblur.util.Log.e(this, "unclean shutdown", ex);
|
com.newsblur.util.Log.e(this, "unclean shutdown", ex);
|
||||||
}
|
}
|
||||||
}
|
super.onDestroy();
|
||||||
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isMemoryLow() {
|
|
||||||
return isMemoryLow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSpeedInfo() {
|
public static String getSpeedInfo() {
|
||||||
|
|
|
@ -13,13 +13,13 @@ import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class OriginalTextService extends SubService {
|
public class OriginalTextService extends SubService {
|
||||||
|
|
||||||
|
public static boolean activelyRunning = false;
|
||||||
|
|
||||||
// special value for when the API responds that it could fatally could not fetch text
|
// special value for when the API responds that it could fatally could not fetch text
|
||||||
public static final String NULL_STORY_TEXT = "__NULL_STORY_TEXT__";
|
public static final String NULL_STORY_TEXT = "__NULL_STORY_TEXT__";
|
||||||
|
|
||||||
private static final Pattern imgSniff = Pattern.compile("<img[^>]*src=(['\"])((?:(?!\\1).)*)\\1[^>]*>", Pattern.CASE_INSENSITIVE);
|
private static final Pattern imgSniff = Pattern.compile("<img[^>]*src=(['\"])((?:(?!\\1).)*)\\1[^>]*>", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
private static volatile boolean Running = false;
|
|
||||||
|
|
||||||
/** story hashes we need to fetch (from newly found stories) */
|
/** story hashes we need to fetch (from newly found stories) */
|
||||||
private static Set<String> Hashes;
|
private static Set<String> Hashes;
|
||||||
static {Hashes = new HashSet<String>();}
|
static {Hashes = new HashSet<String>();}
|
||||||
|
@ -33,11 +33,15 @@ public class OriginalTextService extends SubService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exec() {
|
protected void exec() {
|
||||||
while ((Hashes.size() > 0) || (PriorityHashes.size() > 0)) {
|
activelyRunning = true;
|
||||||
if (parent.stopSync()) return;
|
try {
|
||||||
gotWork();
|
while ((Hashes.size() > 0) || (PriorityHashes.size() > 0)) {
|
||||||
fetchBatch(PriorityHashes);
|
if (parent.stopSync()) return;
|
||||||
fetchBatch(Hashes);
|
fetchBatch(PriorityHashes);
|
||||||
|
fetchBatch(Hashes);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
activelyRunning = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,27 +101,10 @@ public class OriginalTextService extends SubService {
|
||||||
return (Hashes.size() + PriorityHashes.size());
|
return (Hashes.size() + PriorityHashes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean haveWork() {
|
|
||||||
return (getPendingCount() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear() {
|
public static void clear() {
|
||||||
Hashes.clear();
|
Hashes.clear();
|
||||||
PriorityHashes.clear();
|
PriorityHashes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean running() {
|
|
||||||
return Running;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void setRunning(boolean running) {
|
|
||||||
Running = running;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean isRunning() {
|
|
||||||
return Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package com.newsblur.service;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class ServiceScheduleReceiver extends BroadcastReceiver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
Log.d(this.getClass().getName(), "starting sync service");
|
|
||||||
Intent i = new Intent(context, NBSyncService.class);
|
|
||||||
context.startService(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,14 +1,16 @@
|
||||||
package com.newsblur.service;
|
package com.newsblur.service;
|
||||||
|
|
||||||
|
import android.app.job.JobParameters;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.newsblur.activity.NbActivity;
|
import com.newsblur.activity.NbActivity;
|
||||||
import com.newsblur.util.AppConstants;
|
import com.newsblur.util.AppConstants;
|
||||||
|
import com.newsblur.util.Log;
|
||||||
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
|
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
|
||||||
|
@ -19,8 +21,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
public abstract class SubService {
|
public abstract class SubService {
|
||||||
|
|
||||||
protected NBSyncService parent;
|
protected NBSyncService parent;
|
||||||
private ExecutorService executor;
|
private ThreadPoolExecutor executor;
|
||||||
protected int startId;
|
|
||||||
private long cycleStartTime = 0L;
|
private long cycleStartTime = 0L;
|
||||||
|
|
||||||
private SubService() {
|
private SubService() {
|
||||||
|
@ -29,15 +30,13 @@ public abstract class SubService {
|
||||||
|
|
||||||
SubService(NBSyncService parent) {
|
SubService(NBSyncService parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
executor = Executors.newFixedThreadPool(1);
|
executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start(final int startId) {
|
public void start() {
|
||||||
if (parent.stopSync()) return;
|
|
||||||
parent.incrementRunningChild();
|
|
||||||
this.startId = startId;
|
|
||||||
Runnable r = new Runnable() {
|
Runnable r = new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (parent.stopSync()) return;
|
||||||
if (NbActivity.getActiveActivityCount() < 1) {
|
if (NbActivity.getActiveActivityCount() < 1) {
|
||||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE );
|
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE );
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,44 +44,37 @@ public abstract class SubService {
|
||||||
}
|
}
|
||||||
Thread.currentThread().setName(this.getClass().getName());
|
Thread.currentThread().setName(this.getClass().getName());
|
||||||
exec_();
|
exec_();
|
||||||
parent.decrementRunningChild(startId);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
executor.execute(r);
|
try {
|
||||||
}
|
executor.execute(r);
|
||||||
|
// enqueue a check task that will run strictly after the real one, so the callback
|
||||||
public void startConditional(int startId) {
|
// can effectively check queue size to see if there are queued tasks
|
||||||
if (haveWork()) start(startId);
|
executor.execute(new Runnable() {
|
||||||
}
|
public void run() {
|
||||||
|
parent.checkCompletion();
|
||||||
/**
|
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
|
||||||
* Stub - children should implement a queue check or ready check so that startConditional()
|
}
|
||||||
* can more efficiently allocate threads.
|
});
|
||||||
*/
|
} catch (RejectedExecutionException ree) {
|
||||||
protected boolean haveWork() {
|
// this is perfectly normal, as service soft-stop mechanics might have shut down our thread pool
|
||||||
return true;
|
// while peer subservices are still running
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void exec_() {
|
private synchronized void exec_() {
|
||||||
try {
|
try {
|
||||||
//if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService started");
|
|
||||||
exec();
|
exec();
|
||||||
//if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService completed");
|
|
||||||
cycleStartTime = 0;
|
cycleStartTime = 0;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
com.newsblur.util.Log.e(this.getClass().getName(), "Sync error.", e);
|
com.newsblur.util.Log.e(this.getClass().getName(), "Sync error.", e);
|
||||||
} finally {
|
}
|
||||||
if (isRunning()) {
|
|
||||||
setRunning(false);
|
|
||||||
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void exec();
|
protected abstract void exec();
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService stopping");
|
Log.d(this, "SubService stopping");
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
try {
|
try {
|
||||||
executor.awaitTermination(AppConstants.SHUTDOWN_SLACK_SECONDS, TimeUnit.SECONDS);
|
executor.awaitTermination(AppConstants.SHUTDOWN_SLACK_SECONDS, TimeUnit.SECONDS);
|
||||||
|
@ -90,21 +82,18 @@ public abstract class SubService {
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
} finally {
|
} finally {
|
||||||
if (AppConstants.VERBOSE_LOG) Log.d(this.getClass().getName(), "SubService stopped");
|
Log.d(this, "SubService stopped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void gotWork() {
|
|
||||||
setRunning(true);
|
|
||||||
NbActivity.updateAllActivities(NbActivity.UPDATE_STATUS);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void gotData(int updateType) {
|
protected void gotData(int updateType) {
|
||||||
NbActivity.updateAllActivities(updateType);
|
NbActivity.updateAllActivities(updateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void setRunning(boolean running);
|
public boolean isRunning() {
|
||||||
protected abstract boolean isRunning();
|
// don't advise completion until there are no tasks, or just one check task left
|
||||||
|
return (executor.getQueue().size() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
|
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
|
||||||
|
|
|
@ -16,7 +16,7 @@ import java.util.Set;
|
||||||
|
|
||||||
public class UnreadsService extends SubService {
|
public class UnreadsService extends SubService {
|
||||||
|
|
||||||
private static volatile boolean Running = false;
|
public static boolean activelyRunning = false;
|
||||||
|
|
||||||
private static volatile boolean doMetadata = false;
|
private static volatile boolean doMetadata = false;
|
||||||
|
|
||||||
|
@ -30,17 +30,20 @@ public class UnreadsService extends SubService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void exec() {
|
protected void exec() {
|
||||||
if (doMetadata) {
|
activelyRunning = true;
|
||||||
gotWork();
|
try {
|
||||||
syncUnreadList();
|
if (doMetadata) {
|
||||||
doMetadata = false;
|
syncUnreadList();
|
||||||
}
|
doMetadata = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (StoryHashQueue.size() > 0) {
|
if (StoryHashQueue.size() > 0) {
|
||||||
getNewUnreadStories();
|
getNewUnreadStories();
|
||||||
parent.pushNotifications();
|
parent.pushNotifications();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
activelyRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncUnreadList() {
|
private void syncUnreadList() {
|
||||||
|
@ -133,7 +136,6 @@ public class UnreadsService extends SubService {
|
||||||
boolean isEnableNotifications = PrefsUtils.isEnableNotifications(parent);
|
boolean isEnableNotifications = PrefsUtils.isEnableNotifications(parent);
|
||||||
if (! (isOfflineEnabled || isEnableNotifications)) return;
|
if (! (isOfflineEnabled || isEnableNotifications)) return;
|
||||||
|
|
||||||
gotWork();
|
|
||||||
startExpensiveCycle();
|
startExpensiveCycle();
|
||||||
|
|
||||||
List<String> hashBatch = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
|
List<String> hashBatch = new ArrayList(AppConstants.UNREAD_FETCH_BATCH_SIZE);
|
||||||
|
@ -161,8 +163,8 @@ public class UnreadsService extends SubService {
|
||||||
StoryHashQueue.remove(hash);
|
StoryHashQueue.remove(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.prefetchOriginalText(response, startId);
|
parent.prefetchOriginalText(response);
|
||||||
parent.prefetchImages(response, startId);
|
parent.prefetchImages(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,17 +204,5 @@ public class UnreadsService extends SubService {
|
||||||
return doMetadata;
|
return doMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean running() {
|
|
||||||
return Running;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void setRunning(boolean running) {
|
|
||||||
Running = running;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected boolean isRunning() {
|
|
||||||
return Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,12 @@ import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import android.app.job.JobInfo;
|
||||||
|
import android.app.job.JobParameters;
|
||||||
|
import android.app.job.JobScheduler;
|
||||||
|
import android.app.job.JobWorkItem;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
@ -49,7 +54,10 @@ public class FeedUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void triggerSync(Context c) {
|
public static void triggerSync(Context c) {
|
||||||
|
// NB: when our minSDKversion hits 28, it could be possible to start the service via the JobScheduler
|
||||||
|
// with the setImportantWhileForeground() flag via an enqueue() and get rid of all legacy startService
|
||||||
|
// code paths
|
||||||
Intent i = new Intent(c, NBSyncService.class);
|
Intent i = new Intent(c, NBSyncService.class);
|
||||||
c.startService(i);
|
c.startService(i);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,6 @@ public class PrefsUtils {
|
||||||
edit.putString(PrefConstants.PREF_COOKIE, cookie);
|
edit.putString(PrefConstants.PREF_COOKIE, cookie);
|
||||||
edit.putString(PrefConstants.PREF_UNIQUE_LOGIN, userName + "_" + System.currentTimeMillis());
|
edit.putString(PrefConstants.PREF_UNIQUE_LOGIN, userName + "_" + System.currentTimeMillis());
|
||||||
edit.commit();
|
edit.commit();
|
||||||
NBSyncService.resumeFromInterrupt();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkForUpgrade(Context context) {
|
public static boolean checkForUpgrade(Context context) {
|
||||||
|
@ -126,8 +125,6 @@ public class PrefsUtils {
|
||||||
s.append("\n");
|
s.append("\n");
|
||||||
s.append("server: ").append(APIConstants.isCustomServer() ? "default" : "custom");
|
s.append("server: ").append(APIConstants.isCustomServer() ? "default" : "custom");
|
||||||
s.append("\n");
|
s.append("\n");
|
||||||
s.append("memory: ").append(NBSyncService.isMemoryLow() ? "low" : "normal");
|
|
||||||
s.append("\n");
|
|
||||||
s.append("speed: ").append(NBSyncService.getSpeedInfo());
|
s.append("speed: ").append(NBSyncService.getSpeedInfo());
|
||||||
s.append("\n");
|
s.append("\n");
|
||||||
s.append("pending actions: ").append(NBSyncService.getPendingInfo());
|
s.append("pending actions: ").append(NBSyncService.getPendingInfo());
|
||||||
|
|
Loading…
Add table
Reference in a new issue