上篇聊完Kotlin协程和编译器插件,这篇进入性能优化专项。美团对性能优化重视程度很高——外卖、到店、酒旅每条业务线都有严格性能指标。面试考的不光你会用什么工具,而是能不能从系统层面分析瓶颈并给出方案。
高级岗要求能独立搭建APM体系并推动优化落地。
今天8道题覆盖美团性能优化面试核心考点。
Q1:美团APM方案怎么做的?
APM(Application Performance Management)是应用性能管理体系。美团的APM涵盖启动耗时、帧率、内存、网络、崩溃率等维度。
数据采集:启动耗时用打点方式(Application.attachBaseContext到首页首帧渲染)。帧率用Choreographer.FrameCallback获取每帧耗时。内存用Debug.getMemoryInfo和Runtime.getRuntime()采样。网络用OkHttp拦截器记录每个请求的耗时和大小。
数据上报:采样+聚合。不是每个用户都上报,按一定比例采样。数据先本地聚合再批量上报,减少网络开销。
告警机制:设定阈值(如启动超过3秒告警),线上指标异常自动通知到负责人。
追问:自己搭建APM怎么做?最小方案:启动打点 + 帧率监控(Choreographer)+ 崩溃收集(Thread.UncaughtExceptionHandler)+ 内存采样。数据存本地SQLite,定期上报到后端。
Q2:列表滑动卡顿怎么优化?
卡顿根因:主线程16.6ms内没完成一帧的绘制(60fps标准)。常见原因:onBindViewHolder做了耗时操作、布局层级太深、过度绘制。
优化手段:onBindViewHolder不做IO/复杂计算/对象分配,数据预处理在子线程完成;用ConstraintLayout减少嵌套层级,ViewStub延迟加载不常用布局;RecyclerView.setHasFixedSize(true)避免重新测量,用RecycledViewPool跨RecyclerView复用ViewHolder;复杂布局用AsyncLayoutInflater异步inflate。
追问:怎么定位具体哪帧卡了?用Choreographer.FrameCallback计算每帧耗时,超过16.6ms的记录堆栈。或用Systrace/Perfetto查看具体耗时分布。BlockCanary原理也是监控主线程Looper的消息处理时间。
Q3:RecyclerView的缓存机制是怎样的?
RecyclerView有四级缓存:
- Scrap(屏幕内):正在显示的ViewHolder,detach后reattach时直接复用,不重新bind。
- CachedViews(屏幕外):刚滑出屏幕的ViewHolder(默认2个),position匹配直接复用,不重新bind。
- ViewCacheExtension(自定义):开发者可扩展的缓存层,实际很少用。
- RecycledViewPool(按type缓存):按viewType分组缓存(每种默认5个),复用时需要重新bind。
追问:嵌套RecyclerView怎么优化?内层RecyclerView共享RecycledViewPool。外层滑动时预加载内层数据。避免嵌套滑动冲突用NestedScrollView或自定义LayoutManager。
Q4:图片加载优化怎么做?
内存优化:按控件尺寸采样解码(inSampleSize),避免加载原图。用BitmapFactory.Options.inBitmap复用Bitmap内存。RGB_565格式代替ARGB_8888(省一半内存,不需要透明度时)。
缓存策略:三级缓存——内存缓存(LruCache)→ 磁盘缓存(DiskLruCache)→ 网络。Glide默认磁盘缓存用LruCache(250MB),内存缓存按应用可用内存1/8。
大图加载:BitmapRegionDecoder只加载显示区域。长图用SubsamplingScaleImageView。缩略图先加载低分辨率再渐进清晰。
追问:Glide和Coil怎么选?Glide成熟稳定、API丰富、支持Gif/WebP。Coil是Kotlin优先、协程驱动、更轻量。新项目用Coil更合适,老项目Glide没必要迁移。
Q5:启动优化怎么做?冷启动和热启动区别?
冷启动:进程不存在,需要创建进程 → 加载Application → 加载MainActivity。耗时最长。
热启动:进程还在,Activity被回收。只需重建Activity。耗时较短。
优化策略:Application不在onCreate做耗时初始化,用AppStartup框架异步初始化非必须组件;利用Splash等待时间预加载首页数据;非首屏SDK(分享、统计)延迟到首页渲染后初始化;Android 7+用Baseline Profile指定AOT编译方法减少JIT耗时。
追问:怎么测量启动时间?adb shell am start -W 可以查看WaitTime(总启动时间)。代码层面在Application.attachBaseContext和首页onFirstDraw之间打点。
Q6:内存优化怎么做?怎么排查内存泄漏?
常见泄漏场景:Activity被静态变量持有、Handler未清理消息、单例持有Activity引用、未注销监听器、WebView未销毁。
排查工具:
- LeakCanary:原理是监控Activity/Fragment的onDestroy,5秒后检查GC Root是否还可达。如果可达就dump堆内存分析泄漏路径。
- MAT(Memory Analyzer Tool):分析heap dump文件,找出GC Root到泄漏对象的引用链。
- Android Profiler:实时查看内存分配、GC频率、对象数量。
优化手段:WeakReference替代强引用、及时置null、onDestroy清理资源、避免静态持有Context。
追问:LeakCanary怎么判断对象泄漏?Activity调用onDestroy后放入ReferenceQueue,5秒后GC并检查是否被回收。未被回收说明泄漏,然后dump hprof文件分析最短GC Root路径。
Q7:编译加速怎么做?
Gradle优化:开启Gradle Daemon和并行编译(org.gradle.parallel=true)、开启构建缓存(org.gradle.caching=true)、用KSP替代KAPT(快2-5倍)、减少注解处理器数量。
模块化加速:组件化后每个模块独立编译,增量编译只重编译修改的模块。用includeBuild引入独立模块。
其他手段:避免buildSrc写复杂逻辑、减少buildConfig字段、非开发时关闭debuggable、CI用Bazel等增量构建工具。
追问:为什么KSP比KAPT快?KAPT要先生成Java stub再交给javac处理,多了一步stub编译。KSP直接处理Kotlin代码,省去stub生成,且完整保留Kotlin特性。
Q8:性能优化的面试怎么回答?
结构化回答:场景 → 问题 → 方案 → 效果。比如"我们App冷启动从4秒优化到1.5秒。先分析耗时分布(启动trace),发现3个SDK初始化占了2秒。改为延迟加载后启动时间降了一半"。
避免空泛:不要只说"用LeakCanary检测泄漏",要说"检测到XX页面泄漏,原因是静态持有Activity引用,改为WeakReference修复,内存占用降了30MB"。
展示深度:讲清楚工具的原理(不只是会用),能从系统层面分析(Framework层为什么这么设计),有数据支撑(优化前后对比数据)。
面试Tips:美团性能优化面喜欢考实际经验——不是问你"知道什么工具",而是"你做了什么优化、效果怎样"。回答时用"场景→问题→方案→效果"结构。Recycler缓存四级机制、LeakCanary原理、启动优化策略是高频考点。编译加速相对考得少但KSP替代KAPT要知道。
下一篇进入Framework专项——WMS窗口管理、ViewRootImpl、Choreographer帧调度、SurfaceFlinger。
做过列表滑动优化的同学扣1,你能讲清楚RecyclerView四级缓存吗?
本系列连载中,关注不迷路,下一篇:美团资深Android(Framework专项)面试真题
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。