基本使用
概述
WorkManager的出现,则是为应用程序中那些不需要及时完成的任务,提供统一的解决方案,以便在设备电量和用户体验之间达到一个比较好的平衡。
特点
-
针对不需要及时完成的任务
比如,发送应用程序日志,同步应用程序数据,备份用户数据等。站在业务的角度,这些任务都不需要立即完成,如果我们自己来管理这些任务,逻辑可能会非常复杂,若API使用不恰当,可能会消耗大量电量。 -
保证任务一定会被执行
WorkManager能保证任务一定会被执行,即使你的应用程序当前不在运行中,哪怕你的设备重启,任务仍然会在适当的时候被执行。这是因为WorkManager有自己的数据库,关于任务的所有信息和数据都保存在这个数据库中,因此,只要你的任务交给了WorkManager,哪怕你的应用程序彻底退出,或者设备重新启动,WorkManager依然能够保证完成你交给的任务。
注意
WorkManager不是一种新的工作线程,它的出现不是为了替代其它类型的工作线程。工作线程通常立即运行,并在执行完成后给到用户反馈。而WorkManager不是即时的,它不能保证任务能立即得到执行。
基础使用
- 在app的build.gradle中添加依赖。
dependencies {
def versions = "2.2.0"
implementation "androidx.work:work-runtime:$versions"
}
- 使用Worker定义任务 。
public class UploadLogWorker extends Worker
{
public UploadLogWorker(@NonNull Context context, @NonNull WorkerParameters workerParams)
{
super(context, workerParams);
}
/**
* 耗时的任务,在doWork()方法中执行
* */
@NonNull
@Override
public Result doWork()
{
Log.e("UploadLogWorker", "doWork()");
return Result.success();
}
}
- doWork()方法有三种类型的返回值
1.执行成功返回Result.success()
2.执行失败返回Result.failure()
3.需要重新执行返回Result.retry()
- 使用WorkRequest配置任务。通过WorkRequest配置我们的任务何时运行以及如何运行。
Constraints constraints = new Constraints.Builder()
.setRequiresCharging(true)
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build();
- 设置任务触发条件。例如,我们可以设置在设备处于充电,网络已连接,且电池电量充足的状态下,才出发我们设置的任务
OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
.setConstraints(constraints)//设置触发条件
.build();
- 设置指数退避策略。假如Worker线程的执行出现了异常,比如服务器宕机,那么你可能希望过一段时间,重试该任务。那么你可以在Worker的doWork()方法中返回Result.retry(),系统会有默认的指数退避策略来帮你重试任务,你也可以通过setBackoffCriteria()方法,自定义指数退避策略
OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class))
.setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS)//设置指数退避算法
.build();
- 将任务提交给系统。WorkManager.enqueue()方法会将你配置好的WorkRequest交给系统来执行
WorkManager.getInstance(this).enqueue(uploadWorkRequest);
- 通过LiveData,我们便可以在任务状态发生变化的时候,收到通知。
WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(MainActivity.this, new Observer<WorkInfo>()
{
@Override
public void onChanged(WorkInfo workInfo)
{
Log.d("onChanged()->", "workInfo:"+workInfo);
}
});
- 任务取消
WorkManager.getInstance(MainActivity.this).cancelAllWork();
- WorkManager和Worker之间的参数传递。数据的传递通过Data对象来完成。
Data inputData = new Data.Builder().putString("input_data", "Hello World!").build();
OneTimeWorkRequest uploadWorkRequest = new OneTimeWorkRequest.Builder(UploadLogWorker.class)
.setInputData(inputData)
.build();
- Worker中接收数据,并在任务执行完成后,向WorkManager传递数据。
@Override
public Result doWork()
{
//接收外面传递进来的数据
String inputData = getInputData().getString("input_data");
// 任务执行完成后返回数据
Data outputData = new Data.Builder().putString("output_data", "Task Success!").build();
return Result.success(outputData);
}
- WorkManager通过LiveData的WorkInfo.getOutputData(),得到从Worker传递过来的数据。
WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadWorkRequest.getId()).observe(MainActivity.this, new Observer<WorkInfo>()
{
@Override
public void onChanged(WorkInfo workInfo)
{
if (workInfo != null && workInfo.getState() == WorkInfo.State.SUCCEEDED)
{
String outputData = workInfo.getOutputData().getString("output_data");
}
}
});
注意:Data只能用于传递一些小的基本类型数据,且数据最大不能超过10kb。
- 任务链。
如果你有一系列的任务需要顺序执行,那么可以利用WorkManager.beginWith().then().then()...enqueue()方法。例如:我们在上传数据之前,需要先对数据进行压缩。
WorkManager.getInstance(this).beginWith(compressWorkRequest).then(uploadWorkRequest).enqueue();
WorkManager的任务执行
WorkManager会根据系统的版本,决定采用JobScheduler或是AlarmManager+Broadcast Receivers来完成任务。但是这些API很可能会受到OEM系统的影响。比如,假设某个系统不允许AlarmManager自动唤起,那么WorkManager很可能就无法正常使用
可能会出现失败情况
-
但是这些API很可能会受到OEM系统的影响
比如,假设某个系统不允许AlarmManager自动唤起,那么WorkManager很可能就无法正常使用 -
实际测试
1.原生系统:基本都执行执行
2.真机(第三方厂商):暂未有全面测试,暂时oppo可以 -
周期性任务不会接收通知
周期任务的实际执行,与所设定的时间差别较大。执行时间看起来并没有太明显的规律。并且在任务执行完成后,WorkInfo并不会收到Success的通知。Android认为Success和Failure都属于终止类的通知。意思是,如果发出这类通知,则表明任务彻底结束,而周期任务不会彻底终止,会一直执行下去,所以我们在使用LiveData观察周期任务时,不会收到Success这类的通知 image.png
WorkManager源码分析
启动点
需要反编译Mainfast.xml文件得到具体
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0"
android:compileSdkVersion="30"
android:compileSdkVersionCodename="11"
package="com.maniu.mn_vip_jetpack_other"
platformBuildVersionCode="30"
platformBuildVersionName="11"
>
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="30"
>
</uses-sdk>
<uses-permission
android:name="android.permission.WAKE_LOCK"
>
</uses-permission>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"
>
</uses-permission>
<uses-permission
android:name="android.permission.RECEIVE_BOOT_COMPLETED"
>
</uses-permission>
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
>
</uses-permission>
<application
android:theme="@7F0D0005"
android:label="@7F0C001B"
android:icon="@7F0B0000"
android:debuggable="true"
android:testOnly="true"
android:allowBackup="true"
android:supportsRtl="true"
android:roundIcon="@7F0B0001"
android:appComponentFactory="androidx.core.app.CoreComponentFactory"
>
<activity
android:name="com.maniu.mn_vip_jetpack_other.MainActivity"
>
<intent-filter
>
<action
android:name="android.intent.action.MAIN"
>
</action>
<category
android:name="android.intent.category.LAUNCHER"
>
</category>
</intent-filter>
</activity>
<activity
android:name="com.maniu.mn_vip_jetpack_other.paging.PagingActivity"
>
</activity>
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:exported="false"
android:multiprocess="true"
android:authorities="com.maniu.mn_vip_jetpack_other.workmanager-init"
android:directBootAware="false"
>
</provider>
<service
android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
android:enabled="@7F030003"
android:exported="false"
android:directBootAware="false"
>
</service>
<service
android:name="androidx.work.impl.background.systemjob.SystemJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="@7F030005"
android:exported="true"
android:directBootAware="false"
>
</service>
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:enabled="@7F030004"
android:exported="false"
android:directBootAware="false"
>
</service>
<receiver
android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
android:enabled="true"
android:exported="false"
android:directBootAware="false"
>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
>
<intent-filter
>
<action
android:name="android.intent.action.ACTION_POWER_CONNECTED"
>
</action>
<action
android:name="android.intent.action.ACTION_POWER_DISCONNECTED"
>
</action>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
>
<intent-filter
>
<action
android:name="android.intent.action.BATTERY_OKAY"
>
</action>
<action
android:name="android.intent.action.BATTERY_LOW"
>
</action>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
>
<intent-filter
>
<action
android:name="android.intent.action.DEVICE_STORAGE_LOW"
>
</action>
<action
android:name="android.intent.action.DEVICE_STORAGE_OK"
>
</action>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
>
<intent-filter
>
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE"
>
</action>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
android:enabled="false"
android:exported="false"
android:directBootAware="false"
>
<intent-filter
>
<action
android:name="android.intent.action.BOOT_COMPLETED"
>
</action>
<action
android:name="android.intent.action.TIME_SET"
>
</action>
<action
android:name="android.intent.action.TIMEZONE_CHANGED"
>
</action>
</intent-filter>
</receiver>
<receiver
android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver"
android:enabled="@7F030003"
android:exported="false"
android:directBootAware="false"
>
<intent-filter
>
<action
android:name="androidx.work.impl.background.systemalarm.UpdateProxies"
>
</action>
</intent-filter>
</receiver>
<service
android:name="androidx.room.MultiInstanceInvalidationService"
android:exported="false"
>
</service>
<provider
android:name="androidx.lifecycle.ProcessLifecycleOwnerInitializer"
android:exported="false"
android:multiprocess="true"
android:authorities="com.maniu.mn_vip_jetpack_other.lifecycle-process"
>
</provider>
</application>
</manifest>
创建过程的调用
-
WorkManagerInitializer.onCreate
image.png -
WorkManager.initialize
image.png - WorkManager.getInstance(this)
public static @NonNull WorkManager getInstance(@NonNull Context context) {
return WorkManagerImpl.getInstance(context);
}
- WorkManagerImpl.getInstance(this)
public static @NonNull WorkManagerImpl getInstance(@NonNull Context context) {
synchronized (sLock) {
WorkManagerImpl instance = getInstance();
if (instance == null) {
...
} 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;
}
}
-
初始化流程图
image.png -
Configuration
image.png
我们回头看一下WorkManagerInitializer#onCreate()中传递的Configuration对象
mExecutor 和mTaskExecutor 都是一个固定数量的线程池。 -
透过装饰模式将线程装饰城线性执行器,并创建一个任务队列
image.png
WorkManagerImpl目的
找到真正执行任务的人
-
根据后台线程池(SerialExecutor)创建数据库
-
创建schedulers
-
创建Processor 对象
image.png -
Schedulers.createBestAvailableBackgroundScheduler会根据设备的sdk版本和设备当前运行状态选择最优的任务执行器SystemJobScheduler或者SystemAlarmScheduler。
image.png
image.png -
internalInit
ForceStopRunnable的run方法内会执行上次app退出后还没有执行的任务。
任务如何加入到队列的enqueue方法
-
WorkContinuationImpl
image.png -
WorkContinuationImpl#enqueue
image.png -
addToDatabase
根据策略不同将任务加入数据库或者删除掉。 image.png
-
PackageManagerHelper.setComponentEnabled
允许将RescheduleReceiver注册到manifest.xml内,我们 可以打开编译后apk的AndroidManifests.xml看到对应的recevier image.png
scheduleWorkInBackground
-
在后台执行任务
image.png -
Schedulers#schedule方法内部会从数据库查询未执行的任务然后根据执行时间排序的所有任务。然后执行任务
image.png
GreedyScheduler怎么执行
image.pngimage.png
image.png
-
StartWorkRunnable会对我们的work包装成WorkerWrapper,WorkerWrapper是Runnable的子类run方法内执行runWorker方法,runWorker方法会从从数据库中查询任务,根据输入的data等生成WorkerParameters实例,然后经过反射创建我们自己创建的Worker实例mWorker ,然后调用mWorker的startWork方法
-
因为回调的结果是要在主线程中返回的,这里使用的主线程Handle.post处理的
image.png -
Worker#startWork
image.png
网友评论