首页
/ 从MVP到MVVM的蜕变:Jetpack架构在Wanandroid项目中的最佳实践

从MVP到MVVM的蜕变:Jetpack架构在Wanandroid项目中的最佳实践

2026-01-18 09:57:20作者:宣聪麟

你是否还在为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架构常见误区

  1. 过度使用DataBinding:DataBinding适合简单绑定,复杂逻辑应放在ViewModel中处理
  2. ViewModel持有View引用:可能导致内存泄漏,应使用LiveData/StateFlow通知UI更新
  3. Repository层职责不清晰:不应包含UI相关逻辑,专注于数据获取与处理
  4. 忽略异常处理:每个网络请求都应进行异常捕获,提供友好错误提示

性能优化建议

  1. 列表优化:使用LazyColumn实现RecyclerView功能,减少视图创建开销
  2. 图片加载:使用Coil或Glide进行图片缓存和懒加载
  3. 数据缓存:对频繁访问的网络数据进行本地缓存,减少网络请求
  4. 避免过度绘制:简化布局层级,减少透明背景使用
  5. 合理使用协程:避免在循环中创建协程,使用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应用,从容应对各种复杂业务场景。建议将这些最佳实践应用到你的项目中,并根据具体需求进行适当调整和优化。

登录后查看全文
热门项目推荐
相关项目推荐