Trae搭建Android 开发中 MVVM 架构,使用指南

我理解你需要一份关于如何搭建 Android MVVM 架构的详细指南。下面我将为你进行详细的解释。

一、MVVM 架构简介

MVVM(Model-View-ViewModel)是一种软件架构模式,将应用分为三个核心组件:
  • Model:负责数据处理(网络请求、数据库操作等),包含数据模型和业务逻辑。
  • View:负责 UI 展示与用户交互(Activity/Fragment、XML 布局),通过观察 ViewModel 获取数据。
  • ViewModel:作为 View 与 Model 的桥梁,存储 UI 相关数据,处理 View 触发的业务逻辑,且生命周期独立于 View。
核心优势:
  • 低耦合:View 与 Model 分离,互不依赖。
  • 数据驱动:ViewModel 通知 View 数据变化,View 只需更新 UI。
  • 生命周期安全:ViewModel 生命周期长于 View,可避免内存泄漏。
  • 可测试性:各组件独立,便于单元测试。

二、核心依赖库

MVVM 架构依赖 Android 官方组件库(Jetpack)实现,需在 build.gradle 中添加以下依赖(以 AndroidX 为例):
// 基础组件
implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "com.google.android.material:material:1.11.0"

// ViewModel + LiveData(数据观察)
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.7.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"

// Lifecycle(生命周期感知)
implementation "androidx.lifecycle:lifecycle-common-java8:2.7.0"

// Retrofit(网络请求,替代 Volley)
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0" // Gson 解析
implementation "com.squareup.okhttp3:okhttp:4.11.0" // OkHttp 支持

// Room(本地数据库,可选)
implementation "androidx.room:room-runtime:2.6.1"
kapt "androidx.room:room-compiler:2.6.1"
implementation "androidx.room:room-ktx:2.6.1" // Kotlin 扩展

// Coroutines(异步处理,替代 RxJava)
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"

// Glide(图片加载,可选)
implementation "com.github.bumptech.glide:glide:4.16.0"
kapt "com.github.bumptech.glide:compiler:4.16.0"
说明:
  • 推荐使用 Kotlin 语言,配合 Coroutines 实现异步操作,代码更简洁。
  • LiveData 用于 ViewModel 向 View 发送数据更新通知(生命周期感知)。
  • Retrofit + OkHttp 负责网络请求,Room 负责本地数据持久化。

三、项目结构设计

按 MVVM 分层思想,推荐以下项目结构(模块化清晰):
com.example.mvvmapp
├── data                // Model 层:数据处理相关
│   ├── api             // 网络请求(Retrofit 接口、服务端模型)
│   │   ├── ApiService.kt   // Retrofit 接口定义
│   │   └── model       // 服务端返回数据模型(如 UserResponse.kt)
│   ├── db              // 本地数据库(Room 实体、DAO)
│   │   ├── AppDatabase.kt  // Room 数据库实例
│   │   ├── dao         // 数据访问对象(如 UserDao.kt)
│   │   └── entity      // 数据库实体(如 UserEntity.kt)
│   └── repository      // 仓库:统一数据来源(网络 + 本地)
│       └── UserRepository.kt
├── ui                  // View 层:UI 相关
│   ├── activity        // 活动(如 LoginActivity.kt)
│   ├── fragment        // 碎片(如 HomeFragment.kt)
│   └── adapter         // 列表适配器(如 UserAdapter.kt)
├── viewmodel           // ViewModel 层:业务逻辑与数据存储
│   └── UserViewModel.kt
├── util                // 工具类(网络判断、Toast 工具等)
│   └── NetworkUtil.kt
└── App.kt              // 应用全局上下文
核心原则:
  • 上层依赖下层,下层不依赖上层(如 View 依赖 ViewModel,ViewModel 依赖 Repository,Repository 依赖 Model)。
  • 数据流动:View → ViewModel → Repository → Model(网络 / 数据库)→ Repository → ViewModel → View。

四、各层详细实现步骤

1. Model 层:数据处理

Model 层负责数据的获取与存储,核心是 Repository(仓库模式),统一管理网络和本地数据。
(1)网络请求(Retrofit)
  • 步骤 1:定义 API 接口(ApiService.kt
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query

interface ApiService {
    // 示例:获取用户列表
    @GET("users")
    suspend fun getUsers(@Query("page") page: Int): Response<UserResponse>
}

 

  • 步骤 2:创建 Retrofit 实例(RetrofitClient.kt
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
    private const val BASE_URL = "https://api.example.com/"

    val apiService: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}
  • 步骤 3:定义数据模型(UserResponse.kt
data class UserResponse(
    val data: List<User>,
    val page: Int,
    val total: Int
)

data class User(
    val id: Int,
    val name: String,
    val email: String
)
(2)本地数据库(Room)
  • 步骤 1:定义实体类(UserEntity.kt
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: Int,
    val name: String,
    val email: String
)
  • 步骤 2:定义 DAO(UserDao.kt
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import kotlinx.coroutines.flow.Flow

@Dao
interface UserDao {
    @Insert suspend fun insertAll(users: List<UserEntity>)

    @Query("SELECT * FROM users")
    fun getLocalUsers(): Flow<List<UserEntity>> // Flow 用于观察数据变化
}

骤 3:创建数据库实例(AppDatabase.kt

import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context

@Database(entities = [UserEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao

    companion object {
        // 单例模式
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}
(3)仓库(Repository)
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map

class UserRepository(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    // 获取用户列表:优先网络,失败则读取本地
    suspend fun getUsers(page: Int): Flow<Result<List<User>>> = flow {
        emit(Result.Loading) // 发送加载状态
        try {
            val response = apiService.getUsers(page)
            if (response.isSuccessful) {
                val users = response.body()?.data ?: emptyList()
                // 网络数据存入本地
                userDao.insertAll(users.map { it.toEntity() })
                emit(Result.Success(users))
            } else {
                emit(Result.Error("网络请求失败"))
            }
        } catch (e: Exception) {
            // 网络异常,读取本地数据
            val localUsers = userDao.getLocalUsers().first() // 阻塞获取一次
            if (localUsers.isNotEmpty()) {
                emit(Result.Success(localUsers.map { it.toModel() }))
            } else {
                emit(Result.Error(e.message ?: "未知错误"))
            }
        }
    }

    // User 转 UserEntity(扩展函数)
    private fun User.toEntity(): UserEntity {
        return UserEntity(id = id, name = name, email = email)
    }

    // UserEntity 转 User
    private fun UserEntity.toModel(): User {
        return User(id = id, name = name, email = email)
    }

    // 数据状态密封类
    sealed class Result<out T> {
        object Loading : Result<Nothing>()
        data class Success<out T>(val data: T) : Result<T>()
        data class Error(val message: String) : Result<Nothing>()
    }
}

2. ViewModel 层:业务逻辑

ViewModel 持有 Repository 实例,处理 View 触发的业务逻辑,通过 LiveData/StateFlow 向 View 发送数据。
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class UserViewModel(
    private val userRepository: UserRepository
) : ViewModel() {
    // 私有 MutableStateFlow(可修改),对外暴露不可修改的 StateFlow
    private val _usersState = MutableStateFlow<UserRepository.Result<List<User>>>(UserRepository.Result.Loading)
    val usersState: StateFlow<UserRepository.Result<List<User>>> = _usersState

    // 加载用户列表
    fun loadUsers(page: Int) {
        viewModelScope.launch { // 自动绑定 ViewModel 生命周期,避免内存泄漏
            userRepository.getUsers(page).collect { result ->
                _usersState.value = result // 更新状态
            }
        }
    }
}
说明:
  • 使用 StateFlow 替代 LiveData(Kotlin 推荐,支持协程,更灵活)。
  • viewModelScope 是 ViewModel 专用协程作用域,当 ViewModel 销毁时自动取消协程。

3. View 层:UI 展示

View 层(Activity/Fragment)观察 ViewModel 的数据变化,更新 UI 并处理用户交互。
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.example.mvvmapp.databinding.ActivityUserBinding
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

class UserActivity : AppCompatActivity() {
    private lateinit var binding: ActivityUserBinding
    private lateinit var userViewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityUserBinding.inflate(layoutInflater) // View Binding
        setContentView(binding.root)

        // 初始化 ViewModel(通过 ViewModelProvider 确保生命周期安全)
        val userRepository = UserRepository(
            RetrofitClient.apiService,
            AppDatabase.getDatabase(this).userDao()
        )
        userViewModel = ViewModelProvider(this) {
            UserViewModel(userRepository)
        }[UserViewModel::class.java]

        // 观察 ViewModel 数据变化
        observeUsersState()

        // 加载数据(用户交互触发,如按钮点击)
        binding.btnLoad.setOnClickListener {
            userViewModel.loadUsers(page = 1)
        }
    }

    private fun observeUsersState() {
        lifecycleScope.launch { // 绑定 Activity 生命周期
            userViewModel.usersState.collect { result ->
                when (result) {
                    is UserRepository.Result.Loading -> {
                        binding.progressBar.visibility = View.VISIBLE
                        binding.tvError.visibility = View.GONE
                    }
                    is UserRepository.Result.Success -> {
                        binding.progressBar.visibility = View.GONE
                        binding.rvUsers.adapter = UserAdapter(result.data) // 更新列表
                    }
                    is UserRepository.Result.Error -> {
                        binding.progressBar.visibility = View.GONE
                        binding.tvError.text = result.message
                        binding.tvError.visibility = View.VISIBLE
                    }
                }
            }
        }
    }
}
(2)布局文件(activity_user.xml
使用 View Binding 简化视图操作:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <Button
        android:id="@+id/btnLoad"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="加载用户列表" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone" />

    <TextView
        android:id="@+id/tvError"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/red"
        android:visibility="gone" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvUsers"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
(3)列表适配器(UserAdapter.kt
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.mvvmapp.databinding.ItemUserBinding

class UserAdapter : ListAdapter<User, UserAdapter.UserViewHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val binding = ItemUserBinding.inflate(
            LayoutInflater.from(parent.context),
            parent,
            false
        )
        return UserViewHolder(binding)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = getItem(position)
        holder.binding.tvName.text = user.name
        holder.binding.tvEmail.text = user.email
    }

    class UserViewHolder(val binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root)

    // DiffUtil:优化列表更新(只刷新变化项)
    class DiffCallback : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }
    }
}

五、关键注意事项

  1. 生命周期安全:
    • 避免在 ViewModel 中持有 View 引用(如 Activity/Fragment),如需 Context,使用 AndroidViewModel 并传入 Application
    • 网络请求、数据库操作必须在协程中执行,避免阻塞主线程。
  2. 数据状态管理:
    • 使用密封类(如 Result)统一管理数据的加载、成功、失败状态,View 只需根据状态更新 UI。
  3. 依赖注入(可选):
    • 复杂项目推荐使用 Hilt 或 Koin 实现依赖注入,替代手动创建 Repository 和 ViewModel,简化代码并提高可测试性。
  4. 错误处理:
    • 网络异常、数据库操作失败需统一捕获,通过 ViewModel 通知 View 显示错误信息。
  5. 性能优化:
    • 列表使用 DiffUtil 优化更新效率。
    • 图片加载使用 Glide 并设置占位图、缓存策略。

六、总结

MVVM 架构通过分层设计实现了代码的低耦合、高可维护性,结合 Jetpack 组件(ViewModel、LiveData/StateFlow、Room)和 Kotlin 协程,可快速搭建健壮的 Android 应用。核心是数据驱动 UI和生命周期安全,遵循 “View 观察 ViewModel,ViewModel 调用 Repository,Repository 封装数据来源” 的数据流模式。
 
 
posted @ 2025-11-24 21:01  福寿螺888  阅读(19)  评论(0)    收藏  举报