APP的启动方式
应用的启动方式分为三种,分别是冷启动、温启动和热启动。
冷启动:应用进程完全不存在时的启动(首次启动或进程被系统回收后),主要是从Luncher点击APP图标后,通过SystemServer去调用Zygote进程fork出一个新的APP进程。期间还实际到初始化Application、AMS初始化、binder通信以及Activity的加载等行为。
温启动:部分应用资源已缓存,但需重建 Activity 生命周期(如返回后台后重新打开)。优化点主要在加速 Activity 重建与数据恢复,避免冗余初始化。
热启动:应用进程仍驻留内存,直接唤醒至前台(如切换回最近使用的应用),在这个过程中我们主要需要缓存Activity的数据,保持应用状态,减少UI刷新的开销。
APP的启动优化
APP的启动优化我们一般谈论的就是冷启动的优化方案,其中查看启动时间的话我们也可以执行下面的命令。
1.adb命令查看
adb shell am start -S -W [packageName]/[activityName]
2.日志过滤
-
reportFullyDrawn()
是 Android 提供的 API,用于主动通知系统应用已完成所有关键渲染和初始化,系统会记录从冷启动开始到此方法调用的时间(TTFD,完全显示所用时间)12。 - 与
onWindowFocusChanged()
不同,此方法标记的是用户可完全交互的终点,而非首帧绘制完成13。
-
-
日志输出
- 调用后会在
Logcat
中生成ActivityTaskManager
日志,包含totalTime
字段(单位:毫秒),通过Logcat
过滤ActivityTaskManager
标签,查看FULLY DRAWN
日志中的totalTime
字段
- 调用后会在
3.Trace分析
在 Application
中启动追踪,在首屏 Activity 停止追踪并生成 .trace
文件
// Application 中
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
Debug.startMethodTracing("cold_launch_trace")
}
// MainActivity 中
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Debug.stopMethodTracing()
}
4.代码调用
通过工具类记录时间差,示例:
// 启动开始(Application 或首个 Activity)
LaunchTimer.startRecord()
// 启动结束(首屏内容加载完成时)
LaunchTimer.endRecord("cold_start")
冷启动的启动过程的话我们就不用详谈了,主要是从Luncher点击图标到app获取焦点的一系列方法。其中在APP进程端我们优化的方法也就是Application内部的onCreate方法、Activity内的UI渲染、View的绘制等、Activity的onCreate()->onResume()的生命周期内的耗时方法以及onWindowFouseChange()的执行。在这些方法内,我们需要查找耗时点并想办法去解决它们。下面是记录一些常见的耗时操作以及如何去修改。
1.第三方sdk的加载
在Application内,我们通常会做一些第三方的sdk加载工作,这在APP的启动过程中是非常耗时的。所以我们需要对这些第三方sdk进行优化
- 异步交给子线程去加载或者通过IdeaHandler去进行闲时加载
- 第三方sdk也为我们配置了启动延迟加载优化(Arouter、EventBus等),我们可以去查看他们的文档查找一下
- App Startup 库合并多个库的初始化到一个Provider中。
2.资源的优化
资源的加载也是APP启动中耗时的一部分,在这部分我们可以将图片转为WebP格式减少资源的大小,也可以使用矢量图替代位图减少分辨率适配资源,还可以将一些不要求质量的图片进行进一步的压缩减少资源加载时间。同时Android studio 也为我们他提供了我用资源扫描功能,在构建的时候我们可以通过shrinkResources=true自动移除无用的图片资源。
3.布局的优化
启动视觉优化:为了避免启动黑白屏,我们可以设置 Activity
启动主题的 windowBackground
为品牌 Logo 或渐变背景,掩盖白屏/黑屏延迟
布局层级优化:
- 使用LayoutInspector对Activity的布局层级进行检测,查找可以减少或者优化的点。
- ConstraintLayout 替代多层嵌套的 LinearLayout/RelativeLayout,减少测量和布局耗时。
- 通过 AsyncLayoutInflater进行异步布局加载
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
AsyncLayoutInflater(this).inflate(R.layout.activity_main, null) { view, resid, parent ->
// 主线程回调,将生成的 View 添加到布局
setContentView(view)
// 初始化 View 控件(如 findViewById)
}
}
- 过开发者选项的 调试 GPU 过度绘制 避免过度绘制
- **ViewStub **延迟加载
- include 标签复用通用布局模板,减少渲染抖动
4.构建配置优化:
启用 ProGuard/R8 剔除无用资源,合并重复资源、按需(不同架构、分辨率)拆分APK,减少资源。
5.内存优化(避免内存抖动):
内存优化是一个繁杂的过程,在启动优化方面我们主要需要关注的就是避免内存抖动防止内存泄漏。在调试过程中我们可以使用StrictMode监测代码中是否有违规操作(线程、网络超时、内存泄漏等)并通过LeakCanary去查找超时调用栈。
// 在 Application 或 Activity 的 onCreate 中启用
if (BuildConfig.DEBUG) {
// 线程策略配置
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // 检测主线程磁盘读
.detectDiskWrites() // 检测主线程磁盘写
.detectNetwork() // 检测主线程网络请
.penaltyLog() // 违规时输出日志(默认
.penaltyDeath() // 违规时直接崩溃(可选
.build()
)
// 虚拟机策略配置
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects() // 检测未关闭的 Closable
.detectLeakedSqlLiteObjects() // 检测未关闭的 SQLite
.detectActivityLeaks() // 检测 Activity 泄漏(API ≥ 11)
.penaltyLog()
.build()
)
}
6.系统层问题优化
SurfaceFlinger 主线程耗时
SurfaceFlinger 负责 Surface 的合成 , 一旦 SurfaceFlinger 主线程调用超时 , 就会产生掉帧 . SurfaceFlinger 主线程耗时会也会导致 hwc service 和 crtc 不能及时完成,
也会阻塞应用的 binder 调用, 如 dequeueBuffer \ queueBuffer 等.后台活动进程太多导致系统繁忙
后台进程活动太多,会导致系统非常繁忙, cpu \ io \ memory 等资源都会被占用, 这时候很容易出现卡顿问题 ,
这也是系统这边经常会碰到的问题。 dumpsys cpuinfo 可以查看一段时间内 cpu 的使用情况:主线程调度不到 , 处于 Runnable 状态
当线程为 Runnable 状态的时候 , 调度器如果迟迟不能对齐进行调度 , 那么就会产生长时间的 Runnable 线程状态 , 导致错过 Vsync 而产生流畅性问题。
启动与布局优化监测工具
sysTrace/Perfetto
抓取Trace文件逐帧分析,查找启动时超时方法。
Trace抓取
1.插入埋点代码
// 开始记录(文件名可自定义)
Debug.startMethodTracing("myAppTrace");
// 待分析的代码段
Debug.stopMethodTracing();
使用 ADB 命令导出文件到本地
adb pull /data/data/<包名>/files/myAppTrace.trace [本地路径]
2.使用 Android Profiler 可视化抓取
Android Studio Proflier
只能说现在的Profiler非常强大,他既可以可视化录制查看内存变化,也可以分析Trace文件,还拥有了MAT的功能分析内存引用。
BlockCanary
BlockCanary 通过监控 Android 主线程的 Looper 消息处理机制,检测单个消息(Message)的执行耗时。当主线程处理消息超过设定阈值(如500ms)时,判定为卡顿并触发警告。
实现方式:通过替换
Looper
的Printer
对象,在消息处理前后插入埋点逻辑,记录时间差。在监控周期内,每 500ms(默认值)采集一次主线程堆栈,通过多次堆栈对比定位耗时根源。
生成
.log
文件记录堆栈、设备信息、CPU 占用率等数据,辅助开发者分析。
使用方式-添加依赖
implementation 'com.github.markzhai:blockcanary-android:1.5.0'
ChoreographerHerper.FrameCallback
Choreographer.FrameCallback允许开发者在 **每一帧绘制前** 插入自定义逻辑,通过
postFrameCallback()注册回调。当系统收到 Vsync 信号后,
doFrame()方法会依次执行已注册的回调,并记录帧开始时间
frameTimeNanos。监控帧间隔时间(如判断是否超过 16.6ms),用于卡顿检测或帧率分析。回调中的 doFrame()
参数 frameTimeNanos
表示当前帧的开始时间,开发者可通过连续两次回调的时间差计算帧耗时。
private var mLastFrameTimeNanos = 0L
Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
if (mLastFrameTimeNanos != 0L) {
val frameCostMs = (frameTimeNanos - mLastFrameTimeNanos) / 1_000_000 // 转换为毫秒
if (frameCostMs > 16) {
Log.w("FrameDrop", "帧耗时异常:${frameCostMs}ms")
}
}
mLastFrameTimeNanos = frameTimeNanos
Choreographer.getInstance().postFrameCallback(this) // 持续监听下一帧
}
})