如何应对 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 来完成我们的任务即使进程被杀死的情况下依然能够执行的能力;

欢迎三连


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

相关推荐
_一条咸鱼_2 天前
Android Runtime调试检测与反制手段(86)
android·面试·android jetpack
_一条咸鱼_4 天前
Android Runtime内存访问越界检查源码解析(82)
android·面试·android jetpack
_一条咸鱼_5 天前
Android Runtime沙箱隔离与进程间通信限制原理深度剖析(78)
android·面试·android jetpack
_一条咸鱼_5 天前
Android Runtime内存安全保护机制深度解析(80)
android·面试·android jetpack
刘龙超6 天前
如何应对 Android 面试官 -> 玩转 Jetpack Navigation
android jetpack
alexhilton6 天前
SnapshotFlow还是collectAsState?对于Jetpack Compose来说哪个更香?
android·kotlin·android jetpack
_一条咸鱼_7 天前
Android Runtime安全上下文管理(76)
android·面试·android jetpack
_一条咸鱼_7 天前
Android Runtime跨进程调用优化方案深度解析(75)
android·面试·android jetpack
_一条咸鱼_7 天前
OpenGL ES 深度剖析
android·面试·android jetpack