小米性能优化专项面经:RecyclerView滑动性能优化、耗电优化的最佳实践、内存优化进阶

33 阅读4分钟

小米性能优化专项面试:小米Android开发覆盖MIUI系统定制、IoT设备互联。技术栈偏底层,面试官喜欢问系统级原理和性能数据。技术方向偏向MIUI定制、IoT互联。特色专项包括MIUI系统定制与IoT设备开发。今天8道题覆盖小米性能优化专项面试核心考点。

Q1:RecyclerView滑动性能优化?

本质就一个问题——onBindViewHolder里干了什么耗时的事。优化项:1)setHasFixedSize(true)——item宽高固定时避免requestLayout;2)预取(prefetch)——GapWorker在RenderThread空闲时提前创建屏幕外ViewHolder;3)DiffUtil——精准局部刷新避免notifyDataSetChanged;4)共用RecycledViewPool——同类列表跨模块共享缓存池。

val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
    override fun areItemsTheSame(o: Int, n: Int) = oldList[o].id == newList[n].id
    override fun areContentsTheSame(o: Int, n: Int) = oldList[o] == newList[n]
})
diffResult.dispatchUpdatesTo(adapter)

追问:RecyclerView的prefetch机制?Android 5.0+默认开启,在GPU渲染空隙预取下一个ViewHolder。嵌套RecyclerView需要手动设置。

Q2:耗电优化的最佳实践?

不是开省电模式开关——那只是把负担转给用户。真正的优化:1)网络——批量合并请求、减少唤醒Radio;2)定位——按需使用、及时removeUpdates、降低更新频率、网络定位优先GPS;3)后台——WorkManager合并任务、遵守Doze模式约束、AlarmManager合并唤醒;4)WakeLock——严格管理、获取后一定finally释放。

val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresBatteryNotLow(true)
    .build()
val work = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
    .setConstraints(constraints).build()

追问:怎么测App耗电?Battery Historian(分析bugreport里的batterystats)看各App的WakeLock/BLE/WiFi使用情况。

Q3:内存优化进阶:大图加载和Bitmap池?

进阶方案:1)区域解码——BitmapRegionDecoder只加载可见区域,超大图(如地图/长图)不OOM;2)Bitmap复用池——Glide内部用BitmapPool配合inBitmap复用Bitmap内存(需API 11+),避免频繁alloc/free;3)内存监控——Android Profiler的Memory Profiler实时监控;4)LeakCanary——自动监控Activity/Fragment泄漏,hprof分析引用链;5)低内存回调——onTrimMemory中释放图片缓存。

// 区域解码加载长图
val decoder = BitmapRegionDecoder.newInstance(inputStream, false)
val rect = Rect(0, 0, width, visibleHeight)
val bitmap = decoder.decodeRegion(rect, opts)

追问:inBitmap的坑?复用的Bitmap尺寸必须>=目标尺寸(API 19+放宽了这个限制),否则复用失败。Glide内部处理了这些兼容性。

Q4:内存泄漏常见场景排查?

高频泄漏源:1)非静态Handler内部类持有Activity(→静态Handler+WeakReference);2)单例持有Activity Context(→传ApplicationContext);3)匿名内部类/Runnable持有外部引用;4)WebView(→独立进程+ApplicationContext+手动destroy);5)资源未关闭(Cursor/InputStream/BroadcastReceiver)。

// WebView避免泄漏方案
val webView = WebView(applicationContext)
override fun onDestroy() {
    webView.removeAllViews()
    webView.destroy()
}

追问:LeakCanary怎么分析引用链?用HAHA库解析hprof文件,从GC Root遍历引用图找到泄漏对象的最短路径。

Q5:线程优化:线程池管理和协程并发控制?

线程优化要点:1)统一线程池——禁止各模块new Thread(),用全局线程池管理(避免线程过多争抢CPU);2)CPU密集和IO密集分开——CPU任务用Default池(核数线程),IO任务用IO池(64线程上限);3)协程并发控制——async并发请求用awaitAll等待全部完成,withContext切换线程;4)信号量(Semaphore)控制并发数——如图片上传最多3个并发;5)线程泄漏监控——守护线程检测线程数超阈值告警。

// 协程并发控制
val semaphore = Semaphore(3) // 最多3个并发
suspend fun upload(files: List<File>) = files.map {
    async { semaphore.withPermit { uploadOne(it) } }
}.awaitAll()

追问:怎么排查线程过多?adb shell ps -T | grep 看进程下所有线程,或用Android Profiler的Thread面板实时监控。

Q6:APK包体积优化实战?

全方位缩减:代码层——R8混淆+裁剪(ProGuard规则精细化)、统一三方库版本;资源层——shrinkResources移除无用资源、WebP/VectorDrawable替代PNG、AndResGuard资源混淆+7z压缩;so层——abiFilters只保留v7a或v8a、动态下载so(插件化);DEX——控制方法数减少Multidex开销。

andResGuard {
    mappingFile = file("./resource_mapping.txt")
    use7zip = true
    useSign = true
    whiteList = ["R.drawable.icon", "R.string.app_name"]
    compressFilePattern = ["*.png", "*.jpg", "*.webp"]
}

追问:WebP比PNG小多少?有损WebP比PNG小约26-34%,无损WebP比PNG小约26%。图片站APK大头,换WebP效果明显。

Q7:数据库优化:Room和SQLite性能调优?

Room优化:1)索引——WHERE/ORDER BY字段加索引,但索引增加写入开销,不要过度索引;2)批量操作——用@Transaction包裹多个操作减少事务开销;3)预编译——@Query编译时生成SQL,运行时直接执行;4)FTS——全文搜索用@Fts4替代LIKE '%keyword%';5)WAL模式——启用Write-Ahead Logging,读写不再互斥(并发读不阻塞写)。

下一篇进入小米Framework专项——AMS进程管理、Broadcast的有序/无序、Android系统启动。

觉得有用的同学扣1,你面试被追问最狠的是哪道题?

本系列连载中,关注不迷路,下一篇:小米Framework专项面经:AMS进程管理、Broadcast的有序/无序、Android系统启动

系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。