最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面。
Android Architecture组件是Android Jetpack的一部分,它们是一组库,旨在帮助开发者设计健壮、可测试和可维护的应用程序,包含一下组件:
本系列文章是各处copy过来的,个人感觉所有的开发者都应该尽早的熟悉Jetpack组件,相信一定会被它的魅力所吸引,最近也在完成一个使用以上所有组件实现的项目,作为对Jetpack组件的项目实践,下面来分析一下每个组件对项目开发的帮助。
WorkManger是Android Jetpack提供执行后台任务管理的组件,它适用于需要保证系统即使应用程序退出也会运行的任务,WorkManager API可以轻松指定可延迟的异步任务以及何时运行它们,这些API允许您创建任务并将其交给WorkManager立即运行或在适当的时间运行。
WorkManager根据设备API级别和应用程序状态等因素选择适当的方式来运行任务。如果WorkManager在应用程序运行时执行您的任务之一,WorkManager可以在您应用程序进程的新线程中运行您的任务。如果您的应用程序未运行,WorkManager会选择一种合适的方式来安排后台任务 - 具体取决于设备API级别和包含的依赖项,WorkManager可能会使用 JobScheduler,Firebase JobDispatcher或AlarmManager
为什么在有了service以后,google还有出WorkManager框架呢?
3.1、使用WorkManager之前需先了解几个类:
3.2、工作流程
添加WorkManger的工作依赖
def work_version = "1.0.0-alpha10"
implementation "android.arch.work:work-runtime:$work_version" // use -ktx for Kotlin
androidTestImplementation "android.arch.work:work-testing:$work_version"
定义Worker类,并覆盖其 doWork()方法
class TestWorker(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {
override fun doWork(): Result {
Log.e("TestWorker", "执行了 doWork() 操作!")
return Result.SUCCESS
}
}
使用 OneTimeWorkRequest.Builder 创建对象Worker,然后将任务排入WorkManager队列
val workRequest = OneTimeWorkRequest.Builder(TestWorker::class.java).build()
WorkManager.getInstance().enqueue(workRequest)
执行结果:
WorkStatus
WorkManager.getInstance().getStatusByIdLiveData(workRequest.id) // 返回LiveData
.observe(this, Observer {
Log.e("TestWorker", it?.state?.name)
if (it?.state!!.isFinished) {
Log.e("TestWorker", "Finish")
}
})
从上面执行过程中看出,回调了Work的三个运行状态RUNNING、SUCCESSESD、FINISH。
任务约束
// 在连接网络、插入电源且设备处于空闲时运行
val myConstraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true)
.build()
除了上面设置的约束外,WorkManger还提供了以下的约束作为Work执行的条件:
配置好Constraints后,创建Work对象
val compressionWork = OneTimeWorkRequest.Builder(TestWorker::class.java)
.setConstraints(myConstraints)
.build()
执行结果:在为连接网络时,Work处于阻塞状态当连接网络后Work立即执行变为SUCCESSED状态:
取消任务
WorkManager.getInstance().cancelWorkById(workRequest.id)
添加TAG
OneTimeWorkRequestBuilder()
.addTag("cleanup")
.build()
/./ 使用:
WorkManager.getInstance().getStatusesByTag("TAG")
WorkManager.getInstance().cancelAllWorkByTag("TAG")
重复的任务
val timesRequest = PeriodicWorkRequest.Builder(TestWorker::class.java,10,TimeUnit.SECONDS)
......
.build()
链式任务
Example:
WorkManager.getInstance()
.beginWith(workA)
.then(workB)
.then(workC)
.enqueue()
执行结果:任务会按照设置的顺序依次执行A、B、C
有一点就是WorkManger在执行过程中,当遇到一个WOrk不成功,则会停止执行,现修改WorkB返回FAILURE状态,再次运行程序结果如下:
override fun doWork(): Result {
Log.e("Worker", "WorkerB 执行了 doWork() 操作!")
return Result.FAILURE
}
代码执行到WorkB就已经结束了,WorkC并未执行。
WorkManager.getInstance()
.beginWith(workA1, workA2, workA3) // 三个对象将并行
.then(workB) // 执行完3个A后,在执行B
.then(workC1, workC2) //...
.enqueue()
WorkContinuation.combine()
// ......继续创建WorkD 和 WorkE 及相应的Request
val configA_B = WorkManager.getInstance().beginWith(workRequest)
.then(workRequestB)
val configC_D = WorkManager.getInstance().beginWith(workRequestC)
.then(workRequestD)
WorkContinuation.combine(configA_B,configC_D)
.then(workRequestE)
.enqueue()
执行结果与上面一致:开始时执行A/C,其余为阻塞状态,A/C执行结束后执行B/D,最后执行WoekerE。如果此时把WorkB中返回FAILURE,执行结果也一致执行到WorkerB就结束了,WorkerE不会执行。
特定的工作方式
WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequest)
//参数:1、工作序列的名称
2、当有相同名称序列时采取的策略方式
3、需要执行的Worker
每个独特的工作序列都有一个名称; 同一时间只允许执行一个使用该名称工作序列
ExistingWorkPolicy提供以下策略:
以ExistingWorkPolicy.REPLACE为例,让WorkA睡眠3秒,模拟同时运行的状态:
override fun doWork(): Result {
Log.e("Worker", "WorkerA begin Sleep()!")
Thread.sleep(3000)
Log.e("Worker", "WorkerA 执行了 doWork() 操作!")
return Result.SUCCESS
}
Append:两个都会执行
WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequest)
.enqueue()
WorkManager.getInstance().beginUniqueWork("worker", ExistingWorkPolicy.APPEND, workRequestB)
.enqueue()
运行结果:
REPLACE:只会执行WorkerB
KEEP:只会执行WorkerA
输入参数和返回值
修改WorkerA如下
const val MIN_NUMBER = "minNumber"
const val MAX_NUMBER = "maxNumber"
const val RESULT_CODE = "Result"
class WorkerA(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {
private var minNumber = 0
private var maxNumber = 0
override fun doWork(): Result {
minNumber = inputData.getInt(MIN_NUMBER, 0) // 使用InputData获取传入的参数
maxNumber = inputData.getInt(MAX_NUMBER, 0)
val result = maxNumber - minNumber // 计算结果
val outData: Data = Data.Builder().putAll(mapOf(RESULT_CODE to result)).build() // 创建返回的数据Data
outputData = outData // 设置返回的数据Data
return Result.SUCCESS
}
}
创建Worker并传递参数
val map = mapOf(MIN_NUMBER to 5, MAX_NUMBER to 15)
val data = Data.Builder().putAll(map).build() // 创建输入参数Data
val mathWork = OneTimeWorkRequestBuilder()
.setInputData(data) // 传递参数
.build()
观察任务WorkStatus获取返回结果
WorkManager.getInstance().getStatusByIdLiveData(workRequest.id)
.observe(this, Observer {
if (it?.state!!.isFinished) {
Log.e("WorkerA", "${it.outputData.getInt(RESULT_CODE, 0)}") // 获取执行结果
}
})
链式调用
const val NUMBER = "NUMBER"
const val RESULT_B = "RESULT_B"
class WorkerB(context: Context,workerParameters: WorkerParameters) : Worker(context,workerParameters){
override fun doWork(): Result {
val input = inputData.getInt(RESULT_CODE,5) // 获取第一个Worker返回的结果
Log.e("Worker", "WorkerB 接受数据 input = $input ")
val map = mapOf(RESULT_B to input*input)
outputData = Data.Builder().putAll(map).build() // 设置返回数据
return Result.SUCCESS
}
}
// Activity中监听WorkerB的执行结果
WorkManager.getInstance().getStatusByIdLiveData(workRequestB.id)
.observe(this, Observer {
if (it?.state!!.isFinished) {
Log.e("WorkerB", "${it.outputData.getInt(RESULT_B, 0)}")
}
})
执行结果:对于WorkA依次传入5和15,在WorkA中计算差值15-5 = 10,所以WorkA中输出10,那么链式调用的WorkB中接收的就是10,加平方后输出为100,输出Log与分析一致,
到此Android Jetpack组件的workmanager就介绍完了。
5.1 WorkManager类
通过上面我们知道任务的执行,是通过**WorkManager.getInstance(this).enqueue(request)**执行的。WorkManager是个抽象类,通过WorkManager.getInstance方法返回的是,它的子类WorkManagerImpl的单例对象。
public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
synchronized (sLock) {
WorkManagerImpl instance = getInstance();
if (instance == null) {
Context appContext = context.getApplicationContext();
if (appContext instanceof Configuration.Provider) {
initialize(
appContext,
((Configuration.Provider) appContext).getWorkManagerConfiguration());
instance = getInstance(appContext);
} else {
throw new IllegalStateException("WorkManager is not initialized properly. You "
+ "have explicitly disabled WorkManagerInitializer in your manifest, "
+ "have not manually called WorkManager#initialize at this point, and "
+ "your Application does not implement Configuration.Provider.");
}
}
return instance;
}
}
public static @Nullable WorkManagerImpl getInstance() {
synchronized (sLock) {
if (sDelegatedInstance != null) {
return sDelegatedInstance;
}
return sDefaultInstance;
}
}
通过反编译我们的APP,我们在AndroidManifest.xml文件中找到了一个provider的配置项。WorkManagerInitializer类又继承自ContentProvider,关于ContentProvider的启动过程这里不过多介绍,在应用的启动时候,会通过ActivityThread初始化ContentProvider(WorkManagerInitializer),即执行了onCreate方法。
在WorkManagerInitializer的onCreate方法中调用了WorkManager.initialize的方法进行初始化。
public boolean onCreate() {
// Initialize WorkManager with the default configuration.
WorkManager.initialize(getContext(), new Configuration.Builder().build());
return true;
}
在WorkManager.initialize内部通过调用 WorkManagerImpl.initialize(context, configuration)完成WorkManagerImpl的初始化任务。
下面重点看一下WorkManagerImpl.initializ内部做了那些初始化操作。
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
synchronized (sLock) {
if (sDelegatedInstance != null && sDefaultInstance != null) {
throw new IllegalStateException("WorkManager is already initialized. Did you "
+ "try to initialize it manually without disabling "
+ "WorkManagerInitializer? See "
+ "WorkManager#initialize(Context, Configuration) or the class level"
+ "Javadoc for more information.");
}
if (sDelegatedInstance == null) {
context = context.getApplicationContext();
if (sDefaultInstance == null) {
sDefaultInstance = new WorkManagerImpl(
context,
configuration,
new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
}
sDelegatedInstance = sDefaultInstance;
}
}
}
private @NonNull Executor createDefaultExecutor() {
return Executors.newFixedThreadPool(
// This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR.
Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)));
}
initialize方法内部通过调用WorkManagerImpl的构造方法完成初始化任务。
public WorkManagerImpl(
@NonNull Context context,
@NonNull Configuration configuration,
@NonNull TaskExecutor workTaskExecutor,
boolean useTestDatabase) {
Context applicationContext = context.getApplicationContext();
WorkDatabase database = WorkDatabase.create(
applicationContext, configuration.getTaskExecutor(), useTestDatabase);
Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
List schedulers = createSchedulers(applicationContext, workTaskExecutor);
Processor processor = new Processor(
context,
configuration,
workTaskExecutor,
database,
schedulers);
internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}
createSchedulers主要是为了创建调度者集合。
public @NonNull List createSchedulers(Context context, TaskExecutor taskExecutor) {
return Arrays.asList(
Schedulers.createBestAvailableBackgroundScheduler(context, this),
// Specify the task executor directly here as this happens before internalInit.
// GreedyScheduler creates ConstraintTrackers and controllers eagerly.
new GreedyScheduler(context, taskExecutor, this));
}
createBestAvailableBackgroundScheduler方法
static Scheduler createBestAvailableBackgroundScheduler(
@NonNull Context context,
@NonNull WorkManagerImpl workManager) {
Scheduler scheduler;
if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
scheduler = new SystemJobScheduler(context, workManager);
setComponentEnabled(context, SystemJobService.class, true);
Logger.get().debug(TAG, "Created SystemJobScheduler and enabled SystemJobService");
} else {
scheduler = tryCreateGcmBasedScheduler(context);
if (scheduler == null) {
scheduler = new SystemAlarmScheduler(context);
setComponentEnabled(context, SystemAlarmService.class, true);
Logger.get().debug(TAG, "Created SystemAlarmScheduler");
}
}
return scheduler;
}
5.2 任务的入执行队列enqueue方法
通过以上分析我们知道WorkManager.getInstance返回的是WorkManagerImpl实例,所以我们进入到enqueue方法中看看。调用WorkContinuationImpl实例的enqueue方法。
public Operation enqueue(
@NonNull List workRequests) {
// This error is not being propagated as part of the Operation, as we want the
// app to crash during development. Having no workRequests is always a developer error.
if (workRequests.isEmpty()) {
throw new IllegalArgumentException(
"enqueue needs at least one WorkRequest.");
}
return new WorkContinuationImpl(this, workRequests).enqueue();
}
直接进入WorkContinuationImpl.enqueue方法看看。
public @NonNull Operation enqueue() {
// Only enqueue if not already enqueued.
if (!mEnqueued) {
// The runnable walks the hierarchy of the continuations
// and marks them enqueued using the markEnqueued() method, parent first.
EnqueueRunnable runnable = new EnqueueRunnable(this);
mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
mOperation = runnable.getOperation();
} else {
Logger.get().warning(TAG,
String.format("Already enqueued work ids (%s)", TextUtils.join(", ", mIds)));
}
return mOperation;
}
EnqueueRunnable类
在scheduleWorkInBackground方法中调度任务执行,内部调用Schedulers类的schedule方法,分配任务。
public void run() {
try {
if (mWorkContinuation.hasCycles()) {
throw new IllegalStateException(
String.format("WorkContinuation has cycles (%s)", mWorkContinuation));
}
boolean needsScheduling = addToDatabase();
if (needsScheduling) {
// Enable RescheduleReceiver, only when there are Worker's that need scheduling.
final Context context =
mWorkContinuation.getWorkManagerImpl().getApplicationContext();
PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
scheduleWorkInBackground();
}
mOperation.setState(Operation.SUCCESS);
} catch (Throwable exception) {
mOperation.setState(new Operation.State.FAILURE(exception));
}
}
public void scheduleWorkInBackground() {
WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
Schedulers.schedule(
workManager.getConfiguration(),
workManager.getWorkDatabase(),
workManager.getSchedulers());
}
Schedulers类。
在调用Scheduler的schedule的调配任务。在分析WorkManager初始化的时候,我们知道主要有GreedyScheduler等调度类。下面重点分析一下该类。
public static void schedule(
@NonNull Configuration configuration,
@NonNull WorkDatabase workDatabase,
List schedulers) {
if (schedulers == null || schedulers.size() == 0) {
return;
}
WorkSpecDao workSpecDao = workDatabase.workSpecDao();
List eligibleWorkSpecs;
if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
// Delegate to the underlying scheduler.
for (Scheduler scheduler : schedulers) {
scheduler.schedule(eligibleWorkSpecsArray);
}
}
}
GreedyScheduler类
判断何时有约束条件。没有则调用startWork执行任务。有则将任务入集合
public void schedule(@NonNull WorkSpec... workSpecs) {
registerExecutionListenerIfNeeded();
// Keep track of the list of new WorkSpecs whose constraints need to be tracked.
// Add them to the known list of constrained WorkSpecs and call replace() on
// WorkConstraintsTracker. That way we only need to synchronize on the part where we
// are updating mConstrainedWorkSpecs.
List constrainedWorkSpecs = new ArrayList<>();
List constrainedWorkSpecIds = new ArrayList<>();
for (WorkSpec workSpec: workSpecs) {
if (workSpec.state == WorkInfo.State.ENQUEUED
&& !workSpec.isPeriodic()
&& workSpec.initialDelay == 0L
&& !workSpec.isBackedOff()) {
if (workSpec.hasConstraints()) {
// Exclude content URI triggers - we don't know how to handle them here so the
// background scheduler should take care of them.
if (Build.VERSION.SDK_INT < 24
|| !workSpec.constraints.hasContentUriTriggers()) {
constrainedWorkSpecs.add(workSpec);
constrainedWorkSpecIds.add(workSpec.id);
}
} else {
Logger.get().debug(TAG, String.format("Starting work for %s", workSpec.id));
mWorkManagerImpl.startWork(workSpec.id);
}
}
}
// onExecuted() which is called on the main thread also modifies the list of mConstrained
// WorkSpecs. Therefore we need to lock here.
synchronized (mLock) {
if (!constrainedWorkSpecs.isEmpty()) {
Logger.get().debug(TAG, String.format("Starting tracking for [%s]",
TextUtils.join(",", constrainedWorkSpecIds)));
mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
}
}
}
WorkManagerImpl.startWork方法
还是调用WorkManagerTaskExecutor的executeOnBackgroundThread方法执行StartWorkRunnable。
public void startWork(String workSpecId, WorkerParameters.RuntimeExtras runtimeExtras) {
mWorkTaskExecutor
.executeOnBackgroundThread(
new StartWorkRunnable(this, workSpecId, runtimeExtras));
}
StartWorkRunnable类
getProcessor方法的是我们在创建WorkManager时创建的Processor对象。这里会调用Processor的startWork方法。
public void run() {
mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
}
Processor类
内部通过构建Work的包装类WorkerWrapper,然后再次调用WorkManagerTaskExecutor类执行WorkerWrapper任务。
public boolean startWork(String id, WorkerParameters.RuntimeExtras runtimeExtras) {
WorkerWrapper workWrapper;
synchronized (mLock) {
// Work may get triggered multiple times if they have passing constraints
// and new work with those constraints are added.
if (mEnqueuedWorkMap.containsKey(id)) {
Logger.get().debug(
TAG,
String.format("Work %s is already enqueued for processing", id));
return false;
}
workWrapper =
new WorkerWrapper.Builder(
mAppContext,
mConfiguration,
mWorkTaskExecutor,
mWorkDatabase,
id)
.withSchedulers(mSchedulers)
.withRuntimeExtras(runtimeExtras)
.build();
ListenableFuture future = workWrapper.getFuture();
future.addListener(
new FutureListener(this, id, future),
mWorkTaskExecutor.getMainThreadExecutor());
mEnqueuedWorkMap.put(id, workWrapper);
}
mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
Logger.get().debug(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
return true;
}
WorkerWrapper类
下面以贴一张执行的类图信息。
任务约束Constraints的任务是如何被触发的呢?
就是通过监听各种约束条件变化的广播,然后经过层层转化,最终的处理逻辑和无限制条件的work流程一致。
参考
Android Jetpack之WorkManager源码分析
Android Jetpack架构组件之WorkManger