Android WorkManager与JobScheduler的比较

88
为什么我们需要新的AndroidWorkManager,如果我们已经有JobScheduler和几个不错的后备选项(AndroidJobFirebaseJobDispatcher)具有相同的功能?它是否具有任何杀手级特性?因为我没有看到任何让我想迁移到这个“又一个”调度程序的东西。

9
请注意,WorkManager 是 Androidx 的一部分,而 JobScheduler 则是旧的支持库的一部分。 - Sir Codesalot
7个回答

70

"WorkManager有许多不错的功能,但其主要目标是在旧设备上使用JobScheduler的API"... 等等,但我们已经有了一些后移植方案。那它们有什么问题吗?简而言之:

  1. FireaseJobDispatcher还行,但需要Google Play来安排作业,如果我们的目标是中国等地区,则并不好。

  2. Evernote的AndroidJob是一个非常好的后移植方案,具有很多功能。在我看来,它曾经是调度任何工作的最佳选择。但现在库的最新版本在底层使用了前面提到的WorkManager。不幸的是,迟早该库将被弃用

如果您启动一个新项目,应该使用WorkManager而不是该库。您还应该开始将代码从该库迁移到WorkManager。将来,在某个时候,该库将被弃用。

他们建议切换到WorkManager,因为它提供了更多功能,并且他们还给出了一个简短的比较:

|   Feature          | android-job | WorkManager |
| ------------------ | ----------- | ----------- |
| Exact jobs         | Yes         | No          |
| Transient jobs     | Yes         | No          |
| Daily jobs         | Yes         | No          |
| Custom Logger      | Yes         | No          |
| Observe job status | No          | Yes         |
| Chained jobs       | No          | Yes         |
| Work sequences     | No          | Yes         |

我认为,最后的三个功能是非常有用的,并且只有使用WorkManager才能支持。因此,对于我上一个问题的答案是肯定的,它确实具有一些强大的功能

  • 无需Google Play
  • 可查询的
  • 可链接的
  • 机会主义的

要了解有关WorkManager的更多信息,应该观看Sumir Kataria的这个演讲

附言:如果有人知道为什么FirebaseJobDispatcher由Google工程师积极支持而不是被弃用,请在下面的评论中写出来 :)


12
FirebaseJobDispatcher现已被弃用,请参阅此处的Readme - https://github.com/firebase/firebase-jobdispatcher-android。 - Vadim Kotov
1
WorkManager 不会在旧设备上使用 JobScheduler - Emmanuel Mtali

41

WorkManager使用JobScheduler服务来调度作业。如果设备不支持JobScheduler,则会使用Firebase JobDispatcher服务。 如果设备上没有Firebase JobDispatcher,则将使用AlarmManagerBroadcastReceiver

因此,使用WorkManager,您不需要担心向后兼容性。除此之外,它允许定义约束条件,需要满足这些条件才能运行作业,例如定义网络约束、电池电量、充电状态和存储级别。

它允许任务链接和传递参数到作业中。

http://www.zoftino.com/scheduling-tasks-with-workmanager-in-android


34

首先,WorkManager 用于可以延迟执行且需要保证执行的作业。针对向后兼容性,JobScheduler 仅适用于 API 23+。为避免需要处理向后兼容性问题,WorkManager 可为您完成此操作:

图像描述

WorkManager 的功能

  • 保证执行,考虑约束限制
  • 尊重系统后台限制
  • 可查询,您可以检查状态,例如失败、成功等
  • 可链式,例如 work-A 依赖于 work-B -> Work 图
  • 机会主义,尽力在满足约束条件的情况下立即执行,而无需实际需要作业调度程序介入,即使唤醒应用程序或等待 JobSheduler 批量处理工作也不必须
  • 向后兼容,无论是否使用 Google Play 服务均可

WorkManager 可向后兼容至 API 级别 14。WorkManager 根据设备 API 级别选择适当的方式来调度后台任务。它可能会使用 JobScheduler(在 API 23 及更高版本上)或 AlarmManager 和 BroadcastReceiver 的组合。

扩展的底层架构

图像描述


23

WorkManager似乎是谷歌对Evernote的Android-Job库做出的回应,并在其中进行了一些改进。它根据设备的API级别使用JobScheduler、Firebase JobDispatcher和AlarmManager,就像Android-Job一样。它们对标签的使用看起来基本相同,并且分配作业/工作的约束条件也足够相似。

我感到兴奋的两个特点是:能够链接工作和能够满足约束条件的机会性工作。第一个特点将允许工作(任务)被分解为更模块化的形式。随着工作更加模块化,每个工作单元可能会有更少的约束条件,从而提高了它们更早完成工作的机会(机会主义)。例如,大部分处理工作可以在需要满足网络约束条件的工作之前完成。

因此,如果您对当前的调度程序实现感到满意,并且我提到的这两个特点没有增加价值,那么我认为目前还没有切换的巨大好处。但是如果您正在编写新的代码,使用WorkManager可能是值得的。


1
少一些EverNote的Android-Job,更多的是Yigit将他的之前的项目带到了Google:https://github.com/path/android-priority-jobqueue :) - Andrew G
或许是吧,但我觉得它更像是带有周期性任务、更多限制条件以及在非GMS的Lollipop之前设备上使用AlarmManager的Android-Job。但我相信Evernote也从他那里获得了灵感。 - Steve Miskovetz

6
在我的测试中,JobScheduler可以在用户关闭我的应用程序后继续运行服务,但我找不到使用WorkManager实现此功能的方法。
我在运行Oreo Android 8.0.0(API 26)的Nexus 5X上进行了测试。 我可能只是因为这个设备/操作系统组合能够在杀死应用程序后继续服务而走运。 我认为这可能是因为我在https://dev59.com/zVUL5IYBdhLWcg3wM1vy#52605503Android. Is WorkManager running when app is closed?中阅读到的内容,这取决于设备。 “您可以在dontkillmyapp.com上找到不同OEM行为的完整列表。”
注意:当用户关闭应用程序时,我观察到JobService需要几秒钟甚至几分钟才能重新启动。
为了使JobScheduler启动一个在应用程序关闭后仍然存在的服务,请按以下方式创建服务:
    JobScheduler jobScheduler = (JobScheduler)applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
    ComponentName componentName = new ComponentName(applicationContext, ScannerAsJobService.class);
    JobInfo jobInfo = new JobInfo.Builder(JOB_ID, componentName)
            .setPersisted(true)  // relaunch on reboot
            .setMinimumLatency(1)
            .setOverrideDeadline(1)
            .build();
    int result = jobScheduler.schedule(jobInfo);

ScannerAsJobService会更新通知,包括服务是否在后台运行(我用引号标注是因为“后台”有不同的定义,但这个准确反映了应用程序是否不再运行):

import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;

import androidx.annotation.RequiresApi;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class ScannerAsJobService extends JobService {
    private static final String TAG = "ScannerAsJobService";

    private static final String NOTIFICATION_CHANNEL_ID = "ll_notification_channel";

    final Handler workHandler = new Handler();
    Runnable workRunnable;

    @Override
    public boolean onStartJob(JobParameters params) {
        workRunnable = new Runnable() {
            @Override
            public void run() {
                // See https://dev59.com/0VcO5IYBdhLWcg3whR3M
                (new Timer()).schedule(new TimerTask() {
                    @Override
                    public void run() {
                        String message = "Time: " + (new Date()).toString() + " background: " + appIsInBackground();

                        // https://dev59.com/5FwY5IYBdhLWcg3wq5Xk#32346246
                        Intent intent = new Intent();

                        PendingIntent pendingIntent = PendingIntent.getActivity(ScannerAsJobService.this, 1, intent, 0);

                        Notification.Builder builder = new Notification.Builder(ScannerAsJobService.this);

                        builder.setAutoCancel(false);
                        builder.setTicker("my ticker info");
                        builder.setContentTitle("my content title");
                        builder.setContentText(message);
                        builder.setSmallIcon(R.drawable.my_icon);
                        builder.setContentIntent(pendingIntent);
//                        builder.setOngoing(true);   // optionally uncomment this to prevent the user from being able to swipe away the notification
                        builder.setSubText("my subtext");   //API level 16
                        builder.setNumber(100);
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            setupNotificationChannel(NOTIFICATION_CHANNEL_ID);
                            builder.setChannelId(NOTIFICATION_CHANNEL_ID);
                        }
                        builder.build();

                        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                        manager.notify(93423, builder.build());

                        Log.d(TAG + " Notification message: " + message);

                    }
                }, 1 * 1000, 5 * 1000);

                jobFinished(params, true);
            }
        };
        workHandler.post(workRunnable);

        // return true so Android knows the workRunnable is continuing in the background
        return true;
    }

    // https://dev59.com/C1cO5IYBdhLWcg3whiBx#45692202
    @RequiresApi(api = Build.VERSION_CODES.O)
    private void setupNotificationChannel(String channelId) {
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // The user-visible name of the channel.
        CharSequence name = getString(R.string.my_channel_name);

        // The user-visible description of the channel.
        String description = getString(R.string.my_channel_name);

        int importance = NotificationManager.IMPORTANCE_LOW;

        NotificationChannel mChannel = new NotificationChannel(channelId, name, importance);

        // Configure the notification channel.
        mChannel.setDescription(description);

        mChannel.enableLights(true);
        // Sets the notification light color for notifications posted to this
        // channel, if the device supports this feature.
        mChannel.setLightColor(Color.RED);

        mChannel.enableVibration(true);
        mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});

        mNotificationManager.createNotificationChannel(mChannel);
    }

    /**
     * Source: <a href="https://dev59.com/KHA65IYBdhLWcg3wuhIR#39589149">https://dev59.com/KHA65IYBdhLWcg3wuhIR#39589149</a>
     * @return true if the app is in the background, false otherwise
     */
    private boolean appIsInBackground() {
        ActivityManager.RunningAppProcessInfo myProcess = new ActivityManager.RunningAppProcessInfo();
        ActivityManager.getMyMemoryState(myProcess);
        boolean isInBackground = myProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
        return isInBackground;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        // returning true schedules the job for retry (we're using it to implement periodicity since
        // JobInfo.Builder.setPeriodic() didn't work
        return true;
    }
}

3

WorkManager在JobScheduler和AlarmManager之上运行。根据用户设备API级别等条件,WorkManager选择要使用的正确API。

WorkManager是一个简单而非常灵活的库,具有许多其他好处。这些包括:

 -Support for both asynchronous one-off and periodic tasks.
 -Support for constraints such as network conditions, storage space, and charging -status 
 -Chaining of complex work requests, including running work in parallel.
 -Output from one work request used as input for the next.
 -Handles API level compatibility back to API level 14.
 -Works with or without Google Play services.
 -Follows system health best practices.
 -LiveData support to easily display work request state in UI.

0

1
那不是尼古拉斯·尼的问题答案。 - androminor

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