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

Jetpack ---- WorkManager入门(三) 翻译自android官网,可直接去官网观看 Jetpack ---- WorkManager入门(三)四、先进概念一、自定义WorkManager配置和初始化按需初始化删除默认的初始化程序Implement Configuration.ProviderWorkManager 2.1.0之前的自定义初始化默认初始化自定义初始化(二)WorkManager中的线程总览Threading in WorkerThreading in CoroutineWorkerThreading in RxWorkerThrea 阅读详情

前言


在这里插入图片描述

本章进行 WorkManager 的学习;

是什么?


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

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

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

有什么用?


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

使用篇


依赖

def work_version = "xxx"
implementation "androidx.work:work-runtime:$work_version"

基础调用

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

// 最简单的 执行任务
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

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

/**
 * 数据互相传递
 * 后台任务
 */
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 进行发送数据的封装

/**
 * 数据 互相传递
 * 测试后台任务 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 进行回传数据的封装;

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方法来监听结果

/**
 * 数据 互相传递
 * 测试后台任务 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

/**
 * 后台任务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 完全复制,这里就不展示了;我们接下来看下如何让它们顺序执行;

/**
 * 多个任务 顺序执行
 * 测试后台任务 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来实现了;

/**
 * 重复执行后台任务  非单个任务,多个任务
 * 测试后台任务
 *
 * @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 方法

WorkManager.getInstance(this).cancelWorkById(periodicWorkRequest.getId())

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

约束任务执行

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

/**
 * 约束条件,约束后台任务执行
 * 测试后台任务
 *
 * @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;

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 任务时 成功 执行任务完毕
    }
}

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

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 实现这个接口

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

<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 来看下:

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方法看下:

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的构造方法中看下:

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方法,我们进入这个方法看下:

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 来管理这个调度器

Processor processor = new Processor(
                context,
                configuration,
                workTaskExecutor,
                database,
                schedulers);

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

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 方法看下:

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

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

执行

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

WorkManager.getInstance(this).enqueue(request)

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

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

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

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函数

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

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

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

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方法,我们进入这个方法看下:

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);

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

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

public void run() {
    mWorkManagerImpl.getProcessor().startWork(mWorkSpecId, mRuntimeExtras);
}

我们进入这个 startWork看下:

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方法看下:

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

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

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

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 帮我们注册了很多的系统服务以及广播;

<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 为切入点,来看下:

public static class NetworkStateProxy extends ConstraintProxy {
}

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

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

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方法看下:

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方法,我们进入这个方法看下:

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方法来执行状态变化;

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方法中,我们进入这个方法看下:

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方法,每次执行的时候都同步检测约束条件;

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

这里直接调用 handleConstraintsChanged方法;

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方法,我们进入这个方法看下;

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 的流程,进入到 CommandHandler 的 onHandleIntent方法中的节点:

if (ACTION_DELAY_MET.equals(action)) {
    handleDelayMet(intent, startId, dispatcher);
}

进入这个方法看下:

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看下:

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方法;

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

欢迎三连


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

Android开发工程师核心技能与面试全攻略 移动应用开发中,Android系统作为全球占有率最高的移动操作系统,其开发技术栈持续演进。从基础的Java/Kotlin语言特性到Android四大组件原理,开发者需要建立完整的知识体系。现代Android开发更涉及Jetpack组件库、Compose声明式UI等前沿技术,性能优化与架构设计能力成为区分工程师水平的关键指标。在工程实践中,内存泄漏排查、启动速度优化等场景考验开发者的实战能力。对于求职者而言,掌握Handler机制、Binder IPC等系统原理,以及具备模块化架构设计经验,是通过大厂技术面试 阅读详情

相关推荐

Android 框架入门到实战:从系统架构到四大组件,面试官问的全在这了(附流程图)

本文系统讲解 Android 框架核心知识,涵盖五层系统架构(Linux内核至应用层)及启动流程。详细剖析四大组件:Activity 生命周期与启动模式、Service 两种启动方式、BroadcastReceiver 注册机制、ContentProvider 跨应用数据共享,以及 Intent、Fragment 通信与 Jetpack 核心组件(ViewModel、LiveData等),并总结常见开发陷阱与选型建议。

weixin_44081096的博客 526

WorkManager的进阶

Advanced Concepts Custom WorkManager Configuration and Initialization 默认情况下,当app启动的时候WorkManager自动的完成了配置,这个配置适用于大多数app.如果我们需要对WorkManager有更多的控制,比如:管理和调度任务,可以使用自己的配置初始化WorkManager. 这里有三种初始化的方式: Defau...

s127838498的专栏 2225

安卓设备安全相关技术

当前安卓设备使用的加密方案是基于Soc中一块可写入的rom实现,称为熔丝,其结果不可逆。 1、secureboot 2、AVB(Android Verify Boot) 3、dm-verity(device mapper) 4、keymaster 5、crypto 6、rpmb(replay private memory block) 7、TEE(Trust execute environment) 8、SELinux .........

weixin_43797328的博客 4836

WorkManager的学习和使用要点笔记

1.在oppo Android 5.1手机上报错Invoke-customs are only supported starting with Android O 解决办法:在app的build.gradle中添加 android{ compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } 2.Wor

qq_34332698的博客 1595

RcisTarget实战:从差异基因到转录因子调控网络的完整分析流程(附代码)

本文详细介绍了RcisTarget工具在转录因子调控网络分析中的完整流程,从差异基因到motif富集分析,再到结果可视化与生物学解读。通过实战代码演示,帮助研究者掌握这一强大的R包应用,揭示基因表达背后的调控机制,特别适用于基因组学和生物信息学研究。

weixin_29214199的博客 228

零基础用AHK操作Excel:从数据读取到自动填写(附完整脚本解析)

本文为零基础用户详细介绍了如何使用AutoHotkey(AHK)通过COM接口高效操作Excel,实现从数据自动读取到填写的完整自动化流程。文章包含完整的脚本解析、环境搭建指南、核心代码逐行讲解以及实际应用案例,帮助办公人员大幅提升数据处理效率,告别重复劳动。

weixin_29202213的博客 553

Android源码阅读 WorkManager - 2

由于笔者目前水平限制,表达能力有限,尽请见谅。WorkManager 是 Android Jetpack 库的一部分,提供了一种向后兼容的方式来安排可延迟的异步任务,这些任务即使在应用退出或设备重启后也应该继续执行。它是 Android 推荐的解决方案,用于处理需要保证执行的后台任务。WorkManager 适合用于那些不需要立即执行的任务,但最终需要完成的任务。前文主要介绍了调用getInstance获取WorkManager实例系统背后做了什么,本文将继续深入。

夏目艾拉的学习之路 1184

用FPGA在HDMI上显示自定义字符:从COE文件到OSD叠加的保姆级教程

本文详细介绍了如何在FPGA上实现HDMI视频流的OSD字符叠加技术,从COE文件生成到动态区域控制的完整流程。通过Verilog代码示例和实战技巧,帮助开发者掌握字符ROM设计、像素坐标追踪和动态混合等核心技术,适用于视频监控、系统状态显示等应用场景。

weixin_28720767的博客 231

多进程初始化WorkManager

1、背景 WorkManager用于处理Android中复杂的异步工作。WorkManager插件足以替换JobScheduler、Firebase JobDispatcher或Alarm Manager来完成这项工作。 假设一个应用有多进程,在初始化WorkManager,除了主进程,还包含子进程,我们命名为remote。 当我创建PeriodicWorkRequest在Application.onCreate,这是最简单的启动方式。这时候会受到异常: Process: com.examp...

1728

Python+Vue计算机毕业设计风电场监控系统的设计与实现7bd6i(程序+LW+源码+部署)

Python3.7.7+Django+Mysql5.7+pip list+HBuilderX(Vscode也行)+Vue+Pychram社区版。2. 前端:vue+css+javascript+jQuery+easyUI+highcharts。2.IDE环境:Python和Python 社区版都可以。Django + Vue +Python+Mysql 等等组成,B/S模式等等。该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。1. 后端:Python、Vue、Django、Mysql。

华信计算机毕设源码程序 340

SE80 Repository Information System 在 SAP CRM WebClient UI 组件分析中的实战指南

在 SAP CRM WebClient UI 开发过程中,如何快速定位组件、视图、上下文节点以及各类运行时对象,一直是调试效率的关键。(后文简称RIS)正是隐藏在SE80里的瑞士军刀,帮助开发者按照组件集、数据元素、上下文节点属性等多维条件进行交叉检索。官方博客将它描述为本文结合多年项目经验,系统梳理RIS的功能脉络与实战技巧,同时引入真实案例,示范如何借助它在几分钟内完成以往动辄数小时的排查任务。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 2万+

Jetpack系列(九) — WorkManager,面试官问的那些安卓原理你都懂吗

考虑到文章的篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档,如果有需要的朋友可以私信我【面试】免费领取点击这里领取Android面试资料汇总篇幅问题,我把这些问题和答案以及我多年面试所遇到的问题和一些面试资料做成了PDF文档,如果有需要的朋友可以私信我【面试】免费领取点击这里领取Android面试资料汇总[外链图片转存中…(img-IxXTuInm-1710666301565)][外链图片转存中…(img-rs0k6fe8-1710666301565)]

983

人工智能训练师(三级)操作技能考核·自测卷

为您定制了一份。这份试卷严格拆分了“业务分析”、“智能训练”、“智能系统设计”、“培训与指导”四大模块,所有填空题均来源于上一轮整理的。您可以先用它进行“第二步(抛开答案自己填)”,填完后对照末尾的答案速查进行“第一步(补全并运行)”的校验,最后标记错题进行“第三步(针对性重复练习)”。

suny8088的博客 102

Android jetpack最佳总结和实践,字节跳动面试官

2)类图Lifecycle组件成员Lifecycle被定义成了抽象类,LifecycleOwner、LifecycleObserver被定义成了接口。组件(Activity、Fragment)实现了LifecycleOwner接口,该只有一个返回Lifecycle对象的方getLifecyle(): LifecycleRegistry。Lifecycle的内部类State标明状态、Event表示事件。

2301_79056696的博客 583

44位abogus补环境与纯算逆向分析

44位ab参数补环境与纯算加密流程逆向分析

吴秋霖的博客 2767

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

WrenAI 新手指南:3 条命令把自然语言变成可信的 SQL

让 AI 直接写 SQL 不难,难在写对。表名猜错、口径理解偏差、JOIN 方向反了,查出来的数字不敢用。WrenAI 解决的就是这件事:把自然语言问题变成经过治理的 text-to-SQL,在一个开放的语义层和上下文层上完成规划、校验与执行,支持 PostgreSQL、MySQL、BigQuery、Snowflake、DuckDB 等 20 多种数据源。整条链路跑在 `wren` 这个 Pyth

gitblog_00670的博客 842

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Android 高级工程师面试参考答案:架构设计、Jetpack 与 Compose

本文探讨了Android开发中架构设计、Jetpack组件和Compose的实践要点。主要内容包括:MVVM相比MVP的优势在于集中管理页面状态,降低维护成本;ViewModel应作为状态协调者而非业务仓库;LiveData、StateFlow和SharedFlow需区分状态和事件;Jetpack组件解决特定复杂度问题;依赖注入提升代码可维护性;MVI适合复杂状态管理;Compose本质是声明式状态驱动UI;重组优化的核心是状态范围控制。文章强调架构选择应基于实际复杂度,而非盲目追随新技术。

帅次的博客 1522

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

面试官:作为一名Android开发者,连jetpack都不懂?你还是改行吧!

JetPack自2018年面世,迎来了它的第三个年头。但是在国内的受欢迎程度远远及不上国外,国内开发者一听到别人说JetPack,脑海中蹦出来的词就是“不成熟”、“不好用”、“调试有点难”,但是事实真的是如此嘛? 我们首先来看看JetPack在发布的时候的雄心壮志: 以上来自谷歌语录,总结起来就是: JetPack是一整套组件库 它能帮我们写出优质应用 它能让我们不需要去写毫无营养的样板代码 4. 它能规范我们的开发行为 接下来来看看JetPack组件库到底包含了哪些组件: 从上图我们

Android578的博客 418

阿里5次面试碰壁,100天深造-Jetpack架构组件从入门到精通,险获腾讯年薪45w+offer!

Jetpack是一套库、工具和指南,可以帮助开发者更轻松地编写优质应用。这些组件可以帮助开发者遵循最佳做法、让开发者摆脱编写样板代码的工作并简化复杂任务,以便开发者将精力集中放在所需的代码上。我见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。

2401_84132204的博客 1005

Android Jetpack从入门到精通,干货满满

11.1 Hilt - Jetpack 推荐的依赖注入类库11.2 Paging3 - 逐步加载和显示数据11.3 App Startup - 在应用启动时初始化组件11.5 更简单的动画 — core-animation 和 SeekableAnimatedVectorDrawable。

erhtre的博客 1452

安卓开发工程师技术栈与面试全攻略

移动开发领域中,安卓技术栈的演进始终围绕提升开发效率与用户体验展开。Kotlin作为官方推荐语言,其空安全特性和协程机制显著改善了代码健壮性和并发处理能力。Jetpack组件库通过模块化设计,为开发者提供了标准化解决方案,特别是在ViewModel和Room等组件的配合下,实现了数据持久化和生命周期管理的优雅处理。在工程实践层面,性能优化涉及冷启动加速、内存管理等多维度技巧,而算法能力如链表操作和动态规划则是面试中的常见考察点。对于正在准备安卓岗位的开发者,深入理解这些核心技术原理,并结合实际项目经验展示问

weixin_30768881的博客 273

2024年Android最新Android Jetpack组件之App Startup(1),android代码学习

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。

2401_84561736的博客 1041

如何快速上手Jetpack?Jetpack入门到精通再到(网易云)强化实战,Github已标星80K+

前言 金九银十的招聘旺季已经快接近尾声,相信有不少读者正在抓紧机会面试。 Android 市场已今非昔比。在过去,迫于招人的压力,应试者只需了解四大组件、视图、网络请求,即可谋得一份满意的工作。 现如今面向标准化开发已成现实,Jetpack 架构组件及标准化开发模式的确立,意味着Android开发已步入成熟阶段。熟练使用Jetpack也成为了招聘要求中必不可少的一项: 为什么要用Jetpack? 许多样板代码不再需要开发者手写,而是可以通过模版工具自动生成,在取缔繁杂耗时的重复工作的同时,避免因人工操作的

datian1234的博客 927

面试官:“会不会熟练使用Jetpack” 我:“......”

前言 金九银十的招聘旺季已经快接近尾声,相信有不少读者正在抓紧机会面试。 Android 市场已今非昔比。在过去,迫于招人的压力,应试者只需了解四大组件、视图、网络请求,即可谋得一份满意的工作。 现如今面向标准化开发已成现实,Jetpack 架构组件及标准化开发模式的确立,意味着Android开发已步入成熟阶段。熟练使用Jetpack也成为了招聘要求中必不可少的一项: 如何快速且熟练的使用Jetpack呢? Jetpack,它是一套组件、工具和指导;是谷歌正式推出的一整套用来构建APP开发的组件。 对于产

datian1234的博客 1976

Android 高级工程师面试:Jetpack 核心 近1年高频追问 20 题

本篇聚焦 Jetpack 核心组件考点,面向已完成 Kotlin 轨道的 Android 应用开发者与面试备战者。覆盖基础 7 题、进阶 7 题、核心 6 题,从 Lifecycle、ViewModel、LiveData 到 Navigation、Room 与协程整合。标准回答讲清机制考点;工程落地见每题追问第 1 条。

帅次的博客 574

不会吧!做Android开发还不会Jetpack架构组件?快来看看这篇文章吧!

1.Hilt - Jetpack 推荐的依赖注入类库2.Paging3 - 逐步加载和显示数据3.App Startup - 在应用启动时初始化组件5.更简单的动画 — core-animation 和 SeekableAnimatedVectorDrawable6.使用 Database Inspector 调试数据库。

2401_84545046的博客 597
上一篇: 如何应对 Android 面试官 -> 玩转 Jetpack Navigation
下一篇: 如何应对 Android 面试官 -> 玩转 Jetpack Paging
老A说
博客等级 码龄14年 1181粉丝 85原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值