从MVP到MVVM的蜕变:Jetpack架构在Wanandroid项目中的最佳实践
你是否还在为Android项目架构选型而困扰?从混乱的MVC到过度设计的MVP,再到如今主流的MVVM,每一次架构演进都伴随着理解与实践的挑战。本文将通过Wanandroid开源项目的实战案例,带你深入理解Kotlin+MVVM+Jetpack架构的精髓,掌握数据驱动UI的开发范式,解决90%的日常开发痛点。
读完本文你将获得:
- 清晰理解MVVM架构在Android中的正确实现方式
- 掌握ViewModel+Repository+DataBinding的数据流转机制
- 学会使用Kotlin协程进行高效异步处理
- 理解依赖注入框架Koin的项目集成技巧
- 获得可直接复用的架构组件代码模板
项目架构全景解析
架构演进历程
Wanandroid项目始于2018年3月,经历了从MVP到MVVM的完整蜕变,目前最新代码基于"mvvm-kotlin"分支构建。项目架构演进过程反映了Android开发社区的技术趋势变化:
timeline
title 项目架构演进时间线
2018-03 : MVP架构初始版本
2019-04 : 引入ViewModel+LiveData
2020-01 : 全面迁移至MVVM+Jetpack
2021-06 : 整合Hilt依赖注入
2022-10 : 采用Jetpack Compose重构UI层
现代MVVM架构分层
项目采用清晰的四层架构设计,严格遵循单一职责原则:
flowchart TD
subgraph 表现层(Presentation)
A[UI组件(Activity/Fragment)]
B[ViewModel]
C[Compose界面]
end
subgraph 领域层(Domain)
D[Repository]
end
subgraph 数据层(Data)
E[Retrofit服务]
F[本地存储]
end
subgraph 基础层(Core)
G[网络模块]
H[工具类]
I[依赖注入]
end
A <--> B
B <--> D
D <--> E
D <--> F
B <--> C
G --> E
H --> A
I --> B
I --> D
各层核心职责:
- 表现层:处理UI逻辑,观察数据变化,不包含业务逻辑
- 领域层:协调数据获取,封装业务逻辑,不依赖Android框架
- 数据层:负责数据的获取和存储,对上层提供统一接口
- 基础层:提供跨层级的通用能力和基础设施
核心架构组件深度剖析
ViewModel:连接UI与数据的桥梁
BaseViewModel作为所有ViewModel的基类,封装了通用的协程调度和UI状态管理能力:
open class BaseViewModel : ViewModel() {
// UI状态封装类
open class UiState<T>(
val isLoading: Boolean = false,
val isRefresh: Boolean = false,
val isSuccess: T? = null,
val isError: String? = null
)
// 异常处理LiveData
val mException: MutableLiveData<Throwable> = MutableLiveData()
// UI线程协程启动器
fun launchOnUI(block: suspend CoroutineScope.() -> Unit) {
viewModelScope.launch { block() }
}
// IO线程协程启动器
suspend fun <T> launchOnIO(block: suspend CoroutineScope.() -> T) {
withContext(Dispatchers.IO) { block() }
}
}
ViewModel实现类通过继承BaseViewModel,专注于特定业务逻辑:
@HiltViewModel
class LoginViewModel @Inject constructor(val repository: LoginRepository) : BaseViewModel() {
// 登录状态
private val _loginState = MutableStateFlow<UiState<User>>(UiState())
val loginState: StateFlow<UiState<User>> = _loginState
fun login(username: String, password: String) {
_loginState.value = UiState(isLoading = true)
launchOnUI {
val result = repository.login(username, password)
_loginState.value = when (result) {
is Success -> UiState(isSuccess = result.data)
is Error -> UiState(isError = result.exception.message)
}
}
}
}
Repository:数据操作的统一入口
BaseRepository封装了网络请求的通用逻辑,提供安全的API调用方式:
open class BaseRepository {
// 安全的API调用封装
suspend fun <T : Any> safeApiCall(
call: suspend () -> Result<T>,
errorMessage: String
): Result<T> {
return try {
call()
} catch (e: Exception) {
// 统一异常处理
Result.Error(IOException(errorMessage, e))
}
}
// 网络响应处理
suspend fun <T : Any> executeResponse(
response: WanResponse<T>,
successBlock: (suspend CoroutineScope.() -> Unit)? = null,
errorBlock: (suspend CoroutineScope.() -> Unit)? = null
): Result<T> {
return coroutineScope {
if (response.errorCode == -1) {
errorBlock?.let { it() }
Result.Error(IOException(response.errorMsg))
} else {
successBlock?.let { it() }
Result.Success(response.data)
}
}
}
}
具体业务仓库继承BaseRepository实现特定数据操作:
class HomeRepository @Inject constructor() : BaseRepository() {
// 获取首页文章列表
suspend fun getArticleList(page: Int): Result<ArticleList> {
return safeApiCall({
executeResponse(WanRetrofitClient.service.getHomeArticle(page)) {
// 可选的成功回调处理
}
}, "获取文章列表失败")
}
// 获取首页Banner
suspend fun getBanners(): Result<List<Banner>> {
return safeApiCall({
executeResponse(WanRetrofitClient.service.getBanner())
}, "获取Banner失败")
}
}
数据模型与状态管理
项目采用密封类(Sealed Class)定义统一的数据状态:
// 数据结果封装
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
// UI状态模型
open class BaseUiModel<T>(
var showLoading: Boolean = false,
var showError: String? = null,
var showSuccess: T? = null,
var showEnd: Boolean = false, // 加载更多结束标记
var isRefresh: Boolean = false // 刷新状态标记
)
关键技术点实战应用
协程与异步处理
项目充分利用Kotlin协程简化异步操作,在BaseViewModel中封装了协程启动方法:
// ViewModel中启动协程
fun loadArticleData() {
launchOnUI {
// 切换到IO线程执行网络请求
val result = withContext(Dispatchers.IO) {
homeRepository.getArticleList(currentPage)
}
// 根据结果更新UI状态
when (result) {
is Success -> {
_uiState.value = _uiState.value.copy(
showLoading = false,
showSuccess = result.data,
isRefresh = false
)
}
is Error -> {
_uiState.value = _uiState.value.copy(
showLoading = false,
showError = result.exception.message,
isRefresh = false
)
}
}
}
}
依赖注入配置
使用Koin实现依赖注入,AppModule集中管理依赖提供:
val appModule = module {
// 单例提供CoroutinesDispatcherProvider
single { CoroutinesDispatcherProvider() }
// 仓库实例
single { HomeRepository() }
single { LoginRepository() }
single { SquareRepository() }
// ViewModel
viewModel { LoginViewModel(get()) }
viewModel { HomeViewModel(get()) }
viewModel { SquareViewModel(get()) }
}
在Application中初始化Koin:
class App : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@App)
modules(appModule)
}
}
}
项目核心功能模块解析
网络请求流程
sequenceDiagram
participant ViewModel
participant Repository
participant Retrofit
participant API
ViewModel->>Repository: 获取文章列表(page=0)
Repository->>Retrofit: 创建API服务
Retrofit->>API: 发起网络请求
API-->>Retrofit: 返回JSON数据
Retrofit-->>Repository: 解析为实体对象
Repository-->>ViewModel: 封装为Result对象
ViewModel-->>UI: 更新UI状态
核心代码实现:
// 基础Retrofit客户端
open class BaseRetrofitClient {
// 创建Retrofit实例
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
}
// 创建服务实例
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass)
}
// WanAndroid API服务
interface WanService {
@GET("article/list/{page}/json")
suspend fun getHomeArticle(@Path("page") page: Int): WanResponse<ArticleList>
@GET("banner/json")
suspend fun getBanner(): WanResponse<List<Banner>>
@FormUrlEncoded
@POST("user/login")
suspend fun login(
@Field("username") username: String,
@Field("password") password: String
): WanResponse<User>
}
文章列表功能实现
文章列表是项目中最核心的功能之一,涉及下拉刷新、上拉加载、收藏等操作:
@Composable
fun HomePage(viewModel: HomeViewModel = hiltViewModel(), onClickArticle: (Article) -> Unit) {
val uiState by viewModel.uiState.collectAsState()
val scope = rememberCoroutineScope()
val refreshState = rememberSwipeRefreshState(isRefreshing = uiState.isRefresh)
SwipeRefresh(
state = refreshState,
onRefresh = { viewModel.refresh() }
) {
LazyColumn {
items(uiState.articles) { article ->
ArticleItem(
article = article,
onItemClick = { onClickArticle(article) },
onCollectClick = { viewModel.collectArticle(article.id) }
)
}
// 加载更多
item {
if (uiState.showLoading) {
LoadingItem()
}
}
}
}
// 错误提示
if (uiState.showError != null) {
ErrorDialog(message = uiState.showError!!) {
viewModel.retry()
}
}
}
最佳实践与避坑指南
MVVM架构常见误区
- 过度使用DataBinding:DataBinding适合简单绑定,复杂逻辑应放在ViewModel中处理
- ViewModel持有View引用:可能导致内存泄漏,应使用LiveData/StateFlow通知UI更新
- Repository层职责不清晰:不应包含UI相关逻辑,专注于数据获取与处理
- 忽略异常处理:每个网络请求都应进行异常捕获,提供友好错误提示
性能优化建议
- 列表优化:使用LazyColumn实现RecyclerView功能,减少视图创建开销
- 图片加载:使用Coil或Glide进行图片缓存和懒加载
- 数据缓存:对频繁访问的网络数据进行本地缓存,减少网络请求
- 避免过度绘制:简化布局层级,减少透明背景使用
- 合理使用协程:避免在循环中创建协程,使用withContext切换线程而非launch
项目快速上手指南
环境要求
- Android Studio Arctic Fox或更高版本
- Kotlin 1.6.0或更高版本
- Gradle 7.0或更高版本
- Android SDK 21或更高版本
项目构建步骤
# 克隆项目仓库
git clone https://gitcode.com/gh_mirrors/wan/wanandroid.git
# 进入项目目录
cd wanandroid
# 构建项目
./gradlew build
# 安装应用到设备
./gradlew installDebug
目录结构说明
app/
├── src/main/java/luyao/wanandroid/
│ ├── base/ # 基础组件
│ ├── model/ # 数据模型和仓库
│ ├── ui/ # 界面组件
│ ├── util/ # 工具类
│ └── App.kt # 应用入口
├── res/ # 资源文件
└── AndroidManifest.xml # 应用配置
总结与展望
Wanandroid项目展示了如何在实际开发中正确应用MVVM架构,通过ViewModel+Repository+DataBinding的组合,实现了数据与UI的解耦,提高了代码的可维护性和可测试性。项目中大量使用的Kotlin特性和Jetpack组件,代表了现代Android开发的最佳实践。
随着Jetpack Compose的成熟,项目UI层正逐步从XML布局迁移到Compose,未来将实现全Compose架构。同时,Kotlin协程和Flow的深入应用,将进一步简化异步编程,提升应用性能。
掌握本文介绍的架构思想和实践技巧,你将能够构建出更加健壮、可维护的Android应用,从容应对各种复杂业务场景。建议将这些最佳实践应用到你的项目中,并根据具体需求进行适当调整和优化。
kernelopenEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。C0105
baihu-dataset异构数据集“白虎”正式开源——首批开放10w+条真实机器人动作数据,构建具身智能标准化训练基座。00
mindquantumMindQuantum is a general software library supporting the development of applications for quantum computation.Python059
PaddleOCR-VLPaddleOCR-VL 是一款顶尖且资源高效的文档解析专用模型。其核心组件为 PaddleOCR-VL-0.9B,这是一款精简却功能强大的视觉语言模型(VLM)。该模型融合了 NaViT 风格的动态分辨率视觉编码器与 ERNIE-4.5-0.3B 语言模型,可实现精准的元素识别。Python00
GLM-4.7GLM-4.7上线并开源。新版本面向Coding场景强化了编码能力、长程任务规划与工具协同,并在多项主流公开基准测试中取得开源模型中的领先表现。 目前,GLM-4.7已通过BigModel.cn提供API,并在z.ai全栈开发模式中上线Skills模块,支持多模态任务的统一规划与协作。Jinja00
AgentCPM-Explore没有万亿参数的算力堆砌,没有百万级数据的暴力灌入,清华大学自然语言处理实验室、中国人民大学、面壁智能与 OpenBMB 开源社区联合研发的 AgentCPM-Explore 智能体模型基于仅 4B 参数的模型,在深度探索类任务上取得同尺寸模型 SOTA、越级赶上甚至超越 8B 级 SOTA 模型、比肩部分 30B 级以上和闭源大模型的效果,真正让大模型的长程任务处理能力有望部署于端侧。Jinja00