30、Logcat 实战案例二:应用冷启动优化中的日志定位与修复

冷启动优化,说白了就是让用户点开 App 图标后,能尽快看到内容。我见过太多 App 启动时黑屏好几秒,用户早就不耐烦地划走了。今天咱们就通过一个真实案例,看看怎么用 Logcat 把启动慢的“罪魁祸首”揪出来。

冷启动的“黄金三秒”

我个人习惯把冷启动分成三个阶段:

  • 阶段一:进程创建到 Application 初始化 —— 系统加载类、创建 Application 对象、执行 attachBaseContext 和 onCreate。
  • 阶段二:Activity 创建到布局加载 —— 创建 MainActivity、setContentView、inflate 布局。
  • 阶段三:首帧渲染到用户可交互 —— 测量、布局、绘制,以及首次数据加载。

每个阶段都有对应的 Logcat 日志。你想想看,如果不知道哪个阶段慢,优化就是瞎蒙。

核心思路: 用 Logcat 抓取 Displayed 时间,再结合 Trace 日志定位具体耗时方法。

第一步:抓取 Displayed 日志

冷启动完成后,系统会输出一条关键日志:

adb logcat -s ActivityManager:I | grep "Displayed"

输出示例:

2025-03-20 10:15:23.456 I/ActivityManager( 1234): Displayed com.example.app/.MainActivity: +2s350ms

这个 +2s350ms 就是总启动时间。我在项目中遇到过,某次优化前这个数字是 4.8 秒,优化后降到了 1.2 秒。怎么做到的?往下看。

小技巧: 每次冷启动前先执行 adb shell am force-stop com.example.app 杀掉进程,确保是真正的冷启动。

第二步:用 Trace 日志定位“谁在拖后腿”

光知道总时间没用,得知道时间花在哪。我推荐用 systraceperfetto 抓取更细粒度的日志。但如果你手边只有 Logcat,也有办法。

在 Application 的 onCreateattachBaseContext 里手动打点:

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        long start = System.currentTimeMillis();
        // 初始化 SDK
        initSDKs();
        Log.d("AppStartup", "SDK init cost: " + (System.currentTimeMillis() - start) + "ms");

        start = System.currentTimeMillis();
        // 加载配置
        loadConfig();
        Log.d("AppStartup", "Config load cost: " + (System.currentTimeMillis() - start) + "ms");
    }
}

然后过滤日志:

adb logcat -s AppStartup

输出:

D/AppStartup( 5678): SDK init cost: 1200ms
D/AppStartup( 5678): Config load cost: 800ms

嗯,这里要注意。SDK 初始化花了 1.2 秒,配置加载花了 0.8 秒,加起来就 2 秒了。这明显有问题。

第三步:分析日志,找到优化点

我曾经遇到一个案例,某个第三方 SDK 的初始化里做了网络请求,而且是在主线程。Logcat 里能看到明显的 ANR 警告:

W/ActivityManager( 1234): ANR in com.example.app
W/ActivityManager( 1234): Reason: Input dispatching timed out
W/ActivityManager( 1234): Load: 12.5 / 8.3 / 6.2
W/ActivityManager( 1234): CPU usage from 0ms to 5000ms later:
W/ActivityManager( 1234):   100% 5678: com.example.app (user 50%, kernel 50%)

看到 100% CPUInput dispatching timed out,基本可以断定主线程被阻塞了。再结合刚才的 AppStartup 日志,锁定是 SDK 初始化里的网络请求导致的。

注意: 主线程里不要做任何 I/O 操作,包括网络请求、数据库读写、SharedPreferences 的 commit(用 apply 替代)。这是冷启动优化的第一铁律。

第四步:修复与验证

修复方案很简单:把耗时的初始化操作丢到子线程,或者延迟加载。比如:

new Thread(() -> {
    initSDKs();  // 子线程初始化
}).start();

或者用 IdleHandler 在空闲时加载:

Looper.myLooper().getQueue().addIdleHandler(() -> {
    initSDKs();
    return false;  // 只执行一次
});

修复后再次抓取 Displayed 日志:

2025-03-20 10:16:45.789 I/ActivityManager( 1234): Displayed com.example.app/.MainActivity: +1s120ms

从 2.35 秒降到了 1.12 秒,效果立竿见影。

核心知识体系

我把冷启动优化的日志定位逻辑画成了流程图,方便你理解:

冷启动优化日志定位流程 抓取 Displayed 日志 手动打点 + Trace 日志 分析耗时模块 主线程 I/O 阻塞 布局过度绘制

常见问题与避坑指南

问题 日志特征 解决方案
主线程网络请求 ANR 日志 + CPU 100% 异步化或延迟加载
布局加载过慢 Displayed 时间长,但 Application 日志正常 使用 AsyncLayoutInflater 或减少布局层级
ContentProvider 初始化 Application 的 onCreate 之前就有耗时日志 移除不必要的 ContentProvider 或使用懒加载
SharedPreferences 写入 频繁 commit 导致主线程卡顿 改用 apply 或 MMKV
我的经验: 曾经有个项目,冷启动一直 3 秒以上,怎么查都查不到原因。后来用 adb shell dumpsys activity processes 一看,发现系统在启动时加载了十几个 ContentProvider,每个都要做数据库迁移。去掉几个不必要的 Provider 后,启动时间直接砍半。

说白了,冷启动优化就是一场“日志侦探游戏”。你不需要猜,Logcat 会告诉你真相。关键是知道看哪里、怎么看。掌握了 Displayed 日志和手动打点这两招,大部分启动慢的问题都能迎刃而解。


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