小米中级Android面试:小米Android开发覆盖MIUI系统定制、IoT设备互联。技术栈偏底层,面试官喜欢问系统级原理和性能数据。技术方向偏向MIUI定制、IoT互联。特色专项包括MIUI系统定制与IoT设备开发。今天8道题覆盖小米中级Android面试核心考点。
Q1:ViewModel的生命周期和设计原理?
ViewModel独立于View的配置变化——屏幕旋转时Activity重建但ViewModel不变(存在ViewModelStore中)。Activity真正finish时才调onCleared()。核心禁忌:不能持有View/Activity/Context引用(用AndroidViewModel可以持有Application)。SavedStateHandle用于进程重建后恢复数据(底层是Bundle序列化)。
class MyVM(private val repo: Repository, savedState: SavedStateHandle) : ViewModel() {
val query = savedState.getLiveData<String>("query")
fun search(q: String) {
savedState["query"] = q
viewModelScope.launch { _result.value = repo.search(q) }
}
}
追问:ViewModel怎么在进程重建后恢复数据?通过SavedStateHandle——在构造时从savedState中取值,内部存到SavedStateRegistry。
Q2:Android内存优化和OOM排查?
内存泄漏=本该释放的对象因被错误引用而没释放。常见场景:非静态内部类Handler持有外部Activity、单例持有Activity Context、静态View、未注销的监听器。排查流程:Profiler dump heap→MAT分析支配树(hprof)→找GC Root到泄漏对象的最短引用路径→修复。
RefWatcher.watch(activity)
// Activity.onDestroy后5秒
// GC触发后检查ReferenceQueue
// 弱引用还在 = 泄漏 → dump heap → 分析引用链
追问:MAT分析时看什么指标?Shallow Heap(对象自身大小)和Retained Heap(对象+其引用的对象被GC能回收的总大小)。看Retained Heap大的。
Q3:Compose的状态管理:remember, mutableStateOf, StateFlow?
mutableStateOf创建一个可观察状态,值变化时触发重组。remember在重组间保留状态(存到Composition中)。StateFlow结合collectAsState()把Flow转为Compose State——自动在Composable生命周期内收集,离开组合时自动取消。关键原则:状态提升——把mutableStateOf放在父级Composable中传入,让子组件无状态可复用。
@Composable
fun SearchScreen(viewModel: SearchVM = hiltViewModel()) {
val query = viewModel.query.collectAsStateWithLifecycle()
TextField(value = query.value, onValueChange = viewModel::onQueryChange)
}
追问:collectAsState和collectAsStateWithLifecycle的区别?后者在Lifecycle低于STARTED时暂停收集,避免后台更新UI。
Q4:Binder机制为什么只需要一次拷贝?
传统IPC需要两次拷贝:发送进程→内核缓冲区→接收进程。Binder的秘诀在mmap——内核缓冲区通过mmap映射到接收进程的用户空间。发送进程把数据copy_from_user到内核缓冲区后,接收进程直接就能读了——不需要第二次copy_to_user。这就是所谓"一次拷贝"的真相。
追问:Binder缓冲区默认多大?通常约1MB(1M-8K),传大Bitmap容易触发TransactionTooLargeException。
Q5:RecyclerView的四级缓存机制?
第一级mAttachedScrap:屏幕内显示的ViewHolder,不重新bind直接复用。第二级mCachedViews:默认容量2,刚滑出屏幕的ViewHolder,同样不重新bind。第三级ViewCacheExtension:开发者自定义缓存。第四级RecycledViewPool:按viewType分组缓存,默认每种5个,取出后需要重新onBind。
val pool = RecyclerView.RecycledViewPool()
rv1.setRecycledViewPool(pool)
rv2.setRecycledViewPool(pool)
追问:前两级缓存和第四级的关键区别?前两级不重新bind(保留数据状态),第四级需要重新bind。
Q6:Compose的重组(recomposition)原理和优化?
重组=Compose检测到State变化后,重新执行受影响的@Composable函数来更新UI。关键:Compose只重组读取了变化State的Composable作用域,不是整棵树。优化手段:1)lambda用remember避免每次重组创建新对象;2)derivedStateOf减少不必要重组——派生状态只有计算结果变化才触发重组;3)key()帮助Compose正确识别item。
val list by remember { mutableStateOf(listOf<Item>()) }
val hasItems by remember { derivedStateOf { list.isNotEmpty() } }
// 只有list空/非空状态变化时才触发重组
追问:Compose为什么能智能跳过重组?编译器在Composable函数中插入$composer参数,通过group key判断是否需要重新执行。
Q7:Handler消息机制底层原理?
核心组件:Message(消息对象,享元模式复用池)、MessageQueue(按时间顺序排列的单链表)、Looper(线程独有一个,死循环取消息)、Handler(发送和处理)。流程:Handler.sendMessage→MessageQueue.enqueueMessage→Looper.loop()死循环取消息→msg.target.dispatchMessage→handleMessage。无消息时nativePollOnce进入epoll_wait休眠,不消耗CPU。
Looper.prepare()
val handler = Handler(Looper.myLooper()!!)
handler.post { /* 在子线程执行 */ }
Looper.loop()
追问:Handler为什么不会造成ANR?无消息时epoll休眠不占CPU。ANR是消息处理超时(输入5秒、广播10秒),不是消息队列本身的问题。
Q8:Service的两种启动方式和生命周期?
startService:onCreate→onStartCommand→(持续运行)→onDestroy。调用者退出后Service仍在后台运行。bindService:onCreate→onBind→(绑定期间)→onUnbind→onDestroy。生命周期跟绑定者一致。Android 8+后台Service限制严格,推荐用JobScheduler/WorkManager做后台任务。
startForeground(NOTIFICATION_ID, NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("运行中").build())
下一篇进入小米高级Android——Android 6.0+运行时权限机制、WMS窗口管理做了哪些事、MVI架构。
觉得有用的同学扣1,你面试被追问最狠的是哪道题?
本系列连载中,关注不迷路,下一篇:小米高级Android面经:Android 6.0+运行时权限机制、WMS窗口管理做了哪些事、MVI架构
系列简介:Android大厂面经连载,覆盖字节跳动、腾讯、阿里、美团等40+企业,从初级到架构师全岗位覆盖。每篇文章包含真实面试题+详细答案+代码示例,帮你拿到大厂Offer。