启动框架 Anchors接入和分析

文章介绍了Android应用冷启动的优化策略,重点在于Application的onCreate方法以及主线程的调度。通过构建任务图,区分必要且耗时的任务,使用异步执行或延迟启动来提高启动速度。AnchorsManager和BaseTask类用于管理任务执行顺序,确保锚点任务优先执行,其余任务在主线程空闲时处理,从而减少启动时间。经测试,该方法可实现约30%的启动速度提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:

https://juejin.cn/post/6844904128443858958

一个更贴近 android 场景的启动框架 | Anchors_com.effective.android:anchors-优快云博客

从哪下手

整个冷启动过程中,系统方法我们无法进行优化,主要需要优化的是系统暴露出来的一些生命周期方法,从Application的attachBaseContext开始,到启动页Activity或者首页Activity的onResume结束,甚至直到Activity的界面绘制结束。优化的目的就是使这个过程尽量快,不要出现卡顿。其中最重要的就是Application中的onCreate方法了。

初始化任务分析

  • 将初始化任务按两个维度区分:是否耗时&是否必要。必要且耗时的,考虑使用其他线程来初始化(比如Tinker初始化),不必要的都延迟启动。

初始任务的状态:

采用任务优化后的状态:

UML类图

使用方式

  • 要执行一个任务,需要自定义Task(继承BaseTask)。

  • 使用自定义工厂创建对应的Task,工厂继承自Project.TaskFactory。

  • 使用Project.Builder创建Task图。

  • 使用AnchorsManager的start方法启动Task(BaseTask的start方法是protected修饰的)

  • 在Demo中,在Application的onCreate方法中调用以下代码启动整个过程。

MainProcessStarter.start(checkPermission);
  • 更多注释都在代码中

BaseTask
  • BaseTask是一个任务单元,其中定义了前置任务dependTasks与后置任务behindTasks,每个Task会有优先级,锚点任务优先级是最高的,优先级主要用来排序。

  • 当向taskB中加入一个前置条件taskA时,taskA会被加入taskB的前置任务dependTasks,同时taskB也会被加入taskA的后置任务behindTasks中。

  • 当向taskB中加入一个后置条件taskC时,taskC会被加入taskB的后置任务behindTasks,同时taskB也会被加入taskC的前置任务dependTasks中。

  • 当一个task执行完成时,会将自己的后置任务behindTasks逐个启动,此时后置的任务会判断自己还有没有前置的任务,如果有就不执行,没有才执行。

Project
  • Project的存在意义在于他的Builder,他能构造一个不会产生环的task图。

AnchorsManager
  • BaseTask是不能直接调用start方法执行的,必须通过AnchorsManager的start才能执行,它定义了如何正确地执行一个task。

  • 从start方法可以看出,锚点任务都会在start所在方法内执行完(比如说我在onCreate中调用了start,则锚点任务都会在onCreate方法中执行)

AnchorsRuntime
  • 会对整个执行过程记录信息,并打印出log

  • 管理着一个线程池,用于执行异步任务

同步与阻塞控制

组成的任务链中调用 TaskDispatcher.await(),内部通过 CountDownLatch 阻塞,直到所有任务完成。

//oncreate中
        // 1. 初始化任务调度器
        val dispatcher = TaskDispatcher.init(this)
        // 2. 添加所有任务(包括锚点任务和依赖任务)
        dispatcher.addTask(AnchorTaskA())
            .addTask(TaskB())
            .addTask(TaskC()) // 其他任务...
        // 3. 启动任务调度(异步执行)
        dispatcher.start()
        // 4. 阻塞主线程,等待所有任务完成
        dispatcher.await() // 内部使用 CountDownLatch 实现,在这阻塞,尝试等待10s


class TaskDispatcher {
    private val latch = CountDownLatch(taskCount)

    fun await() {
        // 阻塞主线程,直到所有任务完成,即countdown减到0之后,await就被释放锁
        latch.await(10, TimeUnit.SECONDS) // 超时保护
    }

    // 每个任务完成后调用,countdown减到0之后释放锁
    private fun onTaskFinished() {
        latch.countDown()
    }
}

为什么会变快

首先看到每个task的启动方法start

/**
 * 调用start启动当前task
 */
protected synchronized void start() {
    if (mState != TaskState.IDLE) {
        throw new RuntimeException("can no run task " + getId() + " again!");
    }
    toStart();
    setExecuteTime(System.currentTimeMillis());
    AnchorsRuntime.executeTask(this);
}
复制代码

最终都会调用到AnchorsRuntime.executeTask(this);,如下:

/**
 * 同步使用handler发送至主线程,异步使用线程池
 * @param task 任务
 */
static void executeTask(BaseTask task) {
    if (task.isAsyncTask()) {
        S_POOL.executeTask(task);
    } else {
        if (AnchorsRuntime.hasAnchorTasks()) {
            AnchorsRuntime.addRunTasks(task);
        } else {
            sHandler.post(task);
        }
    }
}
复制代码

首先异步执行当然会减少启动时间,那如果全部都是同步执行呢?经过测试,启动过程也是变快了的。最终启动变快的原因就在于这个sHandler.post(task);(sHandler的Looper是主线程的Looper)。当每个task执行时,会post到主线程的消息队列的末尾,相当于不是立即执行,而是等待主线程现有的任务执行完了才执行,相当于给App启动“让路”。除了锚点任务会优先保证执行,任务链后面的task无法保证任务链中的同步关系,因为都是通过post;

systrace分析

在Demo中,START_TASK_3、START_FIRST_OF_ALL是锚点任务,START_TASK_3 depend on START_CONFIG_PRELOAD,任务图是在Application的onCreate中执行的,所以这三个任务会在onCreate中执行完毕,如下图:

继续往后看,START_TASK_2在启动页的Resume之后执行了

因为它post的时候,ActivityThread.H(主线程Handler)已经把LaunchActivity、ResumeActivity的message发送给了主线程任务队列。

最后这张图是剩下的任务执行过程,可以参看Demo的代码进行比对。Demo中分了Project1与Project2,前者是不需要申请权限的,后者需要,在他们之间有一个AwaitPermStartTask,可以阻塞整个任务图的执行,等待用户授权后在继续执行需要授权的任务(从图中的p2_start开始)。具体原理是使用CountDownLatch进行阻塞,使用RxBus进行回调。

从整体来看,本来全在Application的onCreate中执行的任务大部分都后移了,App启动的任务会被稍微“提前”,对于部分任务,更是在MainAcitivty启动之后才执行的,所以达到了优化的效果。但是这种方法也会有缺点,这些后移的任务如果需要在主线程执行,可能会影响到界面的绘制,造成卡顿,可以考虑使用MessageQueue.IdleHandler,在主线程空闲的时候执行剩下的任务。

用数据说话

最后,经过测试Demo,启动速度从900ms多加速到650ms左右,大约提速30%(从Application的attachBaseContext开始计时,直到MainActivity的onWindowFocusChanged)(ps:这里的耗时都是我自定义的,所以真正的优化效果还要看具体情况)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值