1. 为什么移动开发需要设计模式?
在移动应用开发领域,代码复杂度随着业务增长呈指数级上升。一个典型的电商App可能同时包含商品展示、购物车管理、支付流程、用户系统等十几个功能模块。如果没有良好的架构设计,Activity/Fragment很快就会变成难以维护的"上帝类"。
我在2018年接手过一个外卖App的重构项目,当时的代码库里有超过2000行的MainActivity,各种网络请求、UI更新、数据解析逻辑全部揉在一起。每次修改下单逻辑都需要在十几个地方同步调整,稍有不慎就会引发连锁崩溃。这种经历让我深刻认识到架构设计的重要性。
MVVM(Model-View-ViewModel)作为Google官方推荐的架构模式,通过数据绑定和观察者模式实现了视图与业务逻辑的彻底解耦。配合Kotlin的语言特性,可以让代码更简洁、更安全。比如用Kotlin的委托属性实现自动化的数据绑定,用密封类(sealed class)优雅地处理UI状态,这些我们后面都会具体展开。
2. MVVM核心组件深度解析
2.1 View层的最佳实践
在Android的MVVM实现中,View层通常由Activity/Fragment组成。但与传统MVC不同,View层应该保持极简主义:
kotlin复制class ProductActivity : AppCompatActivity() {
private val viewModel: ProductViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_product)
// 观察ViewModel暴露的LiveData
viewModel.products.observe(this) { products ->
binding.recyclerView.adapter = ProductAdapter(products)
}
viewModel.errorMessage.observe(this) { message ->
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
}
几个关键注意事项:
- 避免在View层直接处理业务逻辑,所有操作都应委托给ViewModel
- 使用Data Binding时注意内存泄漏,在onDestroy中解除绑定
- 对于复杂UI,可以考虑使用自定义BindingAdapter
经验:我习惯在BaseActivity中封装通用的观察逻辑,比如加载状态、错误提示等,避免每个Activity重复编写相同代码。
2.2 ViewModel的生命周期管理
ViewModel之所以能避免配置变更导致的数据丢失,核心在于其生命周期比Activity更长。但这也带来一些陷阱:
kotlin复制class ProductViewModel(
private val savedStateHandle: SavedStateHandle,
private val repository: ProductRepository
) : ViewModel() {
private val _products = MutableLiveData<List<Product>>()
val products: LiveData<List<Product>> = _products
init {
viewModelScope.launch {
try {
_products.value = repository.loadProducts()
} catch (e: Exception) {
savedStateHandle["error"] = e.message
}
}
}
}
常见问题排查:
- 内存泄漏:ViewModel中引用View/Context会导致泄漏,应该使用ApplicationContext
- 状态恢复:对于重要数据,应该通过SavedStateHandle保存
- 协程泄漏:使用viewModelScope会自动取消,但要注意阻塞操作
2.3 Model层的现代实现方案
现代Android开发中,Model层通常包含以下几个部分:
- 数据源(本地数据库、网络API、缓存等)
- Repository作为单一数据入口
- 数据转换器(DTO转Domain Model)
Kotlin的扩展函数和运算符重载在这里大显身手:
kotlin复制class ProductRepository(
private val localDataSource: ProductLocalDataSource,
private val remoteDataSource: ProductRemoteDataSource
) {
suspend fun getProducts(): Flow<List<Product>> {
return flow {
// 优先从缓存读取
emit(localDataSource.getCachedProducts())
// 同时发起网络请求
val freshProducts = remoteDataSource.fetchProducts()
localDataSource.cacheProducts(freshProducts)
emit(freshProducts)
}.map { products ->
// 使用扩展函数转换数据
products.toDomainModel()
}
}
}
3. Kotlin特性在MVVM中的妙用
3.1 密封类处理UI状态
传统Java中常用枚举或常量表示不同状态,Kotlin的密封类提供了更类型安全的方案:
kotlin复制sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<out T>(val data: T) : UiState<T>()
data class Error(val message: String) : UiState<Nothing>()
}
// 在ViewModel中使用
private val _state = MutableLiveData<UiState<List<Product>>>()
val state: LiveData<UiState<List<Product>>> = _state
// 在View层处理
when(val currentState = viewModel.state.value) {
is UiState.Loading -> showProgressBar()
is UiState.Success -> showProducts(currentState.data)
is UiState.Error -> showError(currentState.message)
}
3.2 委托属性简化代码
Kotlin的委托属性可以大幅减少样板代码:
kotlin复制// 自动保存到SavedStateHandle的委托
class SavedStateDelegate<T>(
private val savedStateHandle: SavedStateHandle,
private val key: String,
private val default: T
) : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T {
return savedStateHandle[key] ?: default
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
savedStateHandle[key] = value
}
}
// 使用方式
class MyViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
var searchQuery by SavedStateDelegate(savedStateHandle, "searchQuery", "")
}
3.3 协程与Flow的完美组合
Kotlin协程让异步代码变得同步般简洁:
kotlin复制fun fetchData() {
viewModelScope.launch {
_state.value = UiState.Loading
try {
repository.getProducts()
.catch { e -> _state.value = UiState.Error(e.message) }
.collect { products ->
_state.value = UiState.Success(products)
}
} catch (e: Exception) {
_state.value = UiState.Error(e.message)
}
}
}
4. 实战:电商商品列表完整实现
4.1 项目结构规划
标准的MVVM项目结构应该按功能而非类型划分:
code复制com.example.ecommerce
├── products
│ ├── data
│ │ ├── local
│ │ ├── remote
│ │ └── ProductRepository.kt
│ ├── domain
│ │ └── Product.kt
│ └── presentation
│ ├── ProductActivity.kt
│ ├── ProductViewModel.kt
│ └── adapter
└── di
└── Modules.kt
4.2 依赖注入配置
使用Hilt实现依赖注入:
kotlin复制@Module
@InstallIn(ViewModelComponent::class)
object ProductModule {
@Provides
fun provideProductRepository(
localDataSource: ProductLocalDataSource,
remoteDataSource: ProductRemoteDataSource
): ProductRepository {
return ProductRepository(localDataSource, remoteDataSource)
}
}
@HiltViewModel
class ProductViewModel @Inject constructor(
private val repository: ProductRepository
) : ViewModel()
4.3 分页加载实现
使用Paging 3库实现流畅的分页体验:
kotlin复制class ProductPagingSource(
private val repository: ProductRepository
) : PagingSource<Int, Product>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Product> {
return try {
val page = params.key ?: 1
val response = repository.getProducts(page, params.loadSize)
LoadResult.Page(
data = response.products,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.isLastPage) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
5. 性能优化与调试技巧
5.1 内存泄漏检测
使用Android Studio的Memory Profiler结合LeakCanary:
gradle复制debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
常见泄漏场景:
- ViewModel中持有View引用
- 未取消的协程
- 静态集合持有Activity引用
5.2 数据绑定性能优化
- 使用
@BindingAdapter替代复杂的表达式 - 避免在绑定表达式中进行复杂计算
- 考虑使用
binding.executePendingBindings()强制立即更新
kotlin复制@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String?) {
Glide.with(view.context)
.load(url)
.into(view)
}
5.3 ViewModel测试策略
使用JUnit和MockK进行单元测试:
kotlin复制class ProductViewModelTest {
@get:Rule
val rule = InstantTaskExecutorRule()
private lateinit var viewModel: ProductViewModel
private val mockRepository = mockk<ProductRepository>()
@Before
fun setup() {
viewModel = ProductViewModel(mockRepository)
}
@Test
fun `load products should update state`() = runTest {
val fakeProducts = listOf(Product("1", "Test", 10.0))
coEvery { mockRepository.getProducts() } returns flow { emit(fakeProducts) }
viewModel.fetchData()
val state = viewModel.state.getOrAwaitValue()
assert(state is UiState.Success)
assertEquals(fakeProducts, (state as UiState.Success).data)
}
}
6. 从MVVM到MVI的演进
随着应用复杂度提升,可以考虑引入MVI(Model-View-Intent)架构:
kotlin复制// 状态集中管理
data class ProductState(
val products: List<Product> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
// 单向数据流
class ProductViewModel : ViewModel() {
private val _state = MutableStateFlow(ProductState())
val state: StateFlow<ProductState> = _state
fun processIntent(intent: ProductIntent) {
when(intent) {
is ProductIntent.Load -> loadProducts()
is ProductIntent.Refresh -> refreshProducts()
}
}
private fun loadProducts() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
try {
val products = repository.getProducts()
_state.update { it.copy(products = products, isLoading = false) }
} catch (e: Exception) {
_state.update { it.copy(error = e.message, isLoading = false) }
}
}
}
}
这种架构虽然学习曲线更陡峭,但对于复杂交互场景特别有效,可以完美解决状态同步问题。