Android中高级面试(二):架构组件与协程实战精要
🔍 一、ViewModel 架构组件深度剖析
1.1 ViewModel 的生命周期管理机制
class UserViewModel : ViewModel() {
private val _userData = MutableLiveData<User>()
val userData: LiveData<User> get() = _userData
// 数据加载逻辑封装在ViewModel中
fun loadUser(userId: String) {
viewModelScope.launch {
_userData.value = repository.fetchUser(userId)
}
}
}
// Activity/Fragment中的使用
class ProfileActivity : AppCompatActivity() {
private val viewModel by viewModels<UserViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
viewModel.userData.observe(this) { user ->
updateUI(user)
}
}
}
核心机制解析:
ViewModel 通过 ViewModelStore
实现与UI控制器的生命周期解耦,其存活周期从首次获取到Activity finish或Fragment detach。当设备旋转时,Activity重建但ViewModel实例保持不变,避免数据重复加载。
1.2 SavedStateHandle 的数据持久化
class SearchViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val query: MutableLiveData<String> = savedStateHandle.getLiveData("query_key")
fun setQuery(query: String) {
savedStateHandle["query_key"] = query
}
}
// 获取实例时注入SavedStateHandle
val viewModel: SearchViewModel by viewModels {
SavedStateViewModelFactory(application, this)
}
应用场景:
当进程因系统资源回收被杀时,SavedStateHandle 可自动恢复数据。其内部采用Bundle存储机制,支持基本数据类型及Parcelable对象。
🌊 二、LiveData 高级应用与陷阱规避
2.1 Transformations 操作符实战
val userIdLiveData = MutableLiveData<String>()
val userLiveData: LiveData<User> = Transformations.switchMap(userIdLiveData) { id ->
repository.getUserById(id)
}
// 复合数据转换示例
val userProfile = Transformations.map(userLiveData) { user ->
"${user.name} | ${user.email}"
}
2.2 避免LiveData的常见陷阱
内存泄漏防范:
// 错误示例:在Activity中使用匿名Observer
viewModel.data.observe(this) { data ->
updateView(data)
// 当Activity销毁时,此匿名Observer无法自动移除
}
// 正确方案:使用LifecycleOwner
class ProfileFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.data.observe(viewLifecycleOwner) { data ->
updateViews(data)
}
}
}
🚀 三、Kotlin协程在Android中的工程实践
3.1 结构化并发模型
class OrderViewModel : ViewModel() {
private val _orderStatus = MutableLiveData<OrderStatus>()
val orderStatus: LiveData<OrderStatus> get() = _orderStatus
fun fetchOrderDetails(orderId: String) {
viewModelScope.launch(Dispatchers.IO) {
try {
val order = repository.getOrder(orderId)
withContext(Dispatchers.Main) {
_orderStatus.value = OrderStatus.Success(order)
}
} catch (e: Exception) {
_orderStatus.value = OrderStatus.Error(e)
}
}
}
}
并发控制技巧:
// 并行请求优化
suspend fun fetchDashboardData() = coroutineScope {
val userDeferred = async { getUserData() }
val notificationsDeferred = async { getNotifications() }
val user = userDeferred.await()
val notifications = notificationsDeferred.await()
DashboardData(user, notifications)
}
3.2 协程与Room数据库的深度集成
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>> // 使用Flow实现实时数据库监听
@Insert
suspend fun insertUser(user: User) // 挂起函数保证线程安全
}
// ViewModel中使用
viewModelScope.launch {
userDao.getAllUsers().collect { users ->
_userList.value = users
}
}
⚡ 四、性能优化与架构设计进阶
4.1 响应式UI架构设计
4.2 内存泄漏检测与防范
// 使用Android Profiler检测步骤:
// 1. 执行可能泄漏的操作(如旋转设备)
// 2. 触发GC
// 3. 检查Activity/Fragment实例是否仍被持有
// 使用LeakCanary自动检测
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
💡 五、高频面试题精析
5.1 ViewModel vs onSaveInstanceState
特性 | ViewModel | onSaveInstanceState |
---|---|---|
数据存活范围 | 直到Activity销毁 | 进程被杀死后重建 |
数据类型限制 | 支持复杂对象 | 仅支持基本类型和Parcelable |
适用场景 | 屏幕旋转保持数据 | 进程回收后恢复数据 |
5.2 LiveData与StateFlow对比
// LiveData示例
viewModel.data.observe(viewLifecycleOwner) { data ->
updateUI(data)
}
// StateFlow示例
viewModel.uiState.collectLatest { state ->
when (state) {
is UiState.Loading -> showProgress()
is UiState.Success -> showData(state.data)
is UiState.Error -> showError(state.exception)
}
}
总结
📚 核心要点回顾
ViewModel架构优势
- 生命周期感知的数据持有者
- 配合SavedStateHandle实现进程回收恢复
- 通过ViewModelProvider.Factory实现依赖注入
LiveData高级用法
- Transformations实现数据流转换
- 配合LifecycleOwner避免内存泄漏
- 与DataBinding的双向绑定集成
协程最佳实践
- 结构化并发保障任务可管理
- viewModelScope自动取消机制
- Flow实现数据库实时监听
性能优化方向
- 使用Profiler检测内存泄漏
- 避免在ViewModel持有Context
- 合理使用Dispatchers控制线程切换
面试实战建议:在解释技术原理时,务必结合具体业务场景。例如当被问到“如何保证数据在屏幕旋转时不丢失”,可回答:“采用ViewModel保存业务数据,同时使用SavedStateHandle处理进程回收场景,具体实现如代码示例中所示...”