美文网首页
WorkManager原理

WorkManager原理

作者: 沪漂意哥哥 | 来源:发表于2022-05-30 16:16 被阅读0次

基本使用

概述

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
    我们回头看一下WorkManagerInitializer#onCreate()中传递的Configuration对象

    image.png
    mExecutor 和mTaskExecutor 都是一个固定数量的线程池。
  • 透过装饰模式将线程装饰城线性执行器,并创建一个任务队列


    image.png
WorkManagerImpl目的

找到真正执行任务的人

  • 根据后台线程池(SerialExecutor)创建数据库

  • 创建schedulers

  • 创建Processor 对象


    image.png
  • Schedulers.createBestAvailableBackgroundScheduler会根据设备的sdk版本和设备当前运行状态选择最优的任务执行器SystemJobScheduler或者SystemAlarmScheduler。


    image.png
    image.png
  • internalInit
    ForceStopRunnable的run方法内会执行上次app退出后还没有执行的任务。

image.png
任务如何加入到队列的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.png
image.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

相关文章

网友评论

      本文标题:WorkManager原理

      本文链接:https://www.haomeiwen.com/subject/zpvoprtx.html