Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

性能优化

性能优化是中级面试的高频区,也是你的潜在主场——风控 SDK 对体积/性能极度敏感,你的经验比一般应用开发者更有说服力。

一、启动优化

启动分类:

  • 冷启动:进程不存在,需创建进程 + Application + Activity,最慢。
  • 温启动:进程在但 Activity 重建。
  • 热启动:Activity 还在,仅恢复,最快。

冷启动耗时点 + 优化:

  • Application.onCreate 过重:第三方 SDK 初始化阻塞。优化:异步初始化、延迟初始化、按需初始化(用 Jetpack App Startup 统一管理依赖顺序)。
  • 闪屏页(Splash):用 Android 12+ SplashScreen API,避免白屏/黑屏。
  • 主线程 IO:首屏不读大文件/SP。
  • 测量:adb shell am start -W 看 TotalTime;Perfetto/Systrace 看主线程占用。
  • 进阶:类预加载、ContentProvider 初始化收敛(很多 SDK 用 ContentProvider 自启,拖慢启动)。

二、内存优化

内存泄漏(常见场景)

  • 非静态内部类/匿名类持有外部 Activity(Handler、Runnable、监听器)。
  • 静态变量持有 Context/View。
  • 单例持有 Activity Context(应用 Application Context)。
  • 未注销监听器/广播/观察者。
  • 资源未关闭(Cursor、Stream、Bitmap)。

LeakCanary 原理(以 LeakCanary 2.x 思路理解):默认会 watch Activity、Fragment、Fragment View、ViewModel 等应被回收的对象。对象销毁后交给 ObjectWatcher,内部用弱引用和引用队列观察;延迟一小段时间后触发/等待 GC,若对象仍未被回收,就认为是 retained object。达到阈值后 dump HPROF,再由 Shark 分析 GC Roots 到泄漏对象的最短强引用链。

步骤看到的证据面试边界
watch 销毁对象对象带 description 和 watch time不是所有 retained 都是业务泄漏,可能是系统/库已知泄漏或调试期延迟。
等待 + GC对象没有进入 ReferenceQueueGC 触发时机不是业务可精确控制的 API,不要说“立刻判定”。
heap dumpHPROF 文件、分析耗时dump 会暂停进程/占内存,通常只在 debug 或内测环境开。
Shark 分析引用链、Library Leak 标记结论要结合生命周期判断,不是看到 Activity 就机械删除引用。

内存抖动

短时间大量创建小对象(如 onDraw / 循环里 new),频繁 GC 导致卡顿。优化:对象复用、避免在高频路径分配、用对象池。

三、卡顿与 ANR

掉帧原理

屏幕 60fps → 每帧 16.6ms 内必须完成 measure/layout/draw。超时就丢帧(jank)。Choreographer 接收 VSync 信号驱动每帧渲染,可监控帧耗时。

卡顿优化

  • 布局优化:减少层级(ConstraintLayout 扁平化)、<merge>/<ViewStub>、避免过度绘制(overdraw)。
  • 主线程不做耗时(IO、解析、复杂计算),用协程切后台。
  • RecyclerView:复用、DiffUtil、避免在 onBindViewHolder 做重活、预取。

ANR(Application Not Responding)

触发条件:

  • 主线程阻塞:常见记忆口径是 Input 事件约 5s 未响应、前台 BroadcastReceiver 约 10s、后台广播约 60s、前台 Service 约 20s、后台 Service 约 200s。但这些阈值会受 Android 版本、前后台状态、组件类型和厂商策略影响,面试要说“常见阈值/典型场景”,不要当成所有版本的硬契约。 定位:
  • /data/anr/traces.txt 看主线程堆栈。
  • 看是否主线程 IO、锁等待、Binder 阻塞、死锁。
  • 工具:ANR-WatchDog、线上监控(主线程卡顿监控)。

卡顿/ANR 排查链路

症状可能原因工具关键证据修复方向取舍
冷启动白屏久Application/Provider 初始化重、主线程 IO、首屏 inflate 慢am start -W、Android Studio Startup/Profiler、PerfettoTotalTime 增大、main thread 上长任务、bindApplication/launchActivity slice 慢延迟/按需初始化、Provider 收敛、首屏最小化延迟初始化可能把耗时转移到首个功能入口,要配合预热和埋点。
滑动掉帧onBind 重活、图片解码过大、布局层级深、频繁 GCProfiler、Perfetto Frame Timeline、Layout Inspector帧耗时超过预算、UI thread/RenderThread slice 长、GC 频繁DiffUtil、异步解码、固定尺寸、减少层级/overdraw过度缓存会增加内存,异步化要处理取消和错位。
偶发长卡顿主线程锁等待、同步 Binder、磁盘 IO、类加载Perfetto/Systrace、StrictMode、线程 dumpmain thread blocked/runnable 状态、binder transaction、disk read/write缩小锁范围、后台线程、缓存预热、异步 IPC后台化不等于无限开线程,要限制并发和生命周期。
ANR主线程长时间不取消息、广播/服务超时、死锁ANR traces、Perfetto、日志埋点、线上 ANR 平台traces 中 main 堆栈、锁 owner、Binder 对端、CPU/IO 状态移除主线程阻塞、拆分任务、避免持锁跨 Binder只看 main 堆栈可能误判,还要看 CPU 饥饿和对端进程。
内存泄漏Activity/Fragment 被静态对象、监听器、协程持有LeakCanary、Memory Profilerretained object、GC Root 引用链、实例数量持续上涨解绑监听、用 application context、取消协程/请求弱引用不是万能解,优先修正生命周期所有权。

四、包体积优化

  • 代码:R8/ProGuard 混淆 + 裁剪无用代码(shrinkResources)。
  • 资源:无用资源删除、图片用 WebP、矢量图、资源混淆(AndResGuard)。
  • so 库:按 ABI 拆分(abiFilters 通常只留 arm64-v8a)、动态下发。这是你的强项——SDK 开发对 so 体积敏感。
  • dex:移除未用依赖、避免方法数膨胀。
  • App Bundle(aab):按设备下发,Google Play 强制。

五、工具链

  • Android Studio Profiler:适合开发期快速看 CPU / Memory / Network / Energy。证据是方法耗时、分配热点、网络请求时间线;局限是插桩/采样会有开销,线上问题要结合 trace/埋点。
  • Memory Profiler:看 Java/Kotlin 堆、对象数量、分配调用栈、dump HPROF。用于“内存上涨/频繁 GC/疑似泄漏”,修复后要对比同一路径的实例数量是否回落。
  • Perfetto / Systrace:系统级 trace,看 main thread、RenderThread、Binder、锁、调度、Frame Timeline。Perfetto 是现代首选;Systrace 常用于老项目/旧资料口径。证据是具体 slice 和线程状态,不是“感觉变流畅”。
  • LeakCanary:自动发现 retained objects 并给出引用链。适合 debug/内测定位泄漏;不要在生产默认开启 heap dump。
  • Layout Inspector:看视图层级、约束、重叠与 Compose/View 混合结构。适合布局层级深、过度绘制、首屏 inflate 慢的问题。
  • StrictMode:开发期检测主线程磁盘/网络、资源未关闭等。适合把“偶发卡顿”提前暴露;惩罚策略可从 log 开始,不要在生产随意 crash 用户。

工具选择速答表

你观察到先用再用面试表达
“启动慢”am start -W 定量Perfetto 找 bindApplication/首帧长任务先量化,再定位主线程 slice,最后验证优化前后。
“滑动卡”Frame Timeline/ProfilerLayout Inspector + 图片库日志区分 UI 线程、RenderThread、GC、图片解码。
“内存涨”Memory ProfilerLeakCanary/heap dump看趋势和引用链,不要只看一次快照。
“线上 ANR”ANR traces/平台聚合Perfetto/埋点复现主线程栈 + 锁/Binder/CPU 状态一起看。
“怀疑主线程 IO”StrictModePerfetto disk slice开发期用 StrictMode 前置发现,trace 证明耗时。

高频面试题

Q1:冷启动优化有哪些手段? 异步/延迟/按需初始化 SDK、收敛 ContentProvider 自启动、避免主线程 IO、用 SplashScreen API、类与资源预加载。用 am start -W 和 Perfetto 量化。

Q2:内存泄漏常见场景?LeakCanary 怎么检测的? 非静态内部类/Handler 持 Activity、单例持 Context、未注销监听、资源未关。LeakCanary 2.x 可理解为 ObjectWatcher 监听销毁对象,用弱引用+引用队列观察是否回收;延迟和 GC 后仍 retained,再 dump HPROF 并用 Shark 分析引用链。结论要结合生命周期,不要把所有 retained 都当业务泄漏。

Q3:为什么 16.6ms 是关键?掉帧怎么排查? 60fps 下每帧预算 16.6ms,超时丢帧。用 Choreographer 监控帧耗时,Profiler/Perfetto 看主线程哪段超时,优化布局层级和主线程耗时操作。

Q4:ANR 触发条件?怎么定位? 主线程阻塞超阈值会触发 ANR,常见口径是输入约 5s、前台广播约 10s、前台 Service 约 20s,后台场景通常更长,但具体阈值和触发条件随版本/前后台/厂商策略变化。定位看 ANR traces 主线程堆栈,结合锁 owner、Binder 对端、CPU/IO 状态,排查主线程 IO、锁竞争、死锁、同步 Binder。

Q5:包体积优化怎么做?(你的强项,展开答) R8 裁剪混淆、资源压缩、WebP/矢量图、so 按 ABI 拆分(只留 arm64-v8a)+ 动态下发、移除无用依赖、用 App Bundle 按需下发。结合 SDK 经验讲 so 瘦身和符号裁剪更有说服力。

Q6:RecyclerView 卡顿如何优化? DiffUtil 局部刷新、onBindViewHolder 不做耗时、setHasFixedSize、预取(prefetch)、图片异步加载、减少 item 布局层级、复用 ViewHolder。

Q7:Serializable 和 Parcelable 哪个对性能更好,为什么? Parcelable。Serializable 用反射、产生大量临时对象触发 GC;Parcelable 专为内存/IPC 设计,无反射,速度快约一个数量级。