启动速度优化:冷启动与热启动流程
启动速度,是用户对App的第一印象。我见过太多功能强大的App,就因为启动慢了半秒,用户直接划走。说白了,启动优化就是跟用户耐心赛跑。
先搞清楚两个概念:冷启动和热启动。它们的区别,决定了你优化的方向。
冷启动 vs 热启动:到底差在哪?
冷启动,是进程从无到有的过程。用户点图标,系统创建进程,加载Application,创建MainActivity,渲染第一帧。整个过程,用户盯着白屏等。
热启动呢?App已经在后台,Activity还在内存里。用户切回来,系统直接把Activity拉到前台。快,非常快,几乎感觉不到延迟。
我遇到过不少团队,只优化冷启动,忽略了热启动场景下的资源重初始化问题。其实两者都要管,只是侧重点不同。
| 对比项 | 冷启动 | 热启动 |
|---|---|---|
| 进程是否存在 | 否 | 是 |
| Application是否重建 | 是 | 否 |
| Activity是否重建 | 是 | 否(直接复用) |
| 优化重点 | Application + 首屏Activity | 避免内存泄漏导致重建 |
核心原则:冷启动优化的是「从0到1」的过程,热启动优化的是「别让用户等」的体验。
Application与Activity生命周期优化
Application的onCreate(),是冷启动的「咽喉要道」。你在这里干了多少活,用户就得等多久。
我个人习惯,把Application的初始化分成三类:
- 必须同步的:崩溃收集、日志系统。这些不初始化,后面出问题没法查。
- 可以异步的:网络库、图片库、数据库。用IdleHandler或者线程池延迟加载。
- 可以懒加载的:推送SDK、统计SDK、广告SDK。等用户进入首页再初始化。
举个例子,我之前接手一个项目,Application里一口气初始化了12个第三方SDK。冷启动耗时直接飙到3秒。后来我改成按需加载,首屏只初始化3个核心库,剩下的丢到子线程。启动时间降到1.2秒。
小技巧:用ContentProvider的初始化顺序来「偷时间」。系统会在Application.onCreate()之前调用ContentProvider的onCreate()。你可以把一些轻量初始化放到这里,但别滥用,否则会拖慢ContentProvider的加载。
Activity的生命周期优化,重点在onCreate()和onResume()。onCreate()里别做耗时操作,比如解析大JSON、加载本地数据库。把这些放到子线程,或者等界面渲染完再处理。
我记得有一次,一个同事在Activity的onCreate()里直接读取了一个2MB的配置文件。结果每次冷启动,界面都要卡顿1秒多。后来改成异步读取,配合LiveData更新UI,问题就解决了。
启动黑白屏处理
黑白屏,说白了就是系统在加载Application和Activity时,还没渲染出你的布局,只能显示一个空白窗口。这个窗口的颜色,取决于你的主题。
为什么会这样?因为系统需要先加载资源、解析布局、测量绘制。这个过程需要时间。如果什么都不做,用户看到的就是白屏或黑屏。
解决方案有两种:
- 方法一:设置透明主题。让启动窗口透明,用户感觉不到白屏。但缺点是,从点击图标到界面出现,会有「延迟感」。
- 方法二:设置闪屏主题。给启动窗口设置一张背景图,或者品牌色。用户点击图标,立刻看到这张图,等Activity加载完成后,再无缝切换到真实界面。
我个人更推荐第二种。用户体验更好,而且能传递品牌信息。具体做法:
// styles.xml
<style name="AppTheme.Splash" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/splash_bg</item>
<item name="android:windowFullscreen">true</item>
</style>
// AndroidManifest.xml
<activity
android:name=".MainActivity"
android:theme="@style/AppTheme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
// MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
// 在super.onCreate之前切换回正常主题
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
注意:闪屏图不要太大。我见过有人用了一张1080p的图片做背景,结果加载图片本身就成了瓶颈。建议用矢量图或者纯色背景,尺寸控制在50KB以内。
ContentProvider初始化优化
ContentProvider的onCreate(),是在Application.onCreate()之前调用的。这意味着,如果你在ContentProvider里做了耗时操作,会直接拖慢整个启动流程。
我曾经在一个项目里发现,启动时间莫名其妙多了800ms。查了半天,发现是一个第三方库的ContentProvider,在onCreate()里初始化了数据库连接池。这个操作本身没问题,但放在ContentProvider里,就成了启动瓶颈。
优化方案:
- 能不用的ContentProvider就别用。很多第三方SDK都提供了禁用ContentProvider初始化的方式。
- 如果必须用,把耗时操作放到子线程。ContentProvider的onCreate()是在主线程执行的,但你可以把真正的初始化丢到子线程。
- 使用懒加载。等真正需要访问ContentProvider时,再初始化内部资源。
举个例子,如果你自己写了一个ContentProvider,可以这样优化:
public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
// 只做轻量初始化
mInitFlag = new AtomicBoolean(false);
// 把耗时操作丢到子线程
new Thread(() -> {
// 初始化数据库连接池
DatabasePool.init(getContext());
mInitFlag.set(true);
}).start();
return true;
}
@Override
public Cursor query(...) {
// 如果还没初始化完成,等待
while (!mInitFlag.get()) {
// 可以加一个超时机制
}
return super.query(...);
}
}
避坑指南:我曾经在ContentProvider里直接调用了SharedPreferences,结果导致死锁。因为SharedPreferences的初始化依赖Context,而ContentProvider的Context在onCreate()时可能还没完全准备好。建议用ApplicationContext替代。
知识体系总览
下面这张图,概括了启动优化的核心脉络。你可以把它当作检查清单,每次做启动优化时对照着看。
启动优化没有银弹。每个App的瓶颈都不一样。我建议你先用Trace工具抓一下启动阶段的耗时,找到真正的瓶颈,再对症下药。别一上来就照着网上的方案全改一遍,那样反而容易出问题。
嗯,启动优化这块,说白了就是跟系统抢时间。你抢回来的每一毫秒,都是用户的耐心。