小米高级Android面经:Android 6.0+运行时权限机制、WMS窗口管理做了哪些事、MVI架构

29 阅读4分钟

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

Q1:Android 6.0+运行时权限机制?

普通权限安装时自动授予。危险权限需运行时弹窗申请。Android 11新增"仅本次允许"单次权限。Android 13细化了媒体权限——READ_MEDIA_IMAGES/VIDEO/AUDIO替代READ_EXTERNAL_STORAGE。Android 14+限制通知权限需申请POST_NOTIFICATIONS。

private val permLauncher = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()) { results ->
        if (results[Manifest.permission.CAMERA] == true) initCamera()
    }
permLauncher.launch(arrayOf(Manifest.permission.CAMERA))

追问:shouldShowRequestPermissionRationale的作用?判断是否需要显示权限说明——首次请求返回false,拒绝后返回true。

Q2:WMS窗口管理做了哪些事?

WMS管理所有窗口——创建、层级、尺寸、焦点、转场动画。窗口层级分三档:应用窗口(1-99)、子窗口(1000-1999,如PopupWindow)、系统窗口(2000-2999,如状态栏、输入法)。WMS还负责输入事件的分发——把触摸事件路由到正确的窗口。

追问:Activity、Window、DecorView、ViewRootImpl四者什么关系?Activity持有PhoneWindow→Window内部有DecorView(根布局)→ViewRootImpl连接Window和WMS。

Q3:MVI架构和单向数据流的实践?

MVI = Model-View-Intent。View发出Intent(用户操作)→Model/ViewModel处理Intent→生成不可变的新State→View根据State渲染。状态是唯一数据源(Single Source of Truth),每次变化产生全新的State对象。优点:状态完全可预测、方便时间旅行调试、易于单元测试。

sealed class CounterIntent { object Increment : CounterIntent(); object Decrement : CounterIntent() }
data class CounterState(val count: Int = 0, val isLoading: Boolean = false)
class CounterViewModel : ViewModel() {
    private val _state = MutableStateFlow(CounterState())
    fun processIntent(intent: CounterIntent) {
        val current = _state.value
        _state.value = when (intent) {
            is CounterIntent.Increment -> current.copy(count = current.count + 1)
            is CounterIntent.Decrement -> current.copy(count = current.count - 1)
        }
    }
}

追问:MVI适合什么场景?状态复杂、多数据源、需要严格状态管理的页面(如订单详情、编辑页面)。简单页面MVVM就够了。

Q4:SharedPreferences的坑和DataStore替代?

SP三大经典坑:1)apply()虽然是异步写磁盘,但在Activity.onStop等生命周期内会等待写入完成,可能ANR;2)首次加载全量读到内存,数据量大时阻塞主线程;3)不支持多进程。DataStore:Preferences(键值对,基于Flow)和Proto(类型安全,基于Protobuf)。异步、类型安全、支持协程。

val ds: DataStore<Preferences> = context.dataStore
suspend fun save(key: String, value: String) {
    ds.edit { it[stringPreferencesKey(key)] = value }
}

追问:MMKV和DataStore怎么选?MMKV基于mmap性能极高但不官方,DataStore是官方推荐且天然支持Flow。

Q5:卡顿检测和优化方案?

卡顿=主线程在16ms内没完成一帧。三方面排查:1)主线程耗时操作——用BlockCanary监控Looper消息处理耗时(>阈值dump堆栈);2)布局层级过深——Systrace看Measure/Layout耗时;3)过度绘制——开发者选项开GPU过度绘制调试,红色区域过多说明重复绘制严重。

Looper.getMainLooper().setMessageLogging { msg ->
    if (msg.startsWith(">>> DISPATCHING")) startTime = SystemClock.uptimeMillis()
    else if (msg.startsWith("<<< FINISHED")) {
        val cost = SystemClock.uptimeMillis() - startTime
        if (cost > thresholdMs) dumpMainThreadStack()
    }
}

追问:卡顿发生时怎么抓取调用栈?Handler定时器(Runnable)每200ms post一次,卡顿时发现未执行→dump主线程当前堆栈。

Q6:Compose中remember和rememberSaveable?

remember:在重组(recomposition)时保留状态,但配置变化(如屏幕旋转)时状态丢失。rememberSaveable:在remember基础上增加持久化——状态自动存储到SavedStateRegistry,配置变化甚至进程重建都能恢复。底层用Bundle序列化,支持基本类型和Parcelable/Saver自定义。

var count by rememberSaveable { mutableStateOf(0) }

追问:Compose的状态提升是什么?把状态从组件移到调用方,让组件变成无状态的可复用函数。

Q7:MVVM架构的核心思想和ViewModel的职责?

MVVM的三层:View(Activity/Fragment,纯UI)——观察ViewModel暴露的响应式数据(StateFlow/LiveData)来更新UI;ViewModel(业务逻辑+UI状态持有)——通过Repository获取数据,处理业务逻辑,暴露UI状态;Model(数据层)——Repository+DataSource,负责数据的获取和持久化。核心优势:ViewModel不持有View引用,单向依赖,天然支持配置变化。

class UserViewModel(private val repo: UserRepository) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    fun loadUser(id: String) {
        viewModelScope.launch {
            _uiState.value = try { UiState.Success(repo.getUser(id)) }
                catch (e: Exception) { UiState.Error(e.message ?: "Unknown") }
        }
    }
}

下一篇进入小米资深Android——组件化架构设计、WorkManager的原理、PMS包管理服务的工作原理。

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

本系列连载中,关注不迷路,下一篇:小米资深Android面经:组件化架构设计、WorkManager的原理、PMS包管理服务的工作原理

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