Android 职位经理

Job manager in Android

我有一项任务是 运行 在 Android 应用程序中进行多个不同的作业。每个作业都是 long-运行ning 并且消耗网络、数据库和文件系统。每个作业可以 运行 由用户手动或由 AlarmManager 安排。每个作业 运行s 直到结束是非常重要的,因此它需要在用户离开应用程序后继续 运行ning,甚至当用户根本没有打开应用程序时。工作有一些像这样的 ID 属性:

class Job {
    int id;
}

我需要这个假设的 JobManager 来接收作业并按 ID 对它们进行排序。如果 id = 1 的作业已经 运行ning,那么 JobManager 应该跳过所有后续的 id = 1 作业,直到该作业完成。但是如果提交了一个id=2的job,那么它就会被接受并且可以运行与第一个job并行

作业还应该保持唤醒锁定直到完成,就像在 CommonsWare 的 WakefulIntentService 中所做的那样。

我有几个实现方法,但都有各自的缺点:

    服务 class 的
  1. Subclass 运行 始终在后台运行,当由于某种原因被终止时会自动重新启动。缺点:即使不 运行ning 也会消耗资源,它是 运行ning 在 UI 线程上,所以我们必须像往常一样管理一些可以被系统杀死的线程,每个客户端必须启动该服务,但没有人知道何时停止它。
  2. 来自 CommonsWare 的 WakefulIntentService。缺点:因为它是 IntentService,所以它 运行 只是顺序的,所以它不能检查现有的 运行ning 作业。
  3. 布尔值 "running" 数据库中每个作业的标志。每次我们想要 运行 作业时检查它。缺点:对 db 的请求太多,难以正确实施,有时 2 个相同的作业仍然可以 运行 并行,不确定标志保持 "true" 以防出现任何意外错误。
  4. 为此目的设计的现有库。至于现在除了CWAC-Wakeful我发现:

    但我仍然不知道,如何将这些库用于 运行 一个集中式服务,该服务可以接受来自任何其他 Activity、服务、BroadcastReceiver、AlarmManager 等的作业,按 ID 和 运行 并行排序。

请告诉我在这种情况下可以使用什么解决方案。

更新: 请参阅下面我自己的解决方案。我不确定它是否适用于所有可能的情况。如果您知道由此可能出现的任何问题,请发表评论。

这似乎适合 Lollipop 上的新 JobScheduler API,然后您将不得不对其进行包装以实现 sdk 实现所缺少的所有功能。

如果您需要在低于 Lollipop 的版本上实现此功能,可以使用 compat 库。

如果有人遇到同样的问题,这是我想出的解决方案。我使用了 Robospice 库,因为它是 运行 服务上的一些作业并将结果同步回 Activity 的最可靠方式。由于我没有找到将此库与 WakeLocks 一起使用的任何方法,因此我扩展了 2 类:SpiceManager 和 SpiceRequest。新的类,WakefulSpiceManager和WakefulSpiceRequest,其实是借鉴了CommonsWare关于WakeLocks的思想,实现上很相似。

WakefulSpiceManager:

public class WakefulSpiceManager extends SpiceManager {
    private static final String NAME = "WakefulSpiceManager";
    private static volatile PowerManager.WakeLock wakeLock;
    private Context context;

    public WakefulSpiceManager(Context context, Class<? extends SpiceService> spiceServiceClass) {
        super(spiceServiceClass);
        this.context = context;
        start(context);
    }

    private static synchronized PowerManager.WakeLock getLock(Context context) {
        if (wakeLock == null) {
            PowerManager mgr = (PowerManager) context.getSystemService(Context.POWER_SERVICE);

            wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, NAME);
            wakeLock.setReferenceCounted(true);
        }

        return wakeLock;
    }

    public <T> void execute(WakefulSpiceRequest<T> request, RequestListener<T> requestListener) {
        PowerManager.WakeLock lock = getLock(context);
        lock.acquire();
        request.setLock(lock);

        // explicitly avoid caching
        super.execute(new CachedSpiceRequest<T>(request, null, ALWAYS_EXPIRED), requestListener);
    }
}

WakefulSpiceRequest:

public abstract class WakefulSpiceRequest<R> extends SpiceRequest<R> {
    private PowerManager.WakeLock lock;

    public WakefulSpiceRequest(Class<R> clazz) {
        super(clazz);
    }

    public void setLock(PowerManager.WakeLock lock) {
        this.lock = lock;
    }

    @Override
    public final R loadDataFromNetwork() throws Exception {
        try {
            return execute();
        } finally {
            if (lock.isHeld()) {
                lock.release();
            }
        }
    }

    public abstract R execute() throws Exception;
}

所以基本上每次我们要从 WakefulSpiceManager 发送请求时,我们都会获取锁。之后,锁被传递给 WakefulSpiceRequest。当请求完成其工作时,它会使用 release() 方法清除锁 - 即使带有 WakefulSpiceManager 的 activity 已经被销毁也会发生这种情况。

现在我们以通常的 Robospice 方式使用那些 类,唯一的例外是我们只需要传递 WakefulSpiceRequests 以在 WakefulSpiceManager 上执行:

    WakefulSpiceManager manager = new WakefulSpiceManager(context, MyService.class);
    manager.execute(new WakefulSpiceRequest<MyResult>(MyResult.class) {
        @Override
        public MyResult execute() throws Exception {
            return ...
        }
    }, new RequestListener<MyResult>() {
        @Override
        public void onRequestFailure(SpiceException e) {
            ...
        }

        @Override
        public void onRequestSuccess(MyResult result) {
            ...
        }
    });

Workmanager will help you schedule tasks in any order you want. You can easily set constraints to the job that you want to be en-queued along with many other advantages over JobScheduler API or alarm manager. Have a look at this video for a brief intro - https://www.youtube.com/watch?v=pErTyQpA390(21:44 处的 WorkManager)。

EDIT: Updated my ans to show the capabilities of the new API You will not need ids to handle the jobs with this one. You can simply enqueue the task and the rest will be handled by the API itself.

一些工作案例场景是

  • WorkManager.getInstance() .beginWith(workA) // Note: WorkManager.beginWith() returns a // WorkContinuation object; the following calls are // to WorkContinuation methods .then(workB) .then(workC) .enqueue();

  • WorkManager.getInstance() // First, run all the A tasks (in parallel): .beginWith(workA1, workA2, workA3) // ...when all A tasks are finished, run the single B task: .then(workB) // ...then run the C tasks (in any order): .then(workC1, workC2) .enqueue();