Skip to content

性能优化 / 稳定性 — 知识详解

更新: 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 内存泄漏

常见场景:

  1. 静态变量持有 Activity:单例持有 Context
  2. Handler 内存泄漏:非静态内部类持有外部 Activity 引用,Message 在队列中等待
  3. 匿名内部类/Lambda:持有外部类引用
  4. 未注销的监听器:EventBus、BroadcastReceiver、Sensor 等
  5. 集合类泄漏:只添加不移除

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 原理

  1. 注册 ActivityLifecycleCallbacks 监听 Activity 销毁
  2. Activity onDestroy 后,将其包装为 WeakReference + ReferenceQueue
  3. 延迟 5 秒后检查 ReferenceQueue,如果 WeakReference 没有入队说明 Activity 没被回收
  4. 手动触发 GC,再次检查
  5. 仍未回收 → 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 秒
ContentProvider10 秒

4.2 ANR 原理

以 InputDispatcher 为例:

  1. InputDispatcher 将事件发送给应用
  2. 启动 5 秒定时器
  3. 应用处理完事件后回复 InputDispatcher
  4. 如果 5 秒内没有回复 → ANR

4.3 ANR 排查

  1. 查看 /data/anr/traces.txt(主线程堆栈)
  2. 分析主线程在做什么:
    • 死锁:两个线程互相等待对方的锁
    • 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 15

5. 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 分析:

  1. 获取 tombstone 文件
  2. addr2linendk-stack 将地址转换为源码位置
  3. 需要保留未 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 规则。