性能优化 / 稳定性 — 知识详解
更新: 5/15/2026 字数: 0 字 时长: 0 分钟
内存优化与启动优化内容较多,已拆分为独立文件:
- memory-startup-deep.md — 内存管理基础、LeakCanary 源码、Bitmap 优化、OOM 排查、启动全链路分析、DAG 任务编排、ContentProvider 优化、Systrace/Perfetto 使用
1. 启动优化
1.1 冷启动全链路
用户点击图标
→ Zygote fork 新进程
→ 创建 ActivityThread(主线程)
→ Application.attachBaseContext
→ ContentProvider.onCreate(所有 Provider)
→ Application.onCreate
→ Activity.onCreate → onStart → onResume
→ ViewRootImpl.performTraversals(首帧绘制)
→ 用户看到内容1.2 优化手段
Application.onCreate 优化:
- 延迟初始化:非必要 SDK 延迟到首帧后或使用时初始化
- 异步初始化:无依赖关系的 SDK 放到子线程
- 任务编排:有依赖关系的初始化任务用 DAG(有向无环图)编排
kotlin
// 任务编排示例
class InitTask {
fun start() {
val executor = Executors.newFixedThreadPool(4)
val latch = CountDownLatch(3)
// 无依赖,并行执行
executor.execute { initSDK_A(); latch.countDown() }
executor.execute { initSDK_B(); latch.countDown() }
executor.execute { initSDK_C(); latch.countDown() }
latch.await() // 等待完成
// 依赖 A/B/C 的初始化
initSDK_D()
}
}Activity 优化:
- 减少布局层级(ConstraintLayout 替代嵌套 LinearLayout)
- 延迟加载非首屏内容(ViewStub)
- 异步 inflate(AsyncLayoutInflater)
指标体系:
- TTID(Time To Initial Display):首帧显示时间
- TTFD(Time To Full Display):完整内容显示时间
- 使用
reportFullyDrawn()标记完整显示时间点
1.3 工具
- Systrace / Perfetto:分析主线程耗时
- Android Studio Profiler:CPU/内存/网络分析
Debug.startMethodTracing()/Debug.stopMethodTracing():方法耗时追踪
2. 卡顿优化
2.1 渲染管线
VSYNC 信号(16.6ms 一次,60fps)
→ Choreographer.doFrame()
→ 处理 Input 事件
→ 处理动画
→ 执行 performTraversals(measure/layout/draw)
→ RenderThread 执行 GPU 渲染
→ SurfaceFlinger 合成显示一帧超过 16.6ms 就会掉帧。
2.2 Choreographer 帧率监控
kotlin
Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
private var lastFrameTimeNanos = 0L
override fun doFrame(frameTimeNanos: Long) {
if (lastFrameTimeNanos != 0L) {
val droppedFrames = ((frameTimeNanos - lastFrameTimeNanos) / 16_666_666) - 1
if (droppedFrames > 0) {
Log.w("Jank", "掉帧: $droppedFrames 帧")
}
}
lastFrameTimeNanos = frameTimeNanos
Choreographer.getInstance().postFrameCallback(this)
}
})2.3 常见卡顿原因
- 主线程 IO(文件读写、SP 操作)
- 主线程网络请求
- 复杂布局(层级过深、过度绘制)
- 频繁 GC(内存抖动)
- 主线程锁等待
- Binder 调用阻塞
2.4 BlockCanary 原理
利用 Looper 的日志打印机制:
java
Looper.getMainLooper().setMessageLogging(printer -> {
if (printer.startsWith(">>>>> Dispatching")) {
startTime = System.currentTimeMillis();
}
if (printer.startsWith("<<<<< Finished")) {
long cost = System.currentTimeMillis() - startTime;
if (cost > threshold) {
// 卡顿!dump 主线程堆栈
}
}
});3. 内存优化
3.1 内存区域
- Java 堆:对象实例,GC 管理
- Native 内存:JNI 分配、Bitmap(Android 8.0+ 在 Native 堆)、so 库
- 虚拟机栈:线程栈,默认 1MB/线程
3.2 内存泄漏
常见场景:
- 静态变量持有 Activity:单例持有 Context
- Handler 内存泄漏:非静态内部类持有外部 Activity 引用,Message 在队列中等待
- 匿名内部类/Lambda:持有外部类引用
- 未注销的监听器:EventBus、BroadcastReceiver、Sensor 等
- 集合类泄漏:只添加不移除
Handler 泄漏解决:
kotlin
class MyActivity : AppCompatActivity() {
// ✅ 静态内部类 + 弱引用
private class MyHandler(activity: MyActivity) : Handler(Looper.getMainLooper()) {
private val weakActivity = WeakReference(activity)
override fun handleMessage(msg: Message) {
weakActivity.get()?.handleMsg(msg)
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null) // 清除所有消息
}
}3.3 LeakCanary 原理
- 注册 ActivityLifecycleCallbacks 监听 Activity 销毁
- Activity onDestroy 后,将其包装为 WeakReference + ReferenceQueue
- 延迟 5 秒后检查 ReferenceQueue,如果 WeakReference 没有入队说明 Activity 没被回收
- 手动触发 GC,再次检查
- 仍未回收 → dump hprof → 分析引用链 → 展示泄漏路径
3.4 Bitmap 优化
kotlin
// 1. 采样加载
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true // 只读取尺寸
}
BitmapFactory.decodeFile(path, options)
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
options.inJustDecodeBounds = false
val bitmap = BitmapFactory.decodeFile(path, options)
// 2. 使用 inBitmap 复用内存
options.inMutable = true
options.inBitmap = reusableBitmap // 复用已有 Bitmap 的内存
// 3. 使用合适的像素格式
options.inPreferredConfig = Bitmap.Config.RGB_565 // 比 ARGB_8888 省一半内存4. ANR
4.1 四种 ANR 类型
| 类型 | 超时时间 |
|---|---|
| InputDispatcher(触摸事件) | 5 秒 |
| BroadcastReceiver | 前台 10 秒,后台 60 秒 |
| Service | 前台 20 秒,后台 200 秒 |
| ContentProvider | 10 秒 |
4.2 ANR 原理
以 InputDispatcher 为例:
- InputDispatcher 将事件发送给应用
- 启动 5 秒定时器
- 应用处理完事件后回复 InputDispatcher
- 如果 5 秒内没有回复 → ANR
4.3 ANR 排查
- 查看
/data/anr/traces.txt(主线程堆栈) - 分析主线程在做什么:
- 死锁:两个线程互相等待对方的锁
- IO 阻塞:文件读写、数据库操作
- Binder 调用:跨进程调用超时
- CPU 密集计算
// traces.txt 示例
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x... self=0x...
| sysTid=12345 nice=-10 cgrp=default sched=0/0 handle=0x...
| state=S schedstat=( 0 0 0 ) utm=100 stm=50 core=0 HZ=100
| stack=0x...-0x... stackSize=8192KB
at com.example.MyClass.doSomething(MyClass.java:42)
- waiting to lock <0x...> (a java.lang.Object) held by thread 155. Crash 治理
5.1 Java Crash
kotlin
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
// 1. 记录崩溃信息(堆栈、设备信息、用户操作路径)
// 2. 上报到服务端
// 3. 可选:尝试恢复或优雅退出
CrashReporter.report(throwable)
Process.killProcess(Process.myPid())
}5.2 Native Crash
通过信号处理捕获:SIGSEGV(段错误)、SIGABRT(abort)等。
工具:Breakpad(Google)、xCrash(爱奇艺开源)。
Native Crash 分析:
- 获取 tombstone 文件
- 用
addr2line或ndk-stack将地址转换为源码位置 - 需要保留未 strip 的 so 文件(带符号表)
5.3 稳定性指标
- Crash Rate:崩溃率 = 崩溃用户数 / DAU
- ANR Rate:ANR 率
- 业界标准:Crash Rate < 0.1%,ANR Rate < 0.3%
6. 包体积优化
- 代码:ProGuard/R8 混淆压缩、移除无用代码
- 资源:移除无用资源(
shrinkResources)、图片压缩(WebP)、资源混淆(AndResGuard) - so 库:只保留 arm64-v8a、按需加载 so
- 动态下发:插件化、Feature Delivery
7. 电量优化
- 减少 WakeLock 使用
- 合并网络请求(批量上报)
- 使用 WorkManager 替代 AlarmManager
- 适配 Doze 模式和 App Standby
- GPS 定位使用合适的精度和频率
8. 内存抖动
8.1 什么是内存抖动
短时间内大量创建和销毁对象,导致频繁 GC。GC 会暂停应用线程(虽然 ART 的 CC 收集器暂停时间很短),频繁 GC 累积起来会导致卡顿。
8.2 常见场景
java
// ❌ 在 onDraw 中创建对象(每帧调用)
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint(); // 每帧创建新对象!
Rect rect = new Rect(); // 每帧创建新对象!
canvas.drawRect(rect, paint);
}
// ✅ 在构造函数中创建,onDraw 中复用
private final Paint paint = new Paint();
private final Rect rect = new Rect();
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(rect, paint); // 复用对象
}其他常见场景:
- 循环中创建字符串(用 StringBuilder)
- RecyclerView.onBindViewHolder 中创建对象
- 频繁装箱拆箱(int → Integer)
8.3 检测工具
Android Studio Profiler 的 Memory 面板:
- 锯齿状的内存曲线 = 内存抖动
- 查看 Allocations 面板定位频繁分配的对象
9. StrictMode
开发阶段检测主线程违规操作:
kotlin
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads() // 检测主线程磁盘读
.detectDiskWrites() // 检测主线程磁盘写
.detectNetwork() // 检测主线程网络
.penaltyLog() // 违规时打日志
.penaltyDeath() // 违规时崩溃(严格模式)
.build()
)
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() // 检测未关闭的 Cursor
.detectLeakedClosableObjects() // 检测未关闭的 Closeable
.detectActivityLeaks() // 检测 Activity 泄漏
.penaltyLog()
.build()
)
}10. Baseline Profile
Android 运行时(ART)使用 JIT 编译热点代码。Baseline Profile 提前告诉 ART 哪些代码是热点,在安装时就 AOT 编译,避免首次运行时的 JIT 开销。
kotlin
// 生成 Baseline Profile
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generateProfile() {
rule.collect(packageName = "com.example.app") {
// 模拟用户操作路径
pressHome()
startActivityAndWait()
// 滑动列表、点击按钮等
}
}
}效果:启动速度提升 20-40%,滑动流畅度提升。Google Play 会自动使用 Baseline Profile。
11. R8 优化
R8 是 Android 的代码压缩和混淆工具(ProGuard 的替代):
- 代码压缩(Shrinking):移除未使用的类、方法、字段
- 混淆(Obfuscation):将类名/方法名缩短(a, b, c)
- 优化(Optimization):内联方法、移除空方法、简化控制流
- 资源压缩:配合
shrinkResources true移除未引用的资源
groovy
android {
buildTypes {
release {
minifyEnabled true // 开启 R8
shrinkResources true // 资源压缩
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}R8 Full Mode(android.enableR8.fullMode=true):更激进的优化,可能需要额外的 keep 规则。
