上篇Framework聊完,这篇进入性能优化。P7+面试考的是"怎么设计线上APM系统?启动从1.5s降到800ms怎么做的?卡顿怎么定位到代码行?"
阿里对性能要求极高——淘宝首页每慢100ms转化率降1%。P7+要能设计方案而不仅是会用工具。
今天8道题覆盖P7+性能优化核心考点。
Q1:阿里APM方案设计?
APM(Application Performance Monitoring):线上性能监控系统,采集用户设备的真实性能数据。
核心指标:启动耗时(冷启动/热启动)、页面帧率(FPS/掉帧率)、卡顿率(单帧>16.6ms的比例)、Crash率、ANR率、网络成功率、网络耗时。
采集方式:启动耗时用SystemClock.elapsedRealtime()记录各阶段时间点上报。帧率用Choreographer.postFrameCallback统计每帧耗时。卡顿检测用Looper的Printer——Looper.getMainLooper().setMessageLogging监控dispatchMessage耗时。
数据聚合:端侧采集原始数据 → 聚合后上报(减少请求次数)→ 服务端按版本/机型/系统版本分维度聚合 → 大盘看板(P50/P90/P99分位值)。
追问:为什么用P90/P99而不用平均值?平均值被极端值拉偏。P90表示90%用户体验在这个值以下,更能反映真实体验。P99用来发现长尾问题(少数用户遇到的严重性能问题)。
Q2:启动优化怎么做到极致?
启动阶段拆分:冷启动分四阶段——Application创建(attachBaseContext→onCreate)→ 首页Activity创建 → 数据请求 → 首页渲染完成。每个阶段埋点统计耗时。
Application优化:非首屏SDK延迟初始化(推送/统计/分享放到首页渲染后再初始化)。启动框架用Alpha——任务按依赖关系拓扑排序并行执行。比如数据库初始化和网络请求可以并行。
首页Activity优化:布局懒加载(ViewStub延迟inflate非首屏View)、预加载(IdleHandler在主线程空闲时预加载下一页数据)、减少IO(SharedPreferences改为MMKV减少磁盘读写)。
数据优化:接口合并(首屏多个请求合并为一个)、数据预取(Application阶段就发首屏请求)、本地缓存兜底(先展示缓存再请求新数据)。
追问:启动优化怎么验证效果?线上看APM大盘的冷启动P50/P90数据。线下用adb shell am start -W统计Displayed时间。A/B实验对比优化前后的留存率——启动快100ms留存率提升0.5%。
Q3:列表卡顿怎么优化?
卡顿根因:主线程单帧耗时>16.6ms。原因:主线程做耗时操作、布局层级太深、过度绘制。
定位工具:Systrace看每帧耗时。GPU呈现模式柱状图看时间分布。BlockCanary检测卡顿并dump堆栈。
优化手段:ConstraintLayout扁平化、ViewStub延迟加载、AsyncTask处理耗时逻辑、Bitmap按需采样、Glide自动管理内存。
RecyclerView专项:setRecycledViewPool共享池、DiffUtil局部刷新、setHasFixedSize避免重测量、addItemDecoration替代item内padding。
Q4:页面帧率怎么监控?
线上监控:Choreographer.FrameCallback统计每帧耗时。Choreographer.getInstance().postFrameCallback(this)注册回调,每帧回调记录时间差。>16.6ms算掉帧,统计掉帧率上报。
线下工具:Systrace全局分析。Perfetto(Systrace升级版,支持更长时间trace)。Android Studio CPU Profiler看方法级耗时。
BlockCanary原理:Hook主线程Looper的Printer,在dispatchMessage前后打印时间戳。如果间隔>阈值(默认500ms),dump主线程堆栈并通知开发者。原理简单但效果显著。
追问:掉帧和卡顿什么区别?掉帧是一帧超过16.6ms,用户不一定感知(偶尔掉1-2帧)。卡顿是连续多帧掉帧(>3帧),用户明显感知到"卡"。监控指标里卡顿率比掉帧率更有意义。
Q5:网络性能怎么优化?
连接优化:HTTP/2多路复用、OkHttp连接池复用(默认5连接keep-alive 5分钟)、HTTPDNS替代LocalDNS防劫持。
传输优化:Gzip/Brotli压缩(减少70%+)、图片WebP/AVIF(比JPEG小30%)、Protobuf替代JSON。
请求优化:接口合并(首屏多API合一)、请求去重(同接口短时不重复)、Application阶段预取首屏数据。
重试策略:超时分级(连接5s读15s)、指数退避(1s→2s→4s)、幂等接口才重试。
Q6:内存优化怎么做?
内存泄漏:LeakCanary检测(Debug包自动扫描)、MAT分析hprof。常见泄漏:匿名内部类持有Activity引用、单例持有Context、未取消的回调/广播注册。
内存抖动:频繁创建临时对象触发GC。优化:对象池复用(Message.obtain)、避免在onDraw里new对象、字符串拼接用StringBuilder。
图片内存:大图按需采样(BitmapFactory.Options.inSampleSize)、Glide自动管理(内存缓存+磁盘缓存+自动降采样)、列表滑动时暂停图片加载(Glide.with(lifecycle).pauseOnScroll())。
追问:怎么设置合理的内存上限?ActivityManager.getMemoryClass()返回App可用内存上限(通常128-512MB)。largeHeap=true可以申请更大堆但不推荐(GC更慢)。建议图片缓存大小设为可用内存的1/8。
Q7:包大小怎么优化?
资源优化:图片转WebP(减少30%)、去除重复资源(lint检查UnusedResources)、SVG替代位图(矢量图不随分辨率增长)。
代码优化:ProGuard/R8混淆+移除未使用代码(shrinkResources=true)、去除无用so(只保留armeabi-v7a和arm64-v8a)、Kotlin替代Java(部分场景代码量减少)。
动态下发:非首屏模块做成动态Feature(Google Play App Bundle/国内用Atlas插件化),用户需要时才下载。
分析工具:Android Studio APK Analyzer看每个文件占比。./gradlew app:dependencies看三方库依赖树——有些库引入大量依赖但只用了一个方法。
追问:so库太大怎么处理?用App Bundle按ABI分包(Google Play自动处理)。国内用插件化方案把so放到插件APK里按需下载。或者编译时用-Oz优化选项减小so体积(clang的体积优化)。
Q8:性能优化怎么量化结果?
基线建立:优化前先测当前性能基线——冷启动P50=1.2s、首页帧率P90=55fps、包大小=18MB。
目标设定:参考竞品和行业标准。冷启动<1s、帧率>58fps、包大小<15MB。目标要有数据支撑,不能拍脑袋。
实验验证:A/B实验对比优化前后。看线上大盘数据(P50/P90/P99),不看个别用户的反馈。样本量要够(至少1万用户/组)。
持续监控:性能指标接入CI——每次发版跑性能回归测试。新代码引入性能退化自动报警。性能指标纳入团队KPI。
面试Tips:P7+性能优化APM考系统设计(采集什么指标、怎么聚合)。启动优化考四阶段拆分和具体手段。帧率考卡顿定位工具链(Systrace/BlockCanary)。网络考连接复用和压缩。内存考泄漏检测和抖动治理。面试要有数据——"我优化了启动"不如"冷启动从1.5s降到900ms,P90提升30%"。
下一篇进入跨平台专项——Flutter引擎原理、Dart isolate、闲鱼跨端架构、小程序容器设计。
做过启动优化的同学评论区聊聊,你们冷启动多少毫秒?
本系列连载中,关注不迷路,下一篇:阿里P8+专家Android(跨平台专项)面试真题
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。