1. 为什么我们需要关注Android架构?
三年前我刚接手一个遗留的Android项目时,曾经被满屏的Activity代码震惊过——业务逻辑、网络请求、数据库操作全部挤在同一个文件里,每次修改都像在拆炸弹。这种经历让我深刻认识到,良好的架构设计不是奢侈品,而是维护大型项目的必需品。
Android架构本质上是一套代码组织规范,它决定了不同功能模块如何划分、如何通信。就像建造房屋需要设计图纸一样,开发Android应用也需要清晰的架构蓝图。随着项目规模扩大,合理的架构能带来三个核心优势:
- 可维护性:新成员能在几天内理解代码结构,而不是几周
- 可测试性:业务逻辑可以脱离界面单独测试
- 可扩展性:新增功能时不会引发连锁反应式的修改
2. Android架构演进史与主流方案
2.1 从传统MVC到现代架构
早期的Android开发普遍采用MVC模式,但这种架构存在明显的缺陷。以用户登录功能为例:
// 典型MVC反例 class LoginActivity extends Activity { void onClick() { String username = editUsername.getText().toString(); String password = editPassword.getText().toString(); // 业务逻辑与界面耦合 if(username.isEmpty() || password.isEmpty()) { Toast.makeText(this, "输入不能为空", Toast.LENGTH_SHORT).show(); return; } // 直接处理网络请求 ApiClient.login(username, password, new Callback() { void onSuccess(User user) { // 直接操作数据库 Database.saveUser(user); startActivity(new Intent(this, MainActivity.class)); } }); } }这种写法导致Activity迅速膨胀到数千行代码。Google在2017年推出的Android架构组件,标志着官方架构方案的诞生。
2.2 现代架构核心组件
一个完整的现代Android架构通常包含以下关键部件:
| 组件 | 职责 | 官方包名 |
|---|---|---|
| ViewModel | 管理界面相关数据 | androidx.lifecycle |
| LiveData | 可观察的数据持有者 | androidx.lifecycle |
| Room | SQLite数据库抽象层 | androidx.room |
| WorkManager | 后台任务调度 | androidx.work |
| Hilt | 依赖注入框架 | dagger.hilt |
3. 分层架构深度解析
3.1 典型四层架构实现
现代Android应用通常采用分层架构,这里以电商应用的商品详情页为例:
com.example.ecommerce ├── data # 数据层 │ ├── local # 本地数据源(Room) │ ├── remote # 远程数据源(Retrofit) │ └── repository # 数据仓库(统一数据入口) ├── domain # 领域层 │ ├── model # 业务模型 │ └── usecase # 业务用例 ├── presentation # 表现层 │ ├── view # 界面(Activity/Fragment) │ └── viewmodel # 界面逻辑控制器 └── di # 依赖注入配置数据层实现示例:
// 数据源接口 interface ProductDataSource { suspend fun getProduct(id: String): Product } // 远程数据源实现 class RemoteProductDataSource @Inject constructor( private val api: EcommerceApi ) : ProductDataSource { override suspend fun getProduct(id: String) = api.getProduct(id) } // 本地数据源实现 @Dao interface ProductDao { @Query("SELECT * FROM products WHERE id = :id") suspend fun getProduct(id: String): ProductEntity? } // 仓库实现 class ProductRepository @Inject constructor( private val remote: RemoteProductDataSource, private val local: ProductDao ) { suspend fun getProduct(id: String): Product { val localProduct = local.getProduct(id) if (localProduct != null) { return localProduct.toDomainModel() } val remoteProduct = remote.getProduct(id) local.insert(remoteProduct.toEntity()) return remoteProduct } }3.2 领域层设计要点
领域层是业务逻辑的核心所在,良好的设计应该:
- 使用纯Kotlin/Java类,不依赖Android框架
- 每个UseCase只完成一个特定业务操作
- 通过接口与数据层通信
class GetProductDetailsUseCase @Inject constructor( private val repository: ProductRepository ) { suspend operator fun invoke(productId: String): Result<Product> { return try { Result.success(repository.getProduct(productId)) } catch (e: Exception) { Result.failure(e) } } }4. 状态管理与UI更新
4.1 UI状态规范化
定义明确的UI状态模型是避免界面混乱的关键:
sealed class ProductDetailState { object Loading : ProductDetailState() data class Success(val product: Product) : ProductDetailState() data class Error(val message: String) : ProductDetailState() } class ProductViewModel @Inject constructor( private val getProductDetails: GetProductDetailsUseCase ) : ViewModel() { private val _state = MutableStateFlow<ProductDetailState>(ProductDetailState.Loading) val state: StateFlow<ProductDetailState> = _state fun loadProduct(productId: String) { viewModelScope.launch { _state.value = ProductDetailState.Loading getProductDetails(productId) .onSuccess { product -> _state.value = ProductDetailState.Success(product) } .onFailure { e -> _state.value = ProductDetailState.Error(e.message ?: "Unknown error") } } } }4.2 界面状态处理技巧
在Fragment中处理状态时,推荐使用repeatOnLifecycle避免资源浪费:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.state.collect { state -> when (state) { is ProductDetailState.Loading -> showLoading() is ProductDetailState.Success -> showProduct(state.product) is ProductDetailState.Error -> showError(state.message) } } } } }5. 依赖注入最佳实践
5.1 Hilt模块配置
分层架构下,依赖注入配置也应分层组织:
// 数据层模块 @Module @InstallIn(SingletonComponent::class) object DataModule { @Provides @Singleton fun provideEcommerceApi(): EcommerceApi { return Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(MoshiConverterFactory.create()) .build() .create(EcommerceApi::class.java) } } // 领域层模块 @Module @InstallIn(ViewModelComponent::class) object DomainModule { @Provides fun provideGetProductDetailsUseCase(repository: ProductRepository): GetProductDetailsUseCase { return GetProductDetailsUseCase(repository) } }5.2 构造函数注入的优势
相比字段注入,构造函数注入有以下优势:
- 依赖关系显式声明
- 便于测试(可以直接传入mock对象)
- 保证对象创建时依赖已就绪
// 推荐写法 class ProductRepository @Inject constructor( private val remote: RemoteProductDataSource, private val local: ProductDao ) { /*...*/ } // 不推荐写法 class ProductRepository { @Inject lateinit var remote: RemoteProductDataSource @Inject lateinit var local: ProductDao }6. 测试策略与实施
6.1 分层测试金字塔
合理的测试应该遵循金字塔结构:
UI测试 (10%) / \ Domain测试 (20%) / \ Data层测试 (70%)6.2 ViewModel测试示例
使用JUnit和MockK进行ViewModel测试:
class ProductViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() private lateinit var viewModel: ProductViewModel private val getProductDetails = mockk<GetProductDetailsUseCase>() @Before fun setup() { viewModel = ProductViewModel(getProductDetails) } @Test fun `load product should emit success state`() = runTest { val product = Product(id = "1", name = "Test Product") coEvery { getProductDetails(any()) } returns Result.success(product) viewModel.loadProduct("1") viewModel.state.test { assertThat(awaitItem()).isInstanceOf(ProductDetailState.Loading::class.java) val success = awaitItem() as ProductDetailState.Success assertThat(success.product).isEqualTo(product) cancelAndIgnoreRemainingEvents() } } }7. 常见架构误区与解决方案
7.1 ViewModel滥用问题
错误场景:
- 在ViewModel中直接处理业务逻辑
- 将Android相关代码(如Context)引入ViewModel
正确做法:
- 业务逻辑放在UseCase中
- 界面相关操作(如导航)通过事件暴露给View
// 错误示例 class WrongViewModel : ViewModel() { fun login(username: String, password: String) { // 不应该直接处理业务逻辑 if (username.isEmpty()) { // 不应该直接处理界面逻辑 showToast("用户名不能为空") return } // 网络请求... } } // 正确示例 class CorrectViewModel @Inject constructor( private val loginUseCase: LoginUseCase ) : ViewModel() { private val _events = Channel<LoginEvent>() val events = _events.receiveAsFlow() fun login(username: String, password: String) { viewModelScope.launch { loginUseCase(username, password) .onSuccess { _events.send(LoginEvent.NavigateToHome) } .onFailure { e -> _events.send(LoginEvent.ShowError(e.message)) } } } sealed class LoginEvent { object NavigateToHome : LoginEvent() data class ShowError(val message: String?) : LoginEvent() } }7.2 过度设计警告
架构设计需要权衡,对于简单场景:
- 单功能工具类应用可能不需要完整分层
- 原型验证阶段可以适当简化
- 团队规模小于3人时可降低架构复杂度
判断标准:当修改一个功能需要同时改动多个不相关文件时,说明架构需要调整了。
8. 架构演进与适配
8.1 从MVP迁移到MVVM
遗留项目迁移建议分步进行:
- 先引入ViewModel替换Presenter
- 用LiveData/Flow替换回调接口
- 逐步拆分数据层
- 最后引入UseCase层
8.2 响应式编程适配
对于复杂交互场景,可以考虑使用响应式编程:
class SearchViewModel @Inject constructor( private val searchUseCase: SearchUseCase ) : ViewModel() { private val queryFlow = MutableStateFlow("") val results: Flow<SearchResult> = queryFlow .debounce(300) .distinctUntilChanged() .flatMapLatest { query -> searchUseCase(query) .catch { emit(SearchResult.Error(it)) } } fun onQueryChanged(query: String) { queryFlow.value = query } }9. 性能优化要点
9.1 数据库访问优化
使用Room时注意:
- 主线程禁止数据库写操作
- 复杂查询使用
@Transaction - 大数据集分页加载
@Dao interface ProductDao { @Transaction @Query("SELECT * FROM products WHERE category = :category") suspend fun getProductsByCategory(category: String): List<ProductWithDetails> @Query("SELECT * FROM products WHERE category = :category LIMIT :pageSize OFFSET :offset") suspend fun getProductsByCategoryPaged(category: String, pageSize: Int, offset: Int): List<Product> }9.2 网络请求优化
- 使用OkHttp拦截器实现统一缓存策略
- 合并短时间内的重复请求
- 优先使用Flow替代LiveData获取网络数据
class CacheInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() val cacheControl = CacheControl.Builder() .maxAge(1, TimeUnit.HOURS) .build() return chain.proceed( request.newBuilder() .cacheControl(cacheControl) .build() ) } }10. 模块化架构进阶
10.1 功能模块拆分原则
大型项目应按功能垂直拆分:
:app (空壳) ├── :feature-home ├── :feature-product ├── :feature-user └── :core (公共基础库)每个功能模块包含自己的:
- 界面层
- 领域层
- 数据层实现
10.2 模块间通信方案
推荐使用深链接导航:
// 在core模块定义导航契约 interface AppNavigator { fun navigateToProductDetail(productId: String) } // 产品模块实现导航 class ProductNavigator @Inject constructor() : AppNavigator { override fun navigateToProductDetail(productId: String) { val intent = Intent(context, ProductDetailActivity::class.java).apply { putExtra("product_id", productId) } context.startActivity(intent) } } // 其他模块通过接口调用 class HomeViewModel @Inject constructor( private val navigator: AppNavigator ) : ViewModel() { fun onProductClicked(productId: String) { navigator.navigateToProductDetail(productId) } }11. 架构设计中的经验之谈
- 渐进式改进:不要试图一次性重构整个项目,每次只改进一个模块
- 文档先行:架构图应该早于代码实现,用图表明确各层职责边界
- 团队共识:确保所有成员理解并遵循同一套架构规范
- 工具辅助:使用Android Studio的Architecture Template生成标准结构
- 性能监控:在关键流程添加性能埋点,及时发现架构瓶颈
一个我亲身经历的教训:曾经在一个电商项目中,因为没有严格控制ViewModel的职责范围,导致某个包含复杂业务逻辑的ViewModel膨胀到2000多行代码。后来我们通过以下步骤解决了这个问题:
- 识别出ViewModel中的业务逻辑块
- 为每个重要业务操作创建独立的UseCase
- 使用Kotlin的operator overloading简化UseCase调用
- 编写单元测试保证重构安全性
整个过程耗时两周,但后续的开发效率提升了40%以上,新功能的添加时间平均缩短了三分之一。