如何应对 Android 面试官 -> 玩转 Jetpack WorkManager

前言


本章进行 WorkManager 的学习;

是什么?


WorkManger 是 Android Jetpack 提供执行后台任务管理的组件,它适用于需要保证系统即使应用程序退出也会运行的任务,WorkManager API 可以轻松指定可延迟的异步任务以及何时运行它们,这些 API 允许您创建任务并将其交给 WorkManager 立即运行或在适当的时间运行;

WorkManager 根据设备 API 级别和应用程序状态等因素选择适当的方式来运行任务。如果WorkManager在应用程序运行时执行您的任务之一,WorkManager 可以在您应用程序进程的新线程中运行您的任务。如果您的应用程序未运行, WorkManager 会选择一种合适的方式来安排后台任务-具体取决于设备 API 级别和包含的依赖项,WorkManager 可能会使用 JobScheduler, Firebase JobDispatcher 或 AlarmManager;

任务的管理,一定会被执行,即使进程杀掉,手机重启,主要是针对的是非及时任务;

有什么用?


  • 确保重要的后台任务,一定会被执行,后台任务(例如:非及时性的(请求服务器及时性)上传,下载,同步数据等);
  • 内部对电量进行了优化,不需要我们去处理电量优化了;
  • API14 到最新版本,都可以使用 WorkManager 来管理你的后台任务;
  • 注意:WorkManager 不能做保活操作;
  • 调度,管理,执行的后台任务的场景,通常是是可延迟的后台任务;

使用篇


依赖

bash 复制代码
def work_version = "xxx"
implementation "androidx.work:work-runtime:$work_version"

基础调用

使用起来还是不算复杂,我们实现一个类,继承 Worker 即可;

kotlin 复制代码
// 最简单的 执行任务
class MainWorker1(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {

    companion object { const val TAG = "Mars" }

    // 后台任务并且异步的 (原理:线程池执行Runnable)
    override fun doWork(): Result {
        Log.d(TAG, "MainWorker1 doWork: run started ... ")

        try {
            Thread.sleep(8000) // 睡眠
        } catch (e: InterruptedException) {
            e.printStackTrace()
            Result.failure(); // 本次任务失败
        } finally {
            Log.d(TAG, "MainWorker1 doWork: run end ... ")
        }

        return Result.success(); // 本次任务成功
    }
}

然后 MainActivity 中使用这个 MainWorker1

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
    
    /**
     * 最简单的 执行任务
     * 测试后台任务 1
     *
     * @param view
     */
    fun testBackgroundWork1(view: View) {
        // OneTimeWorkRequest  单个一次的任务
        val oneTimeWorkRequest = OneTimeWorkRequest.Builder(MainWorker1::class.java).build()
        WorkManager.getInstance(this).enqueue(oneTimeWorkRequest)
    }
}

这样我们就完成了最基础的使用;

数据交互

完成 Activity 和 WorkerManager 的数据传递;我们先来定一个 MainWorker2

kotlin 复制代码
/**
 * 数据互相传递
 * 后台任务
 */
class MainWorker2(context: Context, private val workerParams: WorkerParameters) : Worker(context, workerParams) {

    companion object { const val TAG = "Mars" }

    // 后台任务并且异步的 (原理:线程池执行Runnable)
    @SuppressLint("RestrictedApi")
    override fun doWork(): Result { 
        // 开始执行了 ENQUEUED
        Log.d(TAG, "MainWorker2 doWork: 后台任务执行了")
        
        // 接收 MainActivity 传递过来的数据,通过 InputData
        val dataString = workerParams.inputData.getString("NBA")
        Log.d(TAG, "MainWorker2 doWork: 接收 MainActivity 传递过来的数据: $dataString")
        return new Result.Success(); // 本地执行 doWork 任务时 成功 执行任务完毕
        
    }
}

定义 MainWorker2 通过 InputData 来接收 Activity 传递过来的数据,接下来我们来看下 Activity 中发送数据,通过 Data 进行发送数据的封装

kotlin 复制代码
/**
 * 数据 互相传递
 * 测试后台任务 2
 *
 * @param view
 */
fun testBackgroundWork2(view: View?) {
    // 单一的任务  一次
    val oneTimeWorkRequest1: OneTimeWorkRequest
    // 数据
    val sendData = Data.Builder().putString("NBA", "Kobe").build()
    // 请求对象初始化
    oneTimeWorkRequest1 = OneTimeWorkRequest.Builder(MainWorker2::class.java)
            .setInputData(sendData) // 数据的携带发送,一般都是携带到 Request 里面去发送数据给 WorkManager2
            .build()
    // 启动
    WorkManager.getInstance(this).enqueue(oneTimeWorkRequest1)
}

这样,我们就完成了 Activity 向 WorkManager 传递数据,以及在 WorkManager 中接收数据;

接下来,我们来看下 WorkManager 怎么向 Activity 回传数据,以及 Activity 中如何接收回传回来的数据;

MainWorker2 中通过 Data 进行回传数据的封装;

kotlin 复制代码
class MainWorker2(context: Context, private val workerParams: WorkerParameters) : Worker(context, workerParams) {

    companion object { const val TAG = "Mars" }

    // 后台任务 并且 异步的 (原理:线程池执行Runnable)
    @SuppressLint("RestrictedApi")
    override fun doWork(): Result { 
        // 开始执行了 ENQUEUED
        Log.d(TAG, "MainWorker2 doWork: 后台任务执行了")
        
        // 接收 MainActivity 传递过来的数据,通过 InputData
        val dataString = workerParams.inputData.getString("NBA")
        Log.d(TAG, "MainWorker2 doWork: 接收 MainActivity 传递过来的数据: $dataString")
        
        // 反馈数据 给 MainActivity
        // 把任务中的数据回传到MainActivity中
        val outputData = Data.Builder().putString("CBA", "Jordan").build()
        return Result.Success(outputData); // 本地执行 doWork 任务时成功执行任务完毕
        
    }
}

Success 标识成功,WorkManager 也提供了失败和重试的标识

Result.Failure() // 本地执行 doWork 任务时失败

Result.Retry(); // 本地执行 doWork 任务时重试一次,只重试一次,成不成不关心了

通过 Data 将回传数据封装起来,并通过 Result.Success 方法回传回去,我们来看下 Activity 中如何接收回传回来的数据;

接收 WorkManager 回传的数据,一般都是通过状态机来接收的,WorkManager 也天然的支持了 LiveData,并提供了 getWorkInfoByIdLiveData方法来监听结果

kotlin 复制代码
/**
 * 数据 互相传递
 * 测试后台任务 2
 *
 * @param view
 */
fun testBackgroundWork2(view: View?) {
    // 单一的任务  一次
    val oneTimeWorkRequest1: OneTimeWorkRequest
    // 数据
    val sendData = Data.Builder().putString("NBA", "Kobe").build()
    // 请求对象初始化
    oneTimeWorkRequest1 = OneTimeWorkRequest.Builder(MainWorker2::class.java)
            .setInputData(sendData) // 数据的携带发送,一般都是携带到 Request 里面去 发送数据给 WorkManager2
            .build()
            
    // 接收 WorkManager 回传的数据
    WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest1.id)
            .observe(this, { workInfo ->
                // ENQUEUED, RUNNING, SUCCEEED
                Log.d(TAG, "状态:" + workInfo.state.name)
                // ENQUEUED, RUNNING  都取不到 回馈的数据 都是 null
                Log.d(TAG, "取到了任务回传的数据: " + workInfo.outputData.getString("CBA"))
                if (workInfo.state.isFinished) { 
                    // 判断成功 SUCCEEDED 状态
                    Log.d(TAG, "取到了任务回传的数据: " + workInfo.outputData.getString("CBA"))
                }
            })
    
    // 启动
    WorkManager.getInstance(this).enqueue(oneTimeWorkRequest1)
}

这样我们就完成了从 WorkManager 接收回传数据;另外,我们只有在状态机是SUCCEEDED状态的时候才能获取到回传的数据;ENQUEUED 和 RUNNING 是获取不到回传的数据;

多任务顺序执行

我们前面讲的都是单个任务,那么多个任务如何顺序执行呢?我们来探索下;

首先,我们来声明多个任务 MainWorker3、MainWorker4、MainWorker5、MainWorker6

kotlin 复制代码
/**
 * 后台任务3
 */
class MainWorker3 (context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {

    companion object { const val TAG = "Mars" }

    @SuppressLint("RestrictedApi")
    override fun doWork(): Result {
        Log.d(TAG, "MainWorker3 doWork: 后台任务执行了")
        return Result.Success() // 本地执行 doWork 任务时 成功 执行任务完毕
    }
}

MainWorker4、MainWorker5、MainWorker6 完全复制,这里就不展示了;我们接下来看下如何让它们顺序执行;

scss 复制代码
/**
 * 多个任务 顺序执行
 * 测试后台任务 3
 *
 * @param view
 */
fun testBackgroundWork3(view: View) {
    // 单一的任务  一次
    val oneTimeWorkRequest3 = OneTimeWorkRequest.Builder(MainWorker3::class.java).build()
    val oneTimeWorkRequest4 = OneTimeWorkRequest.Builder(MainWorker4::class.java).build()
    val oneTimeWorkRequest5 = OneTimeWorkRequest.Builder(MainWorker5::class.java).build()
    val oneTimeWorkRequest6 = OneTimeWorkRequest.Builder(MainWorker6::class.java).build()
    
    // 顺序执行这四个任务,3、4、5、6
    WorkManager.getInstance(this)
        .beginWith(oneTimeWorkRequest3) // 做初始化检查的任务成功后
        .then(oneTimeWorkRequest4) // 业务4 任务成功后
        .then(oneTimeWorkRequest5) // 业务5 任务成功后
        .then(oneTimeWorkRequest6) // 最后检查工作任务6
        .enqueue()
    
    // 也可以采用集合的方式来顺序执行
    val oneTimeWorkRequests: MutableList<OneTimeWorkRequest> = ArrayList() // 集合方式
    oneTimeWorkRequests.add(oneTimeWorkRequest3)
    oneTimeWorkRequests.add(oneTimeWorkRequest4)

    WorkManager.getInstance(this).beginWith(oneTimeWorkRequests)
        .then(oneTimeWorkReques5)
        .then(oneTimeWorkRequest6) // 最后再检查同步
        .enqueue()
}

这样我们就完成了任务的顺序执行,接下来,我们来看下周期性任务如何实现

周期性任务执行

周期性任务就不能使用 OneTimeWorkRequest,要使用 PeriodicWorkRequest 来实现了;

kotlin 复制代码
/**
 * 重复执行后台任务  非单个任务,多个任务
 * 测试后台任务
 *
 * @param view
 */
fun testBackgroundWork4(view: View) {
    //
    val periodicWorkRequest = PeriodicWorkRequest.Builder(MainWorker3::class.java, 10, TimeUnit.SECONDS).build()
    // 监听状态
    WorkManager.getInstance(this).getWorkInfoByIdLiveData(periodicWorkRequest.id)
        .observe(this, { workInfo ->
            Log.d(TAG, "状态:" + workInfo.state.name) // ENQUEEN & RUNNING  循环反复
            if (workInfo.state.isFinished) {
                // 这里不会打印,因为一直在轮询任务,导致任务不会有完成了
                Log.d(TAG, "状态:isFinished=true 同学们注意:后台任务已经完成了...")
            }
        })
    WorkManager.getInstance(this).enqueue(periodicWorkRequest)
}

PS:这里有个点要注意下,虽然我们传入的是 10 秒,但是 Google 规定,这个值不能小于 15 分钟,否则会强制改成 15 分钟;

因为一直在轮询任务,导致任务不会有完成状态,只有 ENQUEEN & RUNNING 状态;

取消任务调用 cancelWorkById 方法

kotlin 复制代码
WorkManager.getInstance(this).cancelWorkById(periodicWorkRequest.getId())

这就是周期性任务的实现,接下来,我们来看下约束条件任务如何执行,也就是我们限定了某些场景下才能执行的任务

约束任务执行

假设,我们限定必须是『网络连接中状态』、『充电中状态』、『空闲状态』下才能执行的任务

kotlin 复制代码
/**
 * 约束条件,约束后台任务执行
 * 测试后台任务
 *
 * @param view
 */
@RequiresApi(api = Build.VERSION_CODES.M)
fun testBackgroundWork5(view: View?) {
    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED) // 必须是联网中
        .setRequiresCharging(true) // 必须是充电中
        .setRequiresDeviceIdle(true) // 必须是空闲时
        .build()
    // 请求对象
    val request = OneTimeWorkRequest.Builder(MainWorker3::class.java)
          .setConstraints(constraints) // Request 关联约束条件
          .build()

    // 加入队列
    WorkManager.getInstance(this).enqueue(request)
}

除了上面设置的约束外,WorkManger还提供了以下的约束作为Work执行的条件:

  • setRequiredNetworkType:网络连接设置
  • setRequiresBatteryNotLow:是否为低电量时运行 默认false
  • setRequiresCharging:是否要插入设备(接入电源),默认false
  • setRequiresDeviceIdle:设备是否为空闲,默认false
  • setRequiresStorageNotLow:设备可用存储是否不低于临界阈值

进程杀死继续执行的任务

前面开头我们有讲到,WorkManager 本身支持在进程不存活的状态下执行任务,我们怎么来验证这个能力,可以通过启动任务之后,写入 SP 的方式来验证下,我们先来声明一个写入 SP 的 Worker;

kotlin 复制代码
class MainWorker7(context: Context, workerParameters: WorkerParameters) : Worker(context, workerParameters) {
    // 三个静态常量 SP的标记
    companion object {
        const val TAG = "Mars"
        const val SP_NAME = "spNAME" // SP name
        const val SP_KEY = "spKEY" // SP Key
    }

    // 后台任务
    @SuppressLint("RestrictedApi")
    override fun doWork(): Result {
        Log.d(TAG, "MainWorker7 doWork: 后台任务执行了 started")
        // 睡眠八秒钟
        try {
            Thread.sleep(8000)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }
        // 获取SP
        val sp = applicationContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
        // 获取 sp 里面的值
        var spIntValue = sp.getInt(SP_KEY, 0)
        sp.edit().putInt(SP_KEY, ++spIntValue).apply() // 隔8秒钟 更新文件
        Log.d(TAG, "MainWorker7 doWork: 后台任务执行了 end")
        return Result.Success() // 本地执行 doWork 任务时 成功 执行任务完毕
    }
}

接下来我们就常规启动这个任务;

kotlin 复制代码
fun testBackgroundWork6(view: View?) {
    // 约束条件
    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED) // 约束条件,必须是网络连接
        .build()
    // 构建Request
    val request = OneTimeWorkRequest.Builder(MainWorker7::class.java)
        .setConstraints(constraints)
        .build()
    // 加入队列
    WorkManager.getInstance(this).enqueue(request)
}

接下来,我们在 Activity 中监听 SP 的变化,通过实现SharedPreferences.OnSharedPreferenceChangeListener接口,来实时监听 SP 内容的变化,我们来让 Activity 实现这个接口

kotlin 复制代码
class MainActivity : AppCompatActivity() , SharedPreferences.OnSharedPreferenceChangeListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_main)
        // 绑定 SP 变化监听
        val sp = getSharedPreferences(MainWorker7.SP_NAME, MODE_PRIVATE)
        sp.registerOnSharedPreferenceChangeListener(this)
        updateToUI()
    }
    
    // 监听 SP 变化,并实时更新UI
    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) 
        = updateToUI()
    
    // 从SP里面获取值,显示到界面
    private fun updateToUI() {
        val sp = getSharedPreferences(MainWorker7.SP_NAME, MODE_PRIVATE)
        val resultValue = sp.getInt(MainWorker7.SP_KEY, 0)
        bt6?.setText("测试后台任务六 -- $resultValue")
    }
    
}

启动任务之后,我们将 APP 杀死,等到任务结束之后,我们来看下 sp 中是否写入了我们期望的内容;

原理篇


原理主要从:初始化、执行、状态变换三个方向来展开讲解

初始化

WorkManager 借助了强大的 Proivder 来进行的初始化,当我们在程序中依赖 WorkManager 之后,编译产出 apk 之后,可以直接在 AS 中打开分析 apk,在 AndroidManifest.xml 中可以看到 WorkManager 相关的 Provider

ini 复制代码
<provider
    android:name="androidx.work.impl.WorkManagerInitializer"
    android:exported="false"
    android:multiprocess="true"
    android:authorities="com.llc.workmanager.workmanager-init"
    android:directBootAware="false" />

这个是 gradle 帮我们生成的 Provider,我们进入这个 WorkManagerInitializer 来看下:

scala 复制代码
public class WorkManagerInitializer extends ContentProvider {
    @Override
    public boolean onCreate() {
        // Initialize WorkManager with the default configuration.
        WorkManager.initialize(getContext(), new Configuration.Builder().build());
        return true;
    }
    
    ... 省略部分代码
}

这里就是在 Provider 中执行了初始化的操作,我们进入这个 initialize方法看下:

less 复制代码
public static void initialize(@NonNull Context context, @NonNull Configuration configuration) {
    synchronized (sLock) {
        if (sDelegatedInstance == null) {
            context = context.getApplicationContext();
            if (sDefaultInstance == null) {
                sDefaultInstance = new WorkManagerImpl(
                        context,
                        configuration,
                        new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
            }
            sDelegatedInstance = sDefaultInstance;
        }
    }
}

很简单的单例初始化,第一次的初始化是放在 Provider 中执行的;我们进入这个 WorkManagerImpl 的构造方法中看下:

less 复制代码
public WorkManagerImpl(
        @NonNull Context context,
        @NonNull Configuration configuration,
        @NonNull TaskExecutor workTaskExecutor,
        // 核心参数
        @NonNull WorkDatabase database) {
    Context applicationContext = context.getApplicationContext();
    Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
    // 核心逻辑 1
    List<Scheduler> schedulers = createSchedulers(applicationContext, workTaskExecutor);
    Processor processor = new Processor(
            context,
            configuration,
            workTaskExecutor,
            database,
            schedulers);
    // 核心逻辑 2       
    internalInit(context, configuration, workTaskExecutor, database, schedulers, processor);
}

可以看到,入参中使用了WorkDatabase 这个就是 Room 中的数据库,所以说 WorkManager 中采用了 Room 来管理相关任务来保证即使手机重启,App 进程被杀掉也依然能够执行我们的任务;

这里也创建了一个调度器,通过 createSchedulers 方法,我们进入这个方法看下:

less 复制代码
public List<Scheduler> createSchedulers(
        @NonNull Context context,
        @NonNull TaskExecutor taskExecutor) {
    return Arrays.asList(
            Schedulers.createBestAvailableBackgroundScheduler(context, this),
            new GreedyScheduler(context, taskExecutor, this));
}

最终 new 了一个 GreedyScheduler 贪婪调度器;

创建这个调度器之后,交给了 Processor 来管理这个调度器

ini 复制代码
Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);

最终调用 internalInit 方法来执行真正的初始化

less 复制代码
private void internalInit(@NonNull Context context,
        @NonNull Configuration configuration,
        @NonNull TaskExecutor workTaskExecutor,
        @NonNull WorkDatabase workDatabase,
        @NonNull List<Scheduler> schedulers,
        @NonNull Processor processor) {

    context = context.getApplicationContext();
    mContext = context;
    mConfiguration = configuration;
    mWorkTaskExecutor = workTaskExecutor;
    mWorkDatabase = workDatabase;
    mSchedulers = schedulers;
    mProcessor = processor;
    mPreferenceUtils = new PreferenceUtils(workDatabase);
    mForceStopRunnableCompleted = false;

    // 这里的 【检查应用程序强制停止】 例如:正在执行任务的时候,手机关机了,或者发生了意外,这里就会重试之前失败的任务哦
    mWorkTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
}

初始化中,启动了一个 ForceStopRunnable 这样的一个『Runnable』来进行初始化的操作,也就是说 WorkManager 的初始化是在子线程中执行的;

我们进入这个 Runnable 的 run 方法看下:

scss 复制代码
public void run() {
    WorkDatabasePathHelper.migrateDatabase(mContext);
    try {
        boolean needsScheduling = cleanUp();
        if (shouldRescheduleWorkers()) {
            mWorkManager.rescheduleEligibleWork();
            mWorkManager.getPreferenceUtils().setNeedsReschedule(false);
        } else if (isForceStopped()) {
            mWorkManager.rescheduleEligibleWork();
        } else if (needsScheduling) {
            Schedulers.schedule(
                    mWorkManager.getConfiguration(),
                    mWorkManager.getWorkDatabase(),
                    mWorkManager.getSchedulers());
        }
        mWorkManager.onForceStopRunnableCompleted();
    } catch (SQLiteCantOpenDatabaseException
            | SQLiteDatabaseCorruptException
            | SQLiteAccessPermException exception) {
       ... 省略部分代码     
    }
}

这个 run 的作用主要是:发现了未完成的任务,重新执行任务;

WorkManager的初始化总结

① WorkManager 的初始化是由 WorkManagerInitializer 这个 ContentProvider 执行的

② 会初始化 Configuration,WorkManagerTaskExecutor,WorkDatabase,Schedulers,Processor

③ GreedyScheduler

④ 发现了未完成的,需要重新执行的任务(之前意外中断的继续执行)

执行

任务执行又分为 约束任务执行非约束任务执行

kotlin 复制代码
WorkManager.getInstance(this).enqueue(request)

通过 enqueue 来启动任务,我们进入这个方法看下:

less 复制代码
public Operation enqueue(
        @NonNull List<? extends WorkRequest> workRequests) {
    ... 省略部分代码
    return new WorkContinuationImpl(this, workRequests).enqueue();
}

这里直接创建 WorkContinuationImpl 并执行 enqueue 方法,我们进入这个方法看下:

java 复制代码
public @NonNull Operation enqueue() {
    if (!mEnqueued) {
        EnqueueRunnable runnable = new EnqueueRunnable(this);
        // 核心逻辑
        mWorkManagerImpl.getWorkTaskExecutor().executeOnBackgroundThread(runnable);
        mOperation = runnable.getOperation();
    } else {
        ... 省略部分代码
    }
    return mOperation;
}

WorkManager 的 TaskExecutor 执行了 EnqueueRunnable ,那么我们来看下 EnqueueRunnable 的 run 函数

scss 复制代码
public void run() {
    try {
        ... 省略部分代码
        // 核心逻辑 1
        boolean needsScheduling = addToDatabase();
        if (needsScheduling) {
            final Context context =
                    mWorkContinuation.getWorkManagerImpl().getApplicationContext();
            PackageManagerHelper.setComponentEnabled(context, RescheduleReceiver.class, true);
            // 核心逻辑 2
            scheduleWorkInBackground();
        }
        mOperation.setState(Operation.SUCCESS);
    } catch (Throwable exception) {
        ... 省略部分代码
    }
}

这里是两块核心的地方,一个是 addToDatabase 这个方法是用来构建数据库的,一个是 scheduleWorkInBackground 这个方法用来执行异步任务;

我们先来看主流程 scheduleWorkInBackground

scss 复制代码
public void scheduleWorkInBackground() {
    WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
    Schedulers.schedule(
            workManager.getConfiguration(),
            workManager.getWorkDatabase(),
            workManager.getSchedulers());
}

这里通过 Schedulers.schedule 来执行调度,我们进入这个 schedule 方法看下:

less 复制代码
public static void schedule(
        @NonNull Configuration configuration,
        @NonNull WorkDatabase workDatabase,
        List<Scheduler> schedulers) {
    WorkSpecDao workSpecDao = workDatabase.workSpecDao();
    List<WorkSpec> eligibleWorkSpecs;
    
    workDatabase.beginTransaction();
    try {
        eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(
                configuration.getMaxSchedulerLimit());
        if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
            long now = System.currentTimeMillis();
            for (WorkSpec workSpec : eligibleWorkSpecs) {
                workSpecDao.markWorkSpecScheduled(workSpec.id, now);
            }
        }
        // 核心逻辑1
        workDatabase.setTransactionSuccessful();
    } finally {
        workDatabase.endTransaction();
    }

    if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
        WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
        for (Scheduler scheduler : schedulers) {
            // 核心逻辑在这里
            scheduler.schedule(eligibleWorkSpecsArray);
        }
    }
}

这里两个逻辑,核心逻辑1是:任务保存到数据,另一个就是执行调度;

最终,for 循环所有的调度器,并执行 schedule 方法,这里一共有三个调度器实现类:

我们前面在初始化的时候创建了一个 GreedyScheduler 调度器,所以这里一定是执行这个调度器的 schedule 方法,我们进入这个方法看下:

scss 复制代码
public void schedule(@NonNull WorkSpec... workSpecs) {
    if (mIsMainProcess == null) {
        mIsMainProcess = TextUtils.equals(mContext.getPackageName(), getProcessName());
    }

    registerExecutionListenerIfNeeded();
    List<WorkSpec> constrainedWorkSpecs = new ArrayList<>();
    List<String> constrainedWorkSpecIds = new ArrayList<>();
    for (WorkSpec workSpec : workSpecs) {
        if (workSpec.state == WorkInfo.State.ENQUEUED
                && !workSpec.isPeriodic()
                && workSpec.initialDelay == 0L
                && !workSpec.isBackedOff()) {
            // 这里区分约束条件任务 和 非约束条件任务    
            if (workSpec.hasConstraints()) {
                if (SDK_INT >= 23 && workSpec.constraints.requiresDeviceIdle()) {
                    //
                } else if (SDK_INT >= 24 && workSpec.constraints.hasContentUriTriggers()) {
                   // 
                } else {
                    constrainedWorkSpecs.add(workSpec);
                    constrainedWorkSpecIds.add(workSpec.id);
                }
            } else {
                // 核心逻辑
                mWorkManagerImpl.startWork(workSpec.id);
            }
        }
    }

    synchronized (mLock) {
        if (!constrainedWorkSpecs.isEmpty()) {
            mConstrainedWorkSpecs.addAll(constrainedWorkSpecs);
            mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
        }
    }
}

我们先来看非约束条件任务,mWorkManagerImpl.startWork(workSpec.id);

less 复制代码
public void startWork(
        @NonNull String workSpecId,
        @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
    mWorkTaskExecutor
            .executeOnBackgroundThread(
                    new StartWorkRunnable(this, workSpecId, runtimeExtras));
}

这里又启动了一个 StartWorkRunnable 来执行任务,我们进入这个 Runnable 的 run 方法看下:

csharp 复制代码
public void run() {
    mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
}

我们进入这个 startWork 看下:

less 复制代码
public boolean startWork(
        @NonNull String id,
        @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {

    WorkerWrapper workWrapper;
    synchronized (mLock) {
        .... 省略部分代码
        workWrapper =
                new WorkerWrapper.Builder(
                        mAppContext,
                        mConfiguration,
                        mWorkTaskExecutor,
                        this,
                        mWorkDatabase,
                        id)
                        .withSchedulers(mSchedulers)
                        .withRuntimeExtras(runtimeExtras)
                        .build();
        ListenableFuture<Boolean> future = workWrapper.getFuture();
        future.addListener(
                new FutureListener(this, id, future),
                mWorkTaskExecutor.getMainThreadExecutor());
        mEnqueuedWorkMap.put(id, workWrapper);
    }
    // 核心逻辑在这里
    mWorkTaskExecutor.getBackgroundExecutor().execute(workWrapper);
    return true;
}

这里又启动了一个 WorkerWrapper 来执行任务,我们进入这个 Runnable 的 run 方法看下:

scss 复制代码
public void run() {
    mTags = mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId);
    mWorkDescription = createWorkDescription(mTags);
    // 核心逻辑
    runWorker();
}

这里直接调用了 runWorker 方法,我们进入这个方法看下:

scss 复制代码
private void runWorker() {
    
    .... 省略部分代码
    
    if (trySetRunning()) {
        final SettableFuture<ListenableWorker.Result> future = SettableFuture.create();
        mWorkTaskExecutor.getMainThreadExecutor()
                .execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // 核心逻辑在这里
                            mInnerFuture = mWorker.startWork();
                            future.setFuture(mInnerFuture);
                        } catch (Throwable e) {
                            future.setException(e);
                        }

                    }
                });
        }, mWorkTaskExecutor.getBackgroundExecutor());
    }
    
    .... 省略部分代码
}

最终执行的是 startWork 方法,这个方法也有两个实现,我们进入这个方法看下:

分为有约束Work 和无约束 Work,我们先看无约束 Work

java 复制代码
public final @NonNull ListenableFuture<Result> startWork() {
    mFuture = SettableFuture.create();
    getBackgroundExecutor().execute(new Runnable() {
        @Override
        public void run() {
            try {
                // 核心逻辑
                Result result = doWork();
                mFuture.set(result);
            } catch (Throwable throwable) {
                mFuture.setException(throwable);
            }

        }
    });
    return mFuture;
}

执行的是 doWork 方法是一个抽象方法,最终执行的就是我们自定的 Worker 的 doWork 方法;

这样,WorkManager 工作的主流程就打通了,我们接下来看下状态变换,就是当 网络发生变化的时候,它为什么可以继续执行任务;

状态变换

以网络切换为例子,通过注册监听网络变化的广播 ConstraintProxy 来监听网络变化,网络发生变化回调onReceive 方法;

我们通过 AndroidManifest.xml 可以看到,WorkManager 帮我们注册了很多的系统服务以及广播;

ini 复制代码
<service
    android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
    android:enabled="@ref/0x7f040003"
    android:exported="false"
    android:directBootAware="false" />

<service
    android:name="androidx.work.impl.background.systemjob.SystemJobService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:enabled="@ref/0x7f040005"
    android:exported="true"
    android:directBootAware="false" />

<service
    android:name="androidx.work.impl.foreground.SystemForegroundService"
    android:enabled="@ref/0x7f040004"
    android:exported="false"
    android:directBootAware="false" />

<receiver
    android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
    android:enabled="true"
    android:exported="false"
    android:directBootAware="false" />

<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
            android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
    </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
            android:name="android.intent.action.BATTERY_LOW" />
    </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
            android:name="android.intent.action.DEVICE_STORAGE_OK" />
    </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" />
    </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
            android:name="android.intent.action.TIME_SET" />

        <action
            android:name="android.intent.action.TIMEZONE_CHANGED" />
    </intent-filter>
</receiver>

<receiver
    android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver"
    android:enabled="@ref/0x7f040003"
    android:exported="false"
    android:directBootAware="false">

    <intent-filter>

        <action
            android:name="androidx.work.impl.background.systemalarm.UpdateProxies" />
    </intent-filter>
</receiver>

<service
    android:name="androidx.room.MultiInstanceInvalidationService"
    android:exported="false" />

我们就以其中的网络变换的 Receiver 为切入点,来看下:

scala 复制代码
public static class NetworkStateProxy extends ConstraintProxy {
}

可以看到,NetworkStateProxy 是继承了 ConstraintProxy,我们来看下 ConstraintProxy 这个类;

scala 复制代码
abstract class ConstraintProxy extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Logger.get().debug(TAG, String.format("onReceive : %s", intent));
        Intent constraintChangedIntent = CommandHandler.createConstraintsChangedIntent(context);
        context.startService(constraintChangedIntent);
    }
}

这里我们重点看下 onReceive 方法,通过 createConstraintsChangedIntent 来创建目标 Intent

less 复制代码
static Intent createConstraintsChangedIntent(@NonNull Context context) {
    Intent intent = new Intent(context, SystemAlarmService.class);
    intent.setAction(ACTION_CONSTRAINTS_CHANGED);
    return intent;
}

Intent 中启动了一个 SystemAlarmService 服务;这是一个系统级别的服务,非 App 级别的;

我们进入这个 Service 的 onStartCommand 方法看下:

scss 复制代码
public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStartCommand(intent, flags, startId);
    if (mIsShutdown) {
        mDispatcher.onDestroy();
        initializeDispatcher();
        mIsShutdown = false;
    }

    if (intent != null) {
        // 核心逻辑在这里
        mDispatcher.add(intent, startId);
    }
    return Service.START_REDELIVER_INTENT;
}

启动服务之后的核心逻辑在 add 方法,我们进入这个方法看下:

java 复制代码
public boolean add(@NonNull final Intent intent, final int startId) {
    .... 省略部分代码
    
    intent.putExtra(KEY_START_ID, startId);
    synchronized (mIntents) {
        boolean hasCommands = !mIntents.isEmpty();
        mIntents.add(intent);
        if (!hasCommands) {
            // 核心逻辑在这里
            processCommand();
        }
    }
    return true;
}

这里直接调用 processCommand 方法来执行状态变化;

csharp 复制代码
private void processCommand() {
    try {
        .... 省略部分代码
        mWorkManager.getWorkTaskExecutor().executeOnBackgroundThread(new Runnable() {
            @Override
            public void run() {
                synchronized (mIntents) {
                    mCurrentIntent = mIntents.get(0);
                }
                if (mCurrentIntent != null) {
                    final int startId = mCurrentIntent.getIntExtra(KEY_START_ID,
                            DEFAULT_START_ID);
                    .... 省略部分代码
                    try {
                        .... 省略部分代码
                        // 核心逻辑在这里
                        mCommandHandler.onHandleIntent(mCurrentIntent, startId,
                                SystemAlarmDispatcher.this);
                    } catch (Throwable throwable) {
                        .... 省略部分代码
                    }  finally {
                        .... 省略部分代码
                    }
                }
            }
        });
    } finally {
        .... 省略部分代码
    }
}

核心逻辑在这个 onHandleIntent 方法中,我们进入这个方法看下:

less 复制代码
void onHandleIntent(
        @NonNull Intent intent,
        int startId,
        @NonNull SystemAlarmDispatcher dispatcher) {

    String action = intent.getAction();
    
    if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
        // 核心逻辑 1
        handleConstraintsChanged(intent, startId, dispatcher);
    } else if (ACTION_RESCHEDULE.equals(action)) {
        handleReschedule(intent, startId, dispatcher);
    } else {
        Bundle extras = intent.getExtras();
        if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
            Logger.get().error(TAG,
                    String.format("Invalid request for %s, requires %s.",
                            action,
                            KEY_WORKSPEC_ID));
        } else {
            if (ACTION_SCHEDULE_WORK.equals(action)) {
                handleScheduleWorkIntent(intent, startId, dispatcher);
            } else if (ACTION_DELAY_MET.equals(action)) {
                handleDelayMet(intent, startId, dispatcher);
            } else if (ACTION_STOP_WORK.equals(action)) {
                handleStopWork(intent, dispatcher);
            } else if (ACTION_EXECUTION_COMPLETED.equals(action)) {
                handleExecutionCompleted(intent, startId);
            } else {
                Logger.get().warning(TAG, String.format("Ignoring intent %s", intent));
            }
        }
    }
}

核心逻辑 1 就是我们前面创建 Service 的时候传入的 ACTION_CONSTRAINTS_CHANGED 所以,一定会执行 handleConstraintsChanged 方法,每次执行的时候都同步检测约束条件;

less 复制代码
private void handleConstraintsChanged(
        @NonNull Intent intent, int startId,
        @NonNull SystemAlarmDispatcher dispatcher) {
    ConstraintsCommandHandler changedCommandHandler =
            new ConstraintsCommandHandler(mContext, startId, dispatcher);
    // 核心逻辑        
    changedCommandHandler.handleConstraintsChanged();
}

这里直接调用 handleConstraintsChanged 方法;

ini 复制代码
void handleConstraintsChanged() {
    List<WorkSpec> candidates = mDispatcher.getWorkManager().getWorkDatabase()
            .workSpecDao()
            .getScheduledWork();
            
    .... 省略部分代码

    for (WorkSpec workSpec : eligibleWorkSpecs) {
        String workSpecId = workSpec.id;
        // 核心逻辑
        Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
        mDispatcher.postOnMainThread(
                new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));
    }

    mWorkConstraintsTracker.reset();
}

这里又创建另一个 Intent,通过 createDelayMetIntent 方法,我们进入这个方法看下;

less 复制代码
static Intent createDelayMetIntent(@NonNull Context context, @NonNull String workSpecId) {
    Intent intent = new Intent(context, SystemAlarmService.class);
    intent.setAction(ACTION_DELAY_MET);
    intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
    return intent;
}

这里依然是启动 SystemAlarmService ,只不过 startId,替换成了 ACTION_DELAY_MET 我们回到这个 Service 的 onStartCommand 方法看下,最终按照前面 ACTION_CONSTRAINTS_CHANGED 的流程,进入到 CommandHandleronHandleIntent 方法中的节点:

scss 复制代码
if (ACTION_DELAY_MET.equals(action)) {
    handleDelayMet(intent, startId, dispatcher);
}

进入这个方法看下:

less 复制代码
private void handleDelayMet(
        @NonNull Intent intent,
        int startId,
        @NonNull SystemAlarmDispatcher dispatcher) {

    Bundle extras = intent.getExtras();
    synchronized (mLock) {
        String workSpecId = extras.getString(KEY_WORKSPEC_ID);
        if (!mPendingDelayMet.containsKey(workSpecId)) {
            DelayMetCommandHandler delayMetCommandHandler =
                    new DelayMetCommandHandler(mContext, startId, workSpecId, dispatcher);
            mPendingDelayMet.put(workSpecId, delayMetCommandHandler);
            // 核心逻辑
            delayMetCommandHandler.handleProcessWork();
        } else {
           .... 省略部分代码
        }
    }
}

我们进入这个 handleProcessWork 看下:

scss 复制代码
void handleProcessWork() {
    
    .... 省略部分代码

    WorkSpec workSpec = mDispatcher.getWorkManager()
            .getWorkDatabase()
            .workSpecDao()
            .getWorkSpec(mWorkSpecId);

    .... 省略部分代码
    
    mHasConstraints = workSpec.hasConstraints();

    if (!mHasConstraints) {
        // 核心逻辑
        onAllConstraintsMet(Collections.singletonList(mWorkSpecId));
    } else {
        // Allow tracker to report constraint changes
        mWorkConstraintsTracker.replace(Collections.singletonList(workSpec));
    }
}

核心逻辑在这个 onAllConstraintsMet 方法;

scss 复制代码
public void onAllConstraintsMet(@NonNull List<String> workSpecIds) {
    if (!workSpecIds.contains(mWorkSpecId)) {
        return;
    }
    synchronized (mLock) {
        if (mCurrentState == STATE_INITIAL) {
            mCurrentState = STATE_START_REQUESTED;
            // 核心逻辑
            boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);
            if (isEnqueued) {
                mDispatcher.getWorkTimer()
                        .startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this);
            } else {、
                cleanUp();
            }
        } else {
            .... 省略部分代码
        }
    }
}

最终调用到了 startWork,就又回到了我们前面看到的 startWork 的逻辑,开始启动我们创建的任务;

到此,约束任务下,状态变换的时候,也就能执行到我们创建的任务了;

好了,WorkManager 就拆解到这里吧~

总结 借助 Room 和系统服务 SystemAlarmService 来完成我们的任务即使进程被杀死的情况下依然能够执行的能力;

欢迎三连


来都来了,点个关注,点个赞吧,你的支持是我前进的最大动力~

相关推荐
我命由我123452 天前
Android 对话框 - 对话框全屏显示(设置 Window 属性、使用自定义样式、继承 DialogFragment 实现、继承 Dialog 实现)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Jeled2 天前
Android 本地存储方案深度解析:SharedPreferences、DataStore、MMKV 全面对比
android·前端·缓存·kotlin·android studio·android jetpack
我命由我123452 天前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
alexhilton7 天前
Kotlin互斥锁(Mutex):协程的线程安全守护神
android·kotlin·android jetpack
是六一啊i9 天前
Compose 在Row、Column上使用focusRestorer修饰符失效原因
android jetpack
用户0609052552210 天前
Compose 主题 MaterialTheme
android jetpack
用户0609052552210 天前
Compose 简介和基础使用
android jetpack
用户0609052552210 天前
Compose 重组优化
android jetpack
行墨10 天前
Jetpack Compose 深入浅出(一)——预览 @Preview
android jetpack