mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-31 21:41:33 +00:00
Use coroutines in sub services
This commit is contained in:
parent
b16e124bad
commit
b857f8f078
8 changed files with 109 additions and 141 deletions
|
@ -1,5 +1,6 @@
|
|||
package com.newsblur.service;
|
||||
|
||||
import com.newsblur.util.ExtensionsKt;
|
||||
import com.newsblur.util.PrefConstants;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
|
||||
|
@ -8,7 +9,7 @@ public class CleanupService extends SubService {
|
|||
public static boolean activelyRunning = false;
|
||||
|
||||
public CleanupService(NBSyncService parent) {
|
||||
super(parent);
|
||||
super(parent, ExtensionsKt.NBScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.newsblur.service;
|
|||
import android.util.Log;
|
||||
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.ExtensionsKt;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -19,7 +20,7 @@ public class ImagePrefetchService extends SubService {
|
|||
static Set<String> ThumbnailQueue = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
public ImagePrefetchService(NBSyncService parent) {
|
||||
super(parent);
|
||||
super(parent, ExtensionsKt.NBScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,6 +5,7 @@ import static com.newsblur.service.NBSyncReceiver.UPDATE_TEXT;
|
|||
import com.newsblur.database.DatabaseConstants;
|
||||
import com.newsblur.network.domain.StoryTextResponse;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.ExtensionsKt;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
@ -22,14 +23,12 @@ public class OriginalTextService extends SubService {
|
|||
private static final Pattern imgSniff = Pattern.compile("<img[^>]*src=(['\"])((?:(?!\\1).)*)\\1[^>]*>", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
/** story hashes we need to fetch (from newly found stories) */
|
||||
private static Set<String> Hashes;
|
||||
static {Hashes = new HashSet<String>();}
|
||||
private static final Set<String> Hashes = new HashSet<>();
|
||||
/** story hashes we should fetch ASAP (they are waiting on-screen) */
|
||||
private static Set<String> PriorityHashes;
|
||||
static {PriorityHashes = new HashSet<String>();}
|
||||
private static final Set<String> PriorityHashes = new HashSet<>();
|
||||
|
||||
public OriginalTextService(NBSyncService parent) {
|
||||
super(parent);
|
||||
super(parent, ExtensionsKt.NBScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
package com.newsblur.service;
|
||||
|
||||
import static com.newsblur.service.NBSyncReceiver.UPDATE_STATUS;
|
||||
|
||||
import android.os.Process;
|
||||
|
||||
import com.newsblur.NbApplication;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.Log;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
/**
|
||||
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
|
||||
* that can be run fully asynchronously from the main sync loop. Like all of the sync service,
|
||||
* flags and data used by these modules need to be static so that parts of the app without a
|
||||
* handle to the service object can access them.
|
||||
*/
|
||||
public abstract class SubService {
|
||||
|
||||
protected NBSyncService parent;
|
||||
private ThreadPoolExecutor executor;
|
||||
private long cycleStartTime = 0L;
|
||||
|
||||
private SubService() {
|
||||
; // no default construction
|
||||
}
|
||||
|
||||
SubService(NBSyncService parent) {
|
||||
this.parent = parent;
|
||||
executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Runnable r = new Runnable() {
|
||||
public void run() {
|
||||
if (parent.stopSync()) return;
|
||||
if (!NbApplication.isAppForeground()) {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE );
|
||||
} else {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT + Process.THREAD_PRIORITY_LESS_FAVORABLE + Process.THREAD_PRIORITY_LESS_FAVORABLE );
|
||||
}
|
||||
Thread.currentThread().setName(this.getClass().getName());
|
||||
exec_();
|
||||
}
|
||||
};
|
||||
try {
|
||||
executor.execute(r);
|
||||
// enqueue a check task that will run strictly after the real one, so the callback
|
||||
// can effectively check queue size to see if there are queued tasks
|
||||
executor.execute(new Runnable() {
|
||||
public void run() {
|
||||
parent.checkCompletion();
|
||||
parent.sendSyncUpdate(UPDATE_STATUS);
|
||||
}
|
||||
});
|
||||
} catch (RejectedExecutionException ree) {
|
||||
// this is perfectly normal, as service soft-stop mechanics might have shut down our thread pool
|
||||
// while peer subservices are still running
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void exec_() {
|
||||
try {
|
||||
exec();
|
||||
cycleStartTime = 0;
|
||||
} catch (Exception e) {
|
||||
com.newsblur.util.Log.e(this.getClass().getName(), "Sync error.", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void exec();
|
||||
|
||||
public void shutdown() {
|
||||
Log.d(this, "SubService stopping");
|
||||
executor.shutdown();
|
||||
try {
|
||||
executor.awaitTermination(AppConstants.SHUTDOWN_SLACK_SECONDS, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
Log.d(this, "SubService stopped");
|
||||
}
|
||||
}
|
||||
|
||||
public 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
|
||||
* defined in AppConstants by sleeping for a short while so the SubService does not dominate system
|
||||
* resources.
|
||||
*/
|
||||
protected void startExpensiveCycle() {
|
||||
if (cycleStartTime == 0) {
|
||||
cycleStartTime = System.nanoTime();
|
||||
return;
|
||||
}
|
||||
|
||||
double lastCycleTime = (System.nanoTime() - cycleStartTime);
|
||||
if (lastCycleTime < 1) return;
|
||||
|
||||
cycleStartTime = System.nanoTime();
|
||||
|
||||
double cooloffTime = lastCycleTime * (1.0 - AppConstants.MAX_BG_DUTY_CYCLE);
|
||||
if (cooloffTime < 1) return;
|
||||
long cooloffTimeMs = Math.round(cooloffTime / 1000000.0);
|
||||
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS;
|
||||
|
||||
if (NbApplication.isAppForeground()) {
|
||||
com.newsblur.util.Log.d(this.getClass().getName(), "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.");
|
||||
try {
|
||||
Thread.sleep(cooloffTimeMs);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package com.newsblur.service
|
||||
|
||||
import com.newsblur.NbApplication.Companion.isAppForeground
|
||||
import com.newsblur.util.AppConstants
|
||||
import com.newsblur.util.Log
|
||||
import com.newsblur.util.NBScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.CancellationException
|
||||
|
||||
/**
|
||||
* A utility construct to make NbSyncService a bit more modular by encapsulating sync tasks
|
||||
* that can be run fully asynchronously from the main sync loop. Like all of the sync service,
|
||||
* flags and data used by these modules need to be static so that parts of the app without a
|
||||
* handle to the service object can access them.
|
||||
*/
|
||||
abstract class SubService(
|
||||
@JvmField
|
||||
protected val parent: NBSyncService,
|
||||
private val coroutineScope: CoroutineScope = NBScope,
|
||||
) {
|
||||
|
||||
private var cycleStartTime = 0L
|
||||
|
||||
protected abstract fun exec()
|
||||
|
||||
fun start() {
|
||||
coroutineScope.launch {
|
||||
if (!parent.stopSync()) {
|
||||
execInternal()
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
parent.checkCompletion()
|
||||
parent.sendSyncUpdate(NBSyncReceiver.UPDATE_STATUS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun execInternal() = coroutineScope {
|
||||
try {
|
||||
ensureActive()
|
||||
exec()
|
||||
cycleStartTime = 0
|
||||
} catch (e: Exception) {
|
||||
Log.e(this.javaClass.name, "Sync error.", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
Log.d(this, "SubService shutdown")
|
||||
try {
|
||||
coroutineScope.cancel()
|
||||
} catch (e: CancellationException) {
|
||||
Log.d(this, "SubService cancelled")
|
||||
} finally {
|
||||
Log.d(this, "SubService stopped")
|
||||
}
|
||||
}
|
||||
|
||||
// don't advise completion until there are no tasks, or just one check task left
|
||||
val isRunning: Boolean
|
||||
get() =// don't advise completion until there are no tasks, or just one check task left
|
||||
coroutineScope.isActive
|
||||
|
||||
/**
|
||||
* If called at the beginning of an expensive loop in a SubService, enforces the maximum duty cycle
|
||||
* defined in AppConstants by sleeping for a short while so the SubService does not dominate system
|
||||
* resources.
|
||||
*/
|
||||
protected fun startExpensiveCycle() {
|
||||
if (cycleStartTime == 0L) {
|
||||
cycleStartTime = System.nanoTime()
|
||||
return
|
||||
}
|
||||
val lastCycleTime = (System.nanoTime() - cycleStartTime).toDouble()
|
||||
if (lastCycleTime < 1) return
|
||||
cycleStartTime = System.nanoTime()
|
||||
val cooloffTime = lastCycleTime * (1.0 - AppConstants.MAX_BG_DUTY_CYCLE)
|
||||
if (cooloffTime < 1) return
|
||||
var cooloffTimeMs = Math.round(cooloffTime / 1000000.0)
|
||||
if (cooloffTimeMs > AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS) cooloffTimeMs = AppConstants.DUTY_CYCLE_BACKOFF_CAP_MILLIS
|
||||
if (isAppForeground) {
|
||||
Log.d(this.javaClass.name, "Sleeping for : " + cooloffTimeMs + "ms to enforce max duty cycle.")
|
||||
try {
|
||||
Thread.sleep(cooloffTimeMs)
|
||||
} catch (e: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,8 +24,6 @@ import kotlinx.coroutines.launch
|
|||
*/
|
||||
class SubscriptionSyncService : JobService() {
|
||||
|
||||
private val scope = NBScope
|
||||
|
||||
override fun onStartJob(params: JobParameters?): Boolean {
|
||||
Log.d(this, "onStartJob")
|
||||
if (!PrefsUtils.hasCookie(this)) {
|
||||
|
@ -33,10 +31,10 @@ class SubscriptionSyncService : JobService() {
|
|||
return false
|
||||
}
|
||||
|
||||
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService, scope)
|
||||
val subscriptionManager = SubscriptionManagerImpl(this@SubscriptionSyncService)
|
||||
subscriptionManager.startBillingConnection(object : SubscriptionsListener {
|
||||
override fun onBillingConnectionReady() {
|
||||
scope.launch {
|
||||
NBScope.launch {
|
||||
subscriptionManager.syncActiveSubscription()
|
||||
Log.d(this, "sync active subscription completed.")
|
||||
// manually call jobFinished after work is done
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.newsblur.service;
|
|||
import com.newsblur.network.domain.StoriesResponse;
|
||||
import com.newsblur.network.domain.UnreadStoryHashesResponse;
|
||||
import com.newsblur.util.AppConstants;
|
||||
import com.newsblur.util.ExtensionsKt;
|
||||
import com.newsblur.util.FeedUtils;
|
||||
import com.newsblur.util.PrefsUtils;
|
||||
import com.newsblur.util.StateFilter;
|
||||
|
@ -26,7 +27,7 @@ public class UnreadsService extends SubService {
|
|||
static { StoryHashQueue = new ArrayList<String>(); }
|
||||
|
||||
public UnreadsService(NBSyncService parent) {
|
||||
super(parent);
|
||||
super(parent, ExtensionsKt.NBScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -20,6 +20,7 @@ fun <R> CoroutineScope.executeAsyncTask(
|
|||
withContext(Dispatchers.Main) { onPostExecute(result) }
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val NBScope = CoroutineScope(
|
||||
CoroutineName(TAG) +
|
||||
Dispatchers.Default +
|
||||
|
|
Loading…
Add table
Reference in a new issue