优化App启动时间?startup-coroutine是什么?

那个让我头疼的启动问题

事情要从我们公司的新项目说起。那时候我们的应用集成了好多第三方 SDK:推送、统计、地图、IM、广告...你懂的,现在的 App 不接十几个 SDK 都不好意思上线。

由于SDK初始化时间过长,导致Splash Activity首屏一直卡在白屏界面,还需要编写各种xml背景图进行填充,很是头疼。

最开始我的 Application 是这样的:

kotlin 复制代码
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        
        // 一堆初始化代码
        Push.init(this)      // 推送
        Analytics.init(this) // 统计
        Map.init(this)       // 地图
        IM.init(this)        // 即时通讯
        Ad.init(this)        // 广告
        // ... 还有更多
        
        // 自己的业务初始化
        initDatabase()
        initConfig()
        initUser()
    }
}

结果就是:

  • 用户点开 App 要等 3-5 秒才能看到界面
  • 所有初始化都在主线程排队执行,一个卡住后面全等着
  • 代码越来越乱,新同事根本不敢动这块代码
  • 测试经常报 ANR(应用无响应)

寻找解决方案的折腾过程

第一站:多线程方案

我首先想到的是用多线程:

kotlin 复制代码
val thread1 = thread { Push.init(this) } //kotlin函数
val thread2 = thread { Analytics.init(this) }
// 启动所有线程...

结果发现问题更多:

  • 线程创建开销大
  • 依赖关系处理起来特别麻烦
  • 异常处理很头疼
  • 代码变得超级复杂

第二站:Jetpack AppStartup

然后我发现了 Google 的 AppStartup,心想官方出品应该很靠谱吧!

用了一段时间发现:

  • 它只是把初始化代码从 Application 移到了各个 Initializer 里
  • 关键问题:所有初始化还是在主线程执行的!
  • 启动时间根本没减少,只是代码看起来整齐了点
  • 复杂的依赖关系写起来还是很麻烦

AppStartup 更像是一个代码组织工具,而不是性能优化工具。

灵光一现:为什么不用协程?

就在我快要放弃的时候,突然想到:我们项目已经在用 Kotlin 协程了,为什么不用协程来解决这个问题呢?

协程的轻量级、灵活的线程调度、简洁的异步表达,不正是我们需要的吗?

于是,我开始动手写 startup-coroutine

框架设计

核心想法:把初始化当成有依赖关系的任务

借鉴App Startup,但是它是串行初始化,我呢允许并行初始化。

我想象中的理想状态是:

  • 每个初始化任务都是独立的
  • 可以声明它依赖哪些其他任务
  • 没有依赖关系的任务可以并行执行
  • 使用拓扑排序自动处理依赖关系,按正确顺序执行

于是就有了 Initializer 接口:

kotlin 复制代码
interface Initializer<T> {
    suspend fun init(app: Application, provider: DependenciesProvider): T
    fun dependencies(): List<KClass<out Initializer<*>>> = emptyList()
}

线程调度的灵活控制

我提供了几种预设的线程策略:

  • AllIO:全在 IO 线程,适合耗时任务
  • ExecuteOnIO:IO 线程执行,主线程创建框架
  • AllMain:全在主线程,只适合轻量任务
  • Default:默认推荐,框架在 IO 线程,任务在主线程

这样大家可以根据自己项目的实际情况选择。

由于协程的suspend函数可以灵活的切换调度线程,所以可以全部在Main线程执行,然后具体的处理业务自由使用withContext(Dispatchers.IO){...}来切换.

BaseActivity 的封装

这个设计源于我们遇到的一个真实问题:

问题场景: 用户正在使用 App,突然来了个电话或者切换到其他应用,这时候系统可能因为内存不足把我们的应用进程给杀掉了。当用户再切回来时,系统会重新创建 Application 和用户最后看到的 Activity。

这时候就出问题了: Application构建完成后,执行Startup的初始化,由于Startup是协程框架,它不阻塞UI,于是还没有等待Startup初始化结束Activity就开始创建了,此时Activity 在 onCreate 时可能会使用那些还没重新初始化的 SDK,结果就是各种空指针异常和崩溃。

我的解决方案: 在 BaseActivity 里统一监听初始化状态。

kotlin 复制代码
abstract class BaseActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        observeStartup() // 关键的一行!
    }
    
    protected open fun observeStartup() {
        Startup.observe(this) { result ->
            when (result) {
                StartupResult.Idle -> showLoading() // 还没初始化,显示加载
                StartupResult.Success -> {
                    hideLoading()
                    onInitFinished() // 初始化完成,继续正常流程
                }
                is StartupResult.Failure -> {
                    hideLoading()
                    // 处理初始化失败
                }
            }
        }
    }
}

这样设计的好处:

  1. 进程重启无忧:即使进程被杀死重建,也能保证 SDK 先初始化再使用
  2. 统一加载状态:所有页面都有统一的加载体验
  3. 错误统一处理:初始化失败时有统一的错误处理机制
  4. 代码复用:每个 Activity 都不用自己写初始化监听逻辑

Startup.observe内部是使用LiveData实现的.目的是为了每个页面监听的时候都可以拿到初始化数据信息.

kotlin 复制代码
open class Startup private constructor(
    private val implementation: IStartup
) : IStartup {
    companion object {

        private val _isInitialized = MutableLiveData<StartupResult>(StartupResult.Idle)

        fun observe(owner: LifecycleOwner, observer: Observer<StartupResult>) {
            _isInitialized.observe(owner, observer)
        }

        fun isInitialized() = _isInitialized.value != StartupResult.Idle

        fun initializedResult() = _isInitialized.value

        fun reset() {
            _isInitialized.postValue(StartupResult.Idle)
        }
        internal fun markInitializedResult(result: StartupResult) {
            _isInitialized.postValue(result)
        }
    }

隐私协议处理的巧妙设计

现在隐私合规要求很严格,很多 SDK 必须在用户同意隐私协议后才能初始化。我的框架也很好的解决了这个问题:

kotlin 复制代码
class MyApp : Application() {

    companion object {
        private lateinit var startup: Startup

        fun startInit() {
            startup.start()
        }
    }

    override fun onCreate() {
        super.onCreate()
        //轻量级数据存储推荐直接初始化,用于进行判断逻辑.
        SpUtils.init(this)

        //构建初始化任务列表.
        val initializer = listOf(
            AdMobInit(),
            AppConfigInit(),
            FlutterEngineInit(),
            FistInitializer(),
            IMInit(),
            MapSdkInit(),
            ExceptionInit()
        )
        //构建startup
        startup = Startup.Builder(this)
            .setDispatchers(StartupDispatchers.AllIO)
            .setDebug(true)
            .add(initializer)
            .build()

        //推荐!!!
        //如果App跳过了手动初始化,就必须做判断,只要条件允许第二次初始化的时候
        //一定在Application中直接执行.
        //这里涉及到了一些进程重启逻辑.
        if (SpUtils.getBoolean("isAgreePrivacy",false)) {
            startInit()
        }
    }

}

class SplashActivity : BaseActivity() {
    
    private fun checkPrivacy() {
        if (用户已同意隐私协议) {
            // 开始初始化
            MyApp.startInit()
        } else {
            // 显示隐私协议弹窗
            showPrivacyDialog {
                // 用户同意后开始初始化
                MyApp.startInit()
            }
        }
    }
}

实际效果怎么样?

用了 startup-coroutine 之后:

  • 启动时间:从 3-5 秒优化到 1-2 秒
  • 代码可维护性:新同事也能轻松添加新的初始化任务
  • 稳定性:再也没有因为进程重启导致的崩溃
  • 开发体验:调试时可以清楚看到每个任务的执行时间和依赖关系

怎么集成使用?

集成超级简单:

  1. 添加依赖:
kotlin 复制代码
implementation("com.github.Dboy233:startup-coroutine:最新版本")
  1. 定义初始化任务:
kotlin 复制代码
class MyInitializer : Initializer<Unit> {
    //如果没有依赖可不重写此方法
    override fun dependencies() = listOf(OtherInitializer::class)
    override suspend fun init(app: Application, provider: DependenciesProvider) {
        // 你的初始化代码
    }
}
  1. 在 Application 中配置:
kotlin 复制代码
val startup = Startup.Builder(this)
    .add(MyInitializer())
    .setDispatchers(StartupDispatchers.ExecuteOnIO)
    .build()
  1. 让 Activity 继承 BaseActivity(或者自己实现初始化监听)

实际应用日志

text 复制代码
--- Startup Coroutine Dependency Graph ---
    
    FlutterEngineInit
    FistInitializer
    ExceptionInit
    AppConfigInit
      └─ FistInitializer
    AdMobInit
      └─ AppConfigInit
    IMInit
      └─ AppConfigInit
    MapSdkInit
      └─ AppConfigInit
    
----------------------------------------


--- Startup Coroutine Performance Summary ---

>> Total Time: 2538ms  |  Status: FAILED
>> Dispatchers Mode: All-IO

>> Individual Task Durations:
   - FlutterEngineInit  |  2503ms  |  Thread: DefaultDispatcher-worker-5
   - IMInit             |  2000ms  |  Thread: DefaultDispatcher-worker-5
   - AdMobInit          |  1832ms  |  Thread: DefaultDispatcher-worker-4
   - MapSdkInit         |  601ms   |  Thread: DefaultDispatcher-worker-4
   - AppConfigInit      |  102ms   |  Thread: DefaultDispatcher-worker-5
   - FistInitializer    |  0ms     |  Thread: DefaultDispatcher-worker-4
   - ExceptionInit      |  -1ms    |  Thread: Error
>> Task time is sum  : 7037 ms

打印初始化结果,可以看到使用协程框架后整体初始化从7秒优化到了1坤秒(2.5秒)。

效果还是立竿见影的,关键是它的初始化不占用UI渲染时间,让UI过度不出现卡顿白屏。

如果你在Log中看到了两次日志输出,不要紧张,它不是执行了两次,而仅仅只是打印两次。

vbscript 复制代码
Log.i("StartupCoroutine", logContent.toString())
println(logContent.toString())

因为Test阶段的时候Log是不输出内容的,所以使用println再打印了一遍.你只需要过滤StartupCoroutine日志即可。

写在最后

开发 startup-coroutine 的过程让我深刻体会到:好的框架不是凭空想象出来的,而是从真实的业务痛点中生长出来的。

我现在把这个框架开源出来,一方面是希望帮助到有同样问题的开发者,另一方面也是想听听大家的想法,看看能不能一起把它做得更好。

如果你也在为应用启动优化而烦恼,不妨试试 startup-coroutine。如果你有任何建议或者发现了什么问题,欢迎来 GitHub 给我提 Issue 或者 PR。

项目地址: https://github.com/Dboy233/startup-coroutine

希望我的这个小工具能帮到你,也期待听到你的使用反馈!

相关推荐
JienDa2 小时前
JienDa聊PHP:CSDN博客仿站实战中PHP框架的协同架构方略
java·架构·php
ZouZou老师3 小时前
FFmpeg性能优化经典案例
性能优化·ffmpeg
刘一说3 小时前
Nacos 与 Spring Cloud Alibaba 集成详解:依赖、配置、实战与避坑指南
spring boot·spring cloud·微服务·架构
七夜zippoe4 小时前
使用Ollama在消费级硬件上运行大模型:从环境配置到企业级实战
性能优化·大模型·模型量化·ollama
周杰伦_Jay5 小时前
【Go 语言主流 Web】 框架详细解析
开发语言·后端·微服务·架构·golang
闲人编程5 小时前
Django微服务架构:单体应用拆分解耦实践
微服务·架构·消息队列·django·api·通信·codecapsule
颜颜yan_5 小时前
基于CANN多Stream异步执行的智能推理管道:突破传统串行瓶颈
运维·架构·stream·昇腾·cann
2401_861277556 小时前
Web应用程序、服务器、数据库性能测试工具Jemeter使用方法与举例说明
性能优化·压力测试
吴法刚6 小时前
Gemini cli 源码分析之-Agent分析-Agent架构系统分析
架构·agent·ai编程·gemini