一、启动速度:用户的第一印象

启动速度这件事,说白了就是用户对你的第一印象。我见过太多App功能做得不错,但一打开要等两三秒,用户直接就划走了。你想想看,在用户耐心只有几百毫秒的今天,启动慢就等于劝退。

我个人习惯把启动优化放在性能优化的第一位。为什么?因为这是用户感知最强烈的环节。你后台优化得再好,用户没等到你启动就关了,那一切白搭。

冷启动 vs 热启动

先搞清楚这两个概念。很多新手容易混淆,我刚开始做Android时也犯过这个错。

类型 定义 典型耗时 优化重点
冷启动 进程从零开始创建,Application重新初始化 500ms - 2000ms Application初始化、ContentProvider
热启动 进程已在后台,Activity重新创建 100ms - 300ms Activity生命周期、对象复用
温启动 进程存活但Activity被回收 200ms - 500ms 缓存恢复、懒加载

冷启动才是我们优化的主战场。热启动相对简单,因为进程已经存在,很多初始化工作已经做过了。我在项目中遇到过最极端的情况:冷启动耗时3.5秒,用户反馈说"这App是不是卡死了"。嗯,那段时间我每天都在跟启动速度死磕。

核心原则:冷启动优化的目标是把用户可见的启动时间压缩到500ms以内。超过这个阈值,用户流失率会明显上升。

二、Application初始化:别什么都往这里塞

我见过太多项目,Application的onCreate里洋洋洒洒写了上百行初始化代码。SDK初始化、数据库创建、配置文件加载、埋点上报……全堆在一起。这其实是个大坑。

为什么会这样?因为很多开发者觉得"反正启动时都要初始化,放一起省事"。但你想过没有,用户可能根本不需要你那些第三方SDK的功能,你却让他在启动时白白等待。

初始化分类策略

我个人习惯把初始化任务分成三类:

  • 必须同步初始化: 比如崩溃收集SDK、日志系统。这些不初始化,后面出问题没法追踪。
  • 可以异步初始化: 比如网络库、图片加载库。这些可以放到子线程去做。
  • 可以延迟初始化: 比如推送SDK、广告SDK。等用户进入主界面后再慢慢加载。
// 错误示范:所有初始化都堆在Application.onCreate里
@Override
public void onCreate() {
    super.onCreate();
    initCrashReport();      // 必须
    initNetwork();          // 可以异步
    initImageLoader();      // 可以异步
    initPushSDK();          // 可以延迟
    initAdSDK();            // 可以延迟
    initDatabase();         // 可以异步
    initConfig();           // 必须
}

// 正确做法:分类处理
@Override
public void onCreate() {
    super.onCreate();
    // 必须同步的
    initCrashReport();
    initConfig();
    
    // 异步初始化
    new Thread(() -> {
        initNetwork();
        initImageLoader();
        initDatabase();
    }).start();
    
    // 延迟初始化 - 等主界面加载完成后
    getLifecycle().addObserver(new DefaultLifecycleObserver() {
        @Override
        public void onStart(@NonNull LifecycleOwner owner) {
            initPushSDK();
            initAdSDK();
        }
    });
}

避坑指南:我曾经把一个数据库初始化放到了异步线程,结果某个页面启动时直接崩溃——因为数据库还没准备好。后来我加了一个"初始化完成回调",所有依赖数据库的操作都等回调后再执行。这个坑,你们别踩。

三、ContentProvider启动链:隐藏的耗时大户

很多人不知道,ContentProvider的初始化是在Application.onCreate之前完成的。这意味着什么?你每个ContentProvider的onCreate都会拖慢启动速度。

我记得有一次排查启动慢的问题,发现一个第三方SDK注册了ContentProvider,它的onCreate里居然做了网络请求。这一下就多花了800ms。你想想看,用户还没看到界面,先等一个网络请求完成,这合理吗?

ContentProvider启动顺序

Android系统在启动进程时,会按以下顺序执行:

  1. 创建Application对象
  2. 按manifest中注册顺序初始化ContentProvider
  3. 执行Application.onCreate
  4. 创建MainActivity

也就是说,所有ContentProvider的初始化都在Application.onCreate之前。如果你有多个ContentProvider,它们会串行执行。一个慢,全部跟着慢。

注意:每个ContentProvider的onCreate默认在主线程执行。如果你的ContentProvider里有耗时操作,直接卡住主线程。我曾经见过一个项目,因为ContentProvider里做了文件IO,启动直接卡了1.2秒。

优化方案

  • 合并ContentProvider: 如果多个ContentProvider属于同一个模块,考虑合并成一个。
  • 使用Startup库: Google官方提供的解决方案,可以管理初始化依赖关系。
  • 移除不必要的ContentProvider: 有些第三方SDK的ContentProvider其实可以手动初始化。

四、启动任务依赖图:用Startup库理清关系

当你的项目越来越大,初始化任务会越来越多。它们之间还有依赖关系——比如网络库必须在日志库之后初始化,图片库必须在网络库之后初始化。这时候就需要一个任务依赖图来管理。

Google的Startup库就是干这个的。它让你可以声明每个初始化任务的依赖关系,然后自动按拓扑排序执行。说白了,就是帮你把一团乱麻理清楚。

核心思想:把初始化任务看作有向无环图中的节点,依赖关系就是边。Startup库会找到最优的执行顺序,能并行的就并行,不能并行的就串行。

Startup库使用示例

// 1. 定义初始化任务
class NetworkInitializer : Initializer<NetworkManager> {
    override fun create(context: Context): NetworkManager {
        return NetworkManager.init(context)
    }
    
    override fun dependencies(): List<Class<out Initializer<*>>> {
        // 依赖日志库先初始化
        return listOf(LogInitializer::class.java)
    }
}

class LogInitializer : Initializer<LogManager> {
    override fun create(context: Context): LogManager {
        return LogManager.init(context)
    }
    
    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList() // 没有依赖
    }
}

// 2. 在AndroidManifest中配置
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <meta-data
        android:name="com.example.NetworkInitializer"
        android:value="androidx.startup" />
</provider>

这样配置后,Startup库会自动分析依赖关系。LogInitializer没有依赖,会先执行。NetworkInitializer依赖LogInitializer,等日志库初始化完再执行。如果有多个没有依赖关系的任务,它们会并行执行。

个人经验:我建议把所有初始化任务都迁移到Startup库管理。刚开始可能觉得麻烦,但项目大了之后,你会发现依赖关系清晰多了。我曾经接手过一个项目,初始化代码散落在各个地方,用了Startup库之后,整个启动流程一目了然。

五、启动优化实战流程图

下面这张图是我总结的启动优化整体流程。从冷启动开始,到Application初始化,再到ContentProvider和Startup库,每一步都有对应的优化策略。

启动优化核心流程 冷启动开始 Application初始化(分类策略) ContentProvider启动链优化 Startup库(任务依赖图) 启动速度达标(<500ms) 同步/异步/延迟 合并/移除/手动初始化 拓扑排序/并行执行 分类初始化 减少主线程耗时 依赖关系清晰

这张图把整个启动优化的脉络理清楚了。从冷启动入口开始,经过Application初始化、ContentProvider优化,最后用Startup库管理依赖关系。每一步都有对应的优化手段,环环相扣。

总结一下:启动优化不是一蹴而就的事。我建议你先用工具(比如Systrace、Profiler)测量当前启动耗时,找到瓶颈点,然后针对性地优化。别一上来就大刀阔斧地改,容易出问题。一步一步来,每次优化后都验证效果,这样最稳妥。

嗯,启动速度这块内容就讲到这里。记住一个原则:用户等不起,你的App必须快。把那些不必要的初始化往后挪,能异步的就异步,能用Startup库管理依赖的就别自己手写。这些经验,都是我踩过坑之后总结出来的。

公众号:蓝海资料掘金营,微信deep3321