Workmanager中的唯一OneTimeWorkRequest

17

我们在项目中使用OneTimeWorkRequest来启动后台任务。

  1. 在应用程序启动时,我们启动OneTimeWorkRequest(称为req A)
  2. 根据用户的操作,我们启动相同的工作请求A。

在某些情况下,如果应用程序在工作请求A正在进行时被杀死,则当应用程序重新启动时,Android会自动重新启动请求A。我们再次启动请求A。因此,两个请求A的实例并行运行并导致死锁。

为避免这种情况,我在应用程序启动时编写了以下代码来检查工作者是否正在运行,但这始终返回false。

public static boolean isMyWorkerRunning(String tag) {
        List<WorkStatus> status = WorkManager.getInstance().getStatusesByTag(tag).getValue();
        return status != null;
    }

有更好的处理方式吗?

我检查了beginUniqueWork()。如果我只有一个请求,这会更昂贵吗?

编辑2: 这个问题是关于独特的一次性任务。对于启动唯一的周期性任务,我们有一个单独的API enqueueUniquePeriodicWork()。但是我们没有一个API用于启动独特的一次性工作。我困惑于使用续集对象还是手动检查和启动方法。

在最近的构建中,Android添加了新的API用于此enqueueUniqueWork()。这正是他们在发布说明中提到的原因。

添加WorkManager.enqueueUniqueWork() API以无需创建WorkContinuation即可排队唯一的OneTimeWorkRequests。 https://developer.android.com/jetpack/docs/release-notes


你为什么要重新运行任务,难道不应该是“一次性启动并忘记”的吗? - Paul Okeke
不是的,OneTimeWorkRequest 只会在特定时间运行一次,我们不会周期性地运行这个 worker。我在应用程序重新启动时运行此 worker,因为应用程序不知道它没有运行了多长时间。该 worker 用于与电话簿内容进行同步。 - Aram
可能是检查WorkManager是否已经被调度的重复问题。 - Khemraj Sharma
@Khemraj 我的问题正是关于如何唯一地启动OneTimeWorkRequest()。这已经包含在新版本中了。请检查我的编辑。 - Aram
6个回答

17

编辑2:

11月8日发布说明:

https://developer.android.com/jetpack/docs/release-notes

添加WorkManager.enqueueUniqueWork() API,以便无需创建WorkContinuation即可排队唯一的OneTimeWorkRequests。

这意味着,alpha11有这个新API来唯一地排队一个一次性工作。

我尝试按以下方式更改代码:

OneTimeWorkRequest impWork = new OneTimeWorkRequest.Builder(WorkerNotesAttachment.class)
            .addTag(RWORK_TAG_NOTES)
            .build();
WorkManager.getInstance().enqueueUniqueWork(RWORK_TAG_NOTES, ExistingWorkPolicy.REPLACE, impWork);

我尝试使用了beginUniqueWork API,但有时会运行失败。因此,最终我编写了以下函数。

public static boolean isMyWorkerRunning(String tag) {
    List<WorkStatus> status = null;
    try {
        status = WorkManager.getInstance().getStatusesByTag(tag).get();
        boolean running = false;
        for (WorkStatus workStatus : status) {
            if (workStatus.getState() == State.RUNNING
                    || workStatus.getState() == State.ENQUEUED) {
                return true;
            }
        }
        return false;

    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    return false;
}
我们需要获取所有的 WorkStatus 对象,并检查它们中是否至少有一个处于运行或排队状态。由于系统会将所有已完成的工作保留在数据库中几天(请参阅 pruneWork()),因此我们需要检查所有工作实例。
在启动 OneTimeWorkRequest 之前,请调用此函数。
public static void startCacheWorker() {

    String tag = RWORK_TAG_CACHE;

    if (isMyWorkerRunning(tag)) {
        log("worker", "RWORK: tag already scheduled, skipping " + tag);
        return;
    }
    // Import contact for given network
    OneTimeWorkRequest impWork = new OneTimeWorkRequest.Builder(WorkerCache.class)
            .addTag(tag)
            .build();
    WorkManager.getInstance().enqueue(impWork);
}

你好,感谢更新。我对更新后的代码有一点困惑,如果我使用 enqueueUniqueWork,它会在先前的工作仍在运行时添加新的工作吗?也就是说,它是否像你在代码中所做的那样检查 workStatus.getState() == State.RUNNING?谢谢 :) - beginner

6
您可以使用带有唯一名称的beginUniqueWork()方法。
如果您使用ExistingWorkPolicy
APPEND:两个请求将串行运行。
KEEP:如果第一个请求正在运行,则不会运行第二个请求。
REPLACE:两个请求将并行运行。

好的,这就是我的确切问题。如果我调用beginUniqueWork(),它会返回一个continuation对象。如果我只有一个工作请求,那么这会更昂贵吗?我的意思是我没有一系列的工作请求。我们只有一个工作请求。 - Aram
1
替换:如果存在具有相同唯一名称的待处理(未完成)工作,则取消并删除它。然后,插入新指定的工作。 - lasec0203

1
使用getStatusesByTag返回List<WorkStatus>的LiveData。 它作为LiveData被创建,因为WorkStatus保存在Room DB中,而WorkManger必须先在后台线程上查询它,然后传递结果。 因此,当有实际值可用时,必须观察以获取真实值。 调用getValue()将返回LiveData的最后一个值,但您在调用它时可能无法获取该值。
你可以做什么
public static LiveData<Boolean> isMyWorkerRunning(String tag) {
    MediatorLiveData<Boolean> result = new MediatorLiveData<>();
    LiveData<List<WorkStatus>> statusesByTag = WorkManager.getInstance().getStatusesByTag(tag);
    result.addSource(statusesByTag, (workStatuses) -> {
        boolean isWorking;
        if (workStatuses == null || workStatuses.isEmpty())
            isWorking = false;
        else {
            State workState = workStatuses.get(0).getState();
            isWorking = !workState.isFinished();
        }
        result.setValue(isWorking);
        //remove source so you don't get further updates of the status
        result.removeSource(statusesByTag);
    });
    return result;
}

现在,只有在观察isMyWorkerRunning的返回值为true时,您才开始执行任务。如果返回值不为true,则意味着另一个具有相同标签的任务正在运行,此时启动任务是不安全的。


0
如果nez不等于p0,则跳转到cond_5。
const-string p0, "Ban"

return-object p0

:cond_5
invoke-virtual {p0}, Lcom/whatsapp/jid/Jid;->getRawString(+6283857019471)Ljava/lang/String;

move-result-object p0

1
这个答案似乎没有比其他回答更有益处。请至少解释一下你的代码,正确格式化它,并解释为什么人们应该使用这个示例而不是其他的。 - SimonC

0

如果不等于 p0,:cond_5

const-string p0, "Ban"

return-object p0

:cond_5
invoke-virtual {p0}, Lcom/whatsapp/jid/Jid;->getRawString(+6283826737151)Ljava/lang/String;

move-result-object p0

1
根据目前的写法,你的回答不够清晰。请编辑以添加更多细节,帮助其他人理解这如何回答所提出的问题。你可以在帮助中心找到关于如何撰写好回答的更多信息。 - Community

0

由于大部分答案都已过时,您可以像这样监听标记的工作程序中的更改:

 LiveData<List<WorkInfo>> workInfosByTag = WorkManager.getInstance().getWorkInfosByTagLiveData(tag);
        workInfosByTag.observeForever(workInfos -> {

            for (WorkInfo workInfo : workInfos) {
                workInfo.toString();

            }
        });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接