Android 后台任务队列治理 Android Priority Job Queue和WorkManager
一:前言
有人说“Android的开发,玩的就是多线程”从某个角度来说的确如此,当初的App被设计的越来越简单,置信很多开发人员都因大量而又简单的后台任务(background work)而焦头烂额:Async-Task和Activity的生命周期太过于耦合,尽管实现简略然而对于重要的后台任务还是不靠谱
ndroid-Priority-Job-Queue是一款专门为Android平台编写的,实现了Job Queue的后台任务队列类库,可能轻松的在后盾执行定时工作,并且进步了用户体验和利用的稳定性。
二:Android Priority Job Queue(后盾治理工作队列)
其应用框架也很简便间接:
- 结构一个工作管理器JobManager,为咱们治理工作;
- 自定义Job类,来作为工作的载体;
- 在须要时,将自定义的Job类实例退出到JobManager中;
这样就OK了,JobManager会依据优先级、持久性、负载平衡、提早,网络管制、分组等因素来治理工作的执行。因为是独立于各个Activity,JobManager为Job的执行提供了一个很好的生命周期,
第一步:增加依赖
dependencies { implementation 'com.birbit:android-priority-jobqueue:3.0.0' }
第二步:配置JobManager
JobManager是整个框架的外围。作为一个重型的对象,倡议Application只构建一个JobManager实例供全局应用
public class MyApplication extends Application { private JobManager jobManager;//工作队列的Job治理 private static MyApplication instance; @Override public void onCreate() { super.onCreate(); instance = this;//1. Application的实例 configureJobManager();//2. 配置JobMananger } //公有结构器 private MyApplication (){ instance=this; } public JobManager getJobManager() { return jobManager; } public static MyApplication getInstance() { return instance; } private void configureJobManager() { //3. JobManager的配置器,利用Builder模式 Configuration configuration = new Configuration.Builder(this) .customLogger(new CustomLogger() { private static final String TAG = "JOBS"; @Override public boolean isDebugEnabled() { return true; } @Override public void d(String text, Object... args) { Log.d(TAG, String.format(text, args)); } @Override public void e(Throwable t, String text, Object... args) { Log.e(TAG, String.format(text, args), t); } @Override public void e(String text, Object... args) { Log.e(TAG, String.format(text, args)); } }) .minConsumerCount(1)//起码的沉闷线程(这里配置是1) .maxConsumerCount(3)//最多的开启线程(这里配置是3) .loadFactor(3)// 一个Thread设置多3个工作(也能够Integer.MAX_VALUE,int最大值) .consumerKeepAlive(120)// 设置线程在没有工作的状况下放弃存活的时长,以秒为单位 .build(); jobManager = new JobManager(configuration); }}
- CustomLogger:日志设置,便于用户查看工作队列的工作信息,在调试的过程中很有用,前面剖析JobManager的任务调度时就会用到;
- minConsumerCount&maxConsumerCount: 起码消费者和最多消费者数量,所谓的消费者就是开启的线程,用来执行工作。工作队列实际上就是一个生产者和消费者问题,用户是生产者,提交工作(Job),开启的线程就是消费者来执行工作,工作被执行就是“生产”。这里所谓的起码和最大将会上面具体解释;
- loadFactor(int): 其意义是设置多少个工作为一组被调配个一个消费者(Thread),也就是一个Thread最多要“承包”几个工作来执行;
- consumerKeepAlive :设置消费者在没有工作的状况下放弃存活的时长,以秒为单位,如果过了这个时长还没有工作,消费者线程就会被回收
第三步:Job
自定义Job类须要继承Android-Priority-Job-Queue提供的Job类
public abstract class BaseJob extends Job {protected BaseJob(Params params) { super(params); } //工作退出队列并被保留在硬盘上,定义此时要解决的逻辑 @Override public void onAdded() { LogUtils.i("onAdded: " + getClass().getSimpleName()); }//工作开始执执行,在此定义工作的主题逻辑,当执行结束后,工作将被从工作队列中删除 @Override public void onRun() throws Throwable { } //工作勾销的时候要执行的逻辑 @Override protected void onCancel(int cancelReason, @Nullable Throwable throwable) { //调用开释 release(); } //当onRun()办法中抛出异样时,就会调用该函数,该函数返回Job类在执行产生异样时的应答策略,是从新执行还是勾销,或者是肯定工夫之后再尝试。 @Override protected RetryConstraint shouldReRunOnThrowable( @NonNull Throwable throwable, int runCount, int maxRunCount) { } /** * 开始做工作 */ public abstract void doJob() throws Throwable; /** * 重写这个办法进行资源回收 */ protected void release() { }}
第四步:Params类
在这里咱们要特地说一下Params类,通过该类能够配置Job类的各种信息,同样是采纳链式调用
//我这里继承方才抽象类BaseJobpublic class StopAppJob extends BaseJob{ public StopAppJob(String pkgName, boolean isFirst) { //这里咱们次要看一下父类调用,这里咱们结构的Params类,new Params(7):是默认结构器传入工作优先级7;groupBy():设置组ID;delayInMs()::设置延迟时间,ms为单位 super(new Params(7).addTags(TAG).groupBy(SCRIPT_JOB).delayInMs(ONE_SECOND)); this.pkgName = pkgName; this.isFirst = isFirst; } @Override public void onRun() throws Throwable { super.onRun(); doJob(); } @Override public void doJob() throws Throwable { //doSomething }}
1.默认结构器传入的是int参数是该工作的优先级,优先级越高,越优先执行。
public Params(int priority) { this.priority = priority; }
2.requireNetwork(): 设置该工作要求拜访网络
3.groupBy(String groupId):设置组ID,被设置雷同组ID的工作,将会依照程序执行
4.persist():设置工作为可长久化的,长久化要求Job类为序列化的,这一点并不意外,因为一个类的内容只有序列化之后能力变成字节模式保留在硬盘上
5.delayInMs(long delayMs):设置延迟时间,ms为单位,在该工夫之后再放入工作队列中。
第五步:执行Job工作
MyApplication.getJobManager() .addJobInBackground( new StopAppJob(PackageInfoConfig.TAOBAO_PACKAGE_NAME, true));
这个库的github:https://github.com/yigit/andr...
然而:这个我的项目曾经不保护了:它是在一个没有 JobScheduler、RxJava 不风行、Kotlin 甚至没有公开诞生的世界中设计的。基于Java语言的
当初大部分应用的是WorkManager,故咱们须要学习一下WorkManager
三:WorkManager应用
WorkManager是Android Jetpack的一部分,是用于后盾工作的架构组件,须要兼顾机会和有保障的执行。机会性执行意味着WorkManager将尽快实现您的后盾工作。有保障的执行意味着即便在来到应用程序的状况下,WorkManager也会兼顾各种状况下开始逻辑工作。
WorkManager是一个简略但非常灵活的库,它具备许多其余长处:
- 反对异步一次性和定期工作
- 反对网络条件,存储空间和充电状态等束缚
- 链接简单的工作申请,包含并行运行工作
- 一个工作申请的输入用作下一个工作的输出
- 将API级别的兼容性解决回API级别14(请参阅正文)
- 能够应用或不应用Google Play服务
- 遵循零碎衰弱最佳实际
- LiveData反对可轻松在UI中显示工作申请状态
WorkManager几个要害的类:
Worker:工作的执行类,是一个抽象类须要集成它来实现要执行的工作
WorkRequest:指定哪个Worker执行工作,能够向WorkRequest中增加细节,指定执行环境,执行程序,ID等。WorkRequest是个抽象类,在代码中应用其子类OneTimeWorkRequest或者PeriodicWorkRequest
WorkManager:对WorkRequest进行排队和治理
WorkStatus:蕴含工作的状态和信息,以LiveData的模式提供给观察者
1.第一步增加依赖
Java
// Java implementation "androidx.work:work-runtime:2.5.0"
Kotlin
// kotlin workmanager implementation "androidx.work:work-runtime-ktx:2.5.0"
2.第二步:应用Worker类定义工作
public class SendLogsWorker extends Worker { public SendLogsWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { //耗时工作执行在doWork()中执行 Log.d("aa","日志"); return Result.success(); }}
源码:
public abstract class Worker extends ListenableWorker { // Package-private to avoid synthetic accessor. SettableFuture<Result> mFuture; @Keep @SuppressLint("BanKeepAnnotation") public Worker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } 。。。。。}
doWork()有3种返回类型
执行胜利 Result.success()
执行失败 Result.failure()
从新执行 Result.retry()
3.第三步:应用WorkRequest分配任务
WorkRequest是一个抽象类,它的两种实现形式:OneTimeWorkRequest和PeriodicWorkRequest别离对应的是一次性工作和周期性工作
OneTimeWorkRequesty一次申请工作
public class TwelveActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_twelve); //触发条件 Constraints constraints=new Constraints.Builder() .setRequiresCharging(true)//设置充电状态,默认是flase .setRequiredNetworkType(NetworkType.UNMETERED)//Wifi .setRequiresStorageNotLow(true)//内存低,不工作 .setRequiresBatteryNotLow(true)//电量低,不工作 .build(); //一次性工作 OneTimeWorkRequest oneTimeWorkRequest=new OneTimeWorkRequest.Builder(SendLogsWorker.class) .setConstraints(constraints) .setInitialDelay(10,TimeUnit.SECONDS)//设置提早执行10s .build(); WorkManager.getInstance(this).enqueueUniqueWork("sendLogs", ExistingWorkPolicy.KEEP,oneTimeWorkRequest);}
PeriodicWorkRequest反复工作申请
PeriodicWorkRequest sendLogsWorkRequest=new PeriodicWorkRequest.Builder(SendLogsWorker.class,5, TimeUnit.SECONDS)//定义5s周期,是没有成果的 //PeriodicWorkRequest应用和OneTimeWorkRequest没有太大区别,须要留神的是,间隔时间不能少于 15 分钟. .setConstraints(new Constraints.Builder().setRequiresCharging(true).build()) .build(); //后果日志,能够看出相差15分钟2021-07-06 14:07:04.265 25585-25647/com.ruan.mygitignore D/aa: 日志2021-07-06 14:22:04.398 25585-26000/com.ruan.mygitignore D/aa: 日志2021-07-06 14:37:04.565 25585-26356/com.ruan.mygitignore D/aa: 日志
触发条件:
- setRequiredNetworkType(NetworkType.UNMETERED) //设置须要的网络条件Wifi(UNMETERED (Wifi),CONNECTED(任何网络),NOT_REQUIRED(没有要求),NOT_ROAMING(连贯非漫游网络),METERED(连贯按流量计费的网络))
- setRequiresStorageNotLow(true)//如果设置为 true,那么当用户设施上的存储空间有余时,工作不会运行。
- setRequiresBatteryNotLow(true)//如果设置为 true,那么当用户设施上的电量有余时,工作不会运行。
WorkRequest
new OneTimeWorkRequest.Builder(SendLogsWorker.class) .setConstraints(constraints) .setBackoffCriteria(BackoffPolicy.LINEAR,OneTimeWorkRequest.MIN_BACKOFF_MILLIS,TimeUnit.MICROSECONDS)//10s重试和退却政策 .setInitialDelay(10,TimeUnit.SECONDS)//提早执行 .addTag("log")//增加Tag标记 .build();
- setInitialDelay(10,TimeUnit.SECONDS)//如果工作没有束缚,或者当工作退出队列时所有束缚都失去了满足,那么零碎可能会抉择立刻运行该工作。如果您不心愿工作立刻运行,能够将工作指定为在通过一段最短初始延迟时间后再启动
- setBackoffCriteria(BackoffPolicy.LINEAR,OneTimeWorkRequest.MIN_BACKOFF_MILLIS,TimeUnit.MICROSECONDS)//10s重试和退却政策,最短退却延迟时间设置为容许的最小值,即 10 秒。因为政策为 LINEAR,每次尝试重试时,重试距离都会减少约 10 秒。例如,第一次运行以 Result.retry() 完结并在 10 秒后重试;而后,如果工作在后续尝试后持续返回 Result.retry(),那么接下来会在 20 秒、30 秒、40 秒后重试,以此类推。如果退却政策设置为 EXPONENTIAL,那么重试时长序列将靠近 20、40、80 秒
- addTag("log")//每个工作申请都有一个惟一标识符,该标识符可用于在当前标识该工作,以便勾销工作或察看其进度,例如,WorkManager.cancelAllWorkByTag(String) 会勾销带有特定标记的所有工作申请,WorkManager.getWorkInfosByTag(String) 会返回一个 WorkInfo 对象列表,该列表可用于确定当前工作状态。
工作状态
一次性工作的状态
定期工作的状态
调度工作
//一次性工作 WorkManager.getInstance(this).enqueueUniqueWork("sendLogs", ExistingWorkPolicy.REPLACE,oneTimeWorkRequest); //反复工作 WorkManager.getInstance(this).enqueueUniquePeriodicWork("sendLogs", ExistingPeriodicWorkPolicy.KEEP,sendLogsWorkRequest);
WorkManager.enqueueUniqueWork()(用于一次性工作)
WorkManager.enqueueUniquePeriodicWork()(用于定期工作)
这两种办法都承受 3 个参数:
- uniqueWorkName - 用于惟一标识工作申请的 String。
- existingWorkPolicy - 此 enum 可告知 WorkManager:如果已有应用该名称且尚未实现的惟一工作链,应执行什么操作。如需理解详情,请参阅抵触解决政策。
- work - 要调度的 WorkRequest。
抵触解决政策
调度惟一工作时,您必须告知 WorkManager 在发生冲突时要执行的操作。您能够通过在将工作退出队列时传递一个枚举来实现此目标。
对于一次性工作,您须要提供一个 ExistingWorkPolicy,它反对用于解决抵触的 4 个选项。
- REPLACE:用新工作替换现有工作。此选项将勾销现有工作。
- KEEP:保留现有工作,并疏忽新工作。
- APPEND:将新工作附加到现有工作的开端。此政策将导致您的新工作链接到现有工作,在现有工作实现后运行。
- 现有工作将成为新工作的先决条件。如果现有工作变为 CANCELLED 或 FAILED 状态,新工作也会变为 CANCELLED 或 FAILED。如果您心愿无论现有工作的状态如何都运行新工作,请改用 APPEND_OR_REPLACE。
- APPEND_OR_REPLACE 函数相似于 APPEND,不过它并不依赖于先决条件工作状态。即便现有工作变为 CANCELLED 或 FAILED 状态,新工作仍会运行。
对于定期工作,您须要提供一个 ExistingPeriodicWorkPolicy,它反对 REPLACE 和 KEEP 这两个选项。这些选项的性能与其对应的 ExistingWorkPolicy 性能雷同。
工作链
WorkManager 容许咱们依照肯定的程序执行工作,比方我想 A、B、C 三个工作按先后顺序执行:
WorkManager.getInstance() .beginWith(workA) .then(workB) .then(workC) .enqueue();
再更简单一点,我想 A 和 B 同时执行,它们都执行完之后,再执行 C:
WorkManager.getInstance() .beginWith(workA,workB) .then(workC) .enqueue();
这样就须要先把 A、B 和 C、D 别离组成一条工作链,再进行联结:
//这里Kolin语言实现val chain1 = WorkManager.getInstance() .beginWith(workA) .then(workB)val chain2 = WorkManager.getInstance() .beginWith(workC) .then(workD)val chain3 = WorkContinuation .combine(chain1, chain2) .then(workE)chain3.enqueue()
结尾:所有美妙的事物,终将毁于工夫