共计 9344 个字符,预计需要花费 24 分钟才能阅读完成。
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 类的各种信息,同样是采纳链式调用
// 我这里继承方才抽象类 BaseJob
public 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()
结尾:所有美妙的事物,终将毁于工夫