🧭 阶段 1:强化体系与架构思维(为期 4 周)
🎯 目标
从 "写模块" → "设计架构"。
你将掌握 Clean Architecture 的思维方式,能独立设计 MVVM + UseCase + Repository 的分层结构,打造企业级架构模板。
🧩 一、Clean Architecture 思维结构图
📱 UI 层(View / Compose) ↓ 事件、状态 🧠 ViewModel 层(State + Event + Effect) ↓ 调用 UseCase ⚙️ Domain 层(UseCase) ↓ 访问数据接口 💾 Data 层(Repository + DataSource) ↙️ 本地 LocalDataSource(Room / MMKV) ↘️ 网络 RemoteDataSource(Retrofit / WebSocket)
关键原则:
-
ViewModel 不直接依赖 Data 层,只依赖 UseCase(接口)
-
UseCase 只做单一业务逻辑
-
Repository 负责数据源合并(网络 + 本地)
-
各层通信通过 Kotlin 协程 + Flow
-
数据流单向:UI → VM → UseCase → Repo → 回流至 UI
📆 二、4 周学习计划(含每日任务)
第 1 周:架构基础与 Kotlin 强化
🎯 理解分层思想 + Kotlin 能力打牢
| 日期 | 学习任务 |
|---|---|
| 周一 | 阅读《Clean Architecture 原则》概要(理解 Domain / Data / UI 分离) |
| 周二 | Kotlin 高级语法复习:sealed class、Flow、inline、crossinline |
| 周三 | 理解「单向数据流(UDF)」:UIState / UIEvent / UIEffect 模型 |
| 周四 | 实战:封装 BaseViewModel<State, Event, Effect> 基类 |
| 周五 | 实战:创建 UiState 管理类,配合 StateFlow 驱动 UI |
| 周末 | 项目练习:把你一个旧页面改造成 UDF 架构(比如录音界面) |
第 2 周:Repository + UseCase 分层实践
🎯 将 ViewModel 逻辑下沉为业务用例(UseCase)
| 日期 | 学习任务 |
|---|---|
| 周一 | 理解 UseCase 概念:单一职责、依赖倒置 |
| 周二 | 实战:编写 GetUserInfoUseCase 与 UpdateUserInfoUseCase |
| 周三 | 封装 Repository 接口与实现类(含本地与远程数据源) |
| 周四 | 编写 LocalDataSource(Room / MMKV) |
| 周五 | 编写 RemoteDataSource(Retrofit + Coroutine) |
| 周末 | 实战整合:实现一个 "用户资料中心" 模块(含离线缓存) |
第 3 周:模块化与依赖管理
🎯 将架构结构扩展到多模块项目,掌握依赖注入思想。
| 日期 | 学习任务 |
|---|---|
| 周一 | Gradle 多模块结构讲解(app / core / feature / data) |
| 周二 | 学习 Hilt(依赖注入)与 Repository 注入实践 |
| 周三 | 拆分 "网络模块" 与 "本地存储模块" |
| 周四 | 使用 Hilt 绑定 Repository 实现到接口 |
| 周五 | 编写一个跨模块的 UseCase(比如 UploadVoiceUseCase) |
| 周末 | 练习:将语音录制模块改造成独立 feature module,可独立注入使用 |
第 4 周:统一 UI 架构与可复用模板
🎯 打造自己的「架构脚手架」。
| 日期 | 学习任务 |
|---|---|
| 周一 | 整理项目结构与包命名规范(domain/data/ui) |
| 周二 | 封装通用的 BaseRepository、Result(Success/Error/Loading)模型 |
| 周三 | 扩展 BaseViewModel 支持加载状态、错误捕获、重试机制 |
| 周四 | 创建模板工程脚本(通过 Gradle 自动生成新模块结构) |
| 周五 | 优化 UIState 模型,支持多页面复用 |
| 周末 | 实战项目验收:创建完整模块(录音上传 + 播放 + 状态流 + Repository) |
💡 三、实战项目建议
| 项目名称 | 实战目标 |
|---|---|
| 🎙️ VoiceManager Clean Demo | 把之前的语音录制模块完全重构成 Clean 架构:UI → ViewModel → UseCase → Repository。 |
| 💬 IM Session 模块重构 | 对聊天列表模块实现 UseCase 分层,Room 缓存 + WebSocket 同步。 |
| 📷 Media Gallery 模块 | 拆分数据源(本地图库 + 网络资源),展示图片加载优化。 |
每个模块最后都形成:
domain/ usecase/ data/ repository/ datasource/ ui/ viewmodel/ activity/
📚 四、推荐学习资料
| 类型 | 名称 | 说明 |
|---|---|---|
| 📖 官方文档 | Guide to app architecture (Google) | 官方架构最佳实践 |
| 📘 教程 | 「Android Clean Architecture with Kotlin」by MindOrks | 实战导向的 Clean 架构入门 |
| 📹 视频 | Philipp Lackner - Clean Architecture 系列(YouTube) | 讲解清晰、含 Flow + Hilt |
| 📗 源码 | nowinandroid (Google 官方 App) | 最完整的 Clean + Compose 实践 |
| 🧠 文章 | Medium: "Why UseCase is key in MVVM" | 深入理解 UseCase 在大型项目的作用 |
🏁 阶段完成标志
完成以下 3 项后,你将顺利晋升到「架构中级」阶段👇:
-
拥有可复用的
BaseViewModel+UIState模板 -
任意模块(如语音录制、聊天列表)实现完整的 Clean 架构
-
能清晰画出项目层次依赖图并独立设计新的模块结构
我接下来可以帮你生成:
-
🧱 第 1 周「架构基础 + BaseViewModel + UIState 实战模板」的完整代码 Demo
-
包含:
BaseViewModel、UiState、UiEvent、UiEffect、ViewModel 使用样例
是否要我直接生成?(✅ 是的,生成第 1 周完整 Demo 模板)
您说:
是
ChatGPT 说:
太好了 👍
我们现在正式进入 阶段 1·第 1 周:架构基础 + BaseViewModel + UIState 实战模板 🚀
这一周的目标是构建一个能在任意项目中通用的「基础架构骨架」:
MVVM + UDF 单向数据流模式(State/Event/Effect)
让你的 ViewModel 层结构化、可维护、可复用。
🧩 一、结构预览
我们先建立以下包结构(推荐):
base/ ├─ state/ # 状态定义(State, Event, Effect) ├─ viewmodel/ # 通用 BaseViewModel └─ utils/ # 扩展工具类
🧠 二、核心概念复习
| 名称 | 作用 | 举例 |
|---|---|---|
| UiState | 描述当前界面状态(数据类) | 当前录音时长、播放状态等 |
| UiEvent | 来自用户的操作事件 | 点击按钮、滑动、删除等 |
| UiEffect | 一次性副作用事件 | Toast、导航、弹窗等 |
| BaseViewModel | 管理 State、处理 Event、发送 Effect | 负责业务逻辑和状态流转 |
⚙️ 三、完整代码模板
1️⃣ UiState.kt
Kotlin
package com.hatio.base.state
/**
* UI 层状态基类
* 所有页面状态都应继承自 UiState
*/
interface UiState
2️⃣ UiEvent.kt
Kotlin
package com.hatio.base.state
/**
* 用户事件:点击、输入、滑动等交互操作
*/
interface UiEvent
3️⃣ UiEffect.kt
Kotlin
package com.hatio.base.state
/**
* 一次性副作用事件:Toast、导航、弹窗等
*/
interface UiEffect
4️⃣ BaseViewModel.kt
Kotlin
package com.hatio.base.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.hatio.base.state.UiEffect
import com.hatio.base.state.UiEvent
import com.hatio.base.state.UiState
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
/**
* 通用的 BaseViewModel
* 使用 StateFlow 管理 UI 状态,Channel 发送一次性事件
*/
abstract class BaseViewModel<S : UiState, E : UiEvent, F : UiEffect> : ViewModel() {
// UI 状态流
private val _uiState: MutableStateFlow<S> by lazy { MutableStateFlow(initialState()) }
val uiState: StateFlow<S> = _uiState.asStateFlow()
// 一次性事件通道(如 Toast / 导航)
private val _uiEffect = Channel<F>(Channel.BUFFERED)
val uiEffect = _uiEffect.receiveAsFlow()
// 初始化默认状态
abstract fun initialState(): S
// 更新状态:局部更新(不可变数据拷贝)
protected fun setState(reducer: S.() -> S) {
_uiState.value = _uiState.value.reducer()
}
// 发送副作用事件(一次性)
protected fun sendEffect(builder: () -> F) {
viewModelScope.launch { _uiEffect.send(builder()) }
}
// 处理用户事件
abstract fun handleEvent(event: E)
}
5️⃣ 示例:实现一个录音状态的 ViewModel
VoiceUiState.kt
Kotlin
package com.hatio.feature.voice
import com.hatio.base.state.UiState
data class VoiceUiState(
val isRecording: Boolean = false,
val isPlaying: Boolean = false,
val recordTime: String = "00:00",
val filePath: String? = null
) : UiState
VoiceUiEvent.kt
Kotlin
package com.hatio.feature.voice import com.hatio.base.state.UiEvent sealed class VoiceUiEvent : UiEvent { object OnRecordClicked : VoiceUiEvent() object OnStopRecord : VoiceUiEvent() object OnPlayClicked : VoiceUiEvent() object OnPausePlay : VoiceUiEvent() }
VoiceUiEffect.kt
Kotlin
package com.hatio.feature.voice
import com.hatio.base.state.UiEffect
sealed class VoiceUiEffect : UiEffect {
data class ShowToast(val message: String) : VoiceUiEffect()
}
VoiceViewModel.kt
Kotlin
package com.hatio.feature.voice
import androidx.lifecycle.viewModelScope
import com.hatio.base.viewmodel.BaseViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
/**
* 示例 ViewModel:控制录音和播放的状态流转
*/
class VoiceViewModel : BaseViewModel<VoiceUiState, VoiceUiEvent, VoiceUiEffect>() {
override fun initialState() = VoiceUiState()
override fun handleEvent(event: VoiceUiEvent) {
when (event) {
VoiceUiEvent.OnRecordClicked -> startRecording()
VoiceUiEvent.OnStopRecord -> stopRecording()
VoiceUiEvent.OnPlayClicked -> startPlaying()
VoiceUiEvent.OnPausePlay -> pausePlaying()
}
}
private fun startRecording() {
setState { copy(isRecording = true, recordTime = "00:00") }
sendEffect { VoiceUiEffect.ShowToast("开始录音") }
// 模拟计时
viewModelScope.launch {
var seconds = 0
while (uiState.value.isRecording) {
delay(1000)
seconds++
val time = String.format("%02d:%02d", seconds / 60, seconds % 60)
setState { copy(recordTime = time) }
}
}
}
private fun stopRecording() {
setState { copy(isRecording = false, filePath = "/path/to/record.aac") }
sendEffect { VoiceUiEffect.ShowToast("录音已停止") }
}
private fun startPlaying() {
setState { copy(isPlaying = true) }
sendEffect { VoiceUiEffect.ShowToast("开始播放") }
}
private fun pausePlaying() {
setState { copy(isPlaying = false) }
sendEffect { VoiceUiEffect.ShowToast("暂停播放") }
}
}
VoiceActivity.kt(简化版)
Kotlin
package com.hatio.feature.voice
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.hatio.feature.voice.databinding.ActivityVoiceBinding
import kotlinx.coroutines.flow.collectLatest
class VoiceActivity : ComponentActivity() {
private val binding by lazy { ActivityVoiceBinding.inflate(layoutInflater) }
private val viewModel by viewModels<VoiceViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
lifecycleScope.launchWhenStarted {
viewModel.uiState.collectLatest { state ->
binding.btnRecord.text = if (state.isRecording) "停止" else "录音"
binding.tvTime.text = state.recordTime
}
}
lifecycleScope.launchWhenStarted {
viewModel.uiEffect.collectLatest { effect ->
when (effect) {
is VoiceUiEffect.ShowToast -> Toast.makeText(this@VoiceActivity, effect.message, Toast.LENGTH_SHORT).show()
}
}
}
binding.btnRecord.setOnClickListener {
if (viewModel.uiState.value.isRecording)
viewModel.handleEvent(VoiceUiEvent.OnStopRecord)
else
viewModel.handleEvent(VoiceUiEvent.OnRecordClicked)
}
}
}
🧱 四、总结与阶段产物
到这里你已经拥有了一个 完全可复用的 UDF 架构模板:
| 模块 | 文件 | 功能 |
|---|---|---|
| base/state | UiState, UiEvent, UiEffect | 定义三种层次数据流 |
| base/viewmodel | BaseViewModel | 统一管理 StateFlow + Effect |
| feature/voice | VoiceViewModel + 状态类 | 业务逻辑示例 |
ChatGPT 说:
是否希望我接下来直接生成「第 2 周:Repository + UseCase 架构模板与 Demo(含网络与本地数据源)」?
您说:
是,希望
ChatGPT 说:
太棒了 ✅ Jeled!这一步正是从「写功能」→「写架构」的关键转折。
我们马上进入你计划的下一篇实战内容:
🚀 第 2 周实战主题
🧱 Repository + UseCase 架构模板与 Demo
目标:实现一个可扩展、可测试、支持网络与本地数据源共存的 Repository 层,并结合 UseCase 规范业务逻辑。
🗂️ 一、项目架构图
app/ ├── data/ │ ├── local/ ← Room / DataStore / MMKV │ ├── remote/ ← Retrofit / OkHttp │ ├── repository/ ← 数据聚合层(核心) │ └── model/ ← DTO + Entity │ ├── domain/ │ ├── model/ ← 业务模型(UI 层使用) │ └── usecase/ ← 用例逻辑(UseCase) │ ├── ui/ │ ├── viewmodel/ ← ViewModel 调用 UseCase │ └── screen/ ← Compose / Fragment / Activity │ └── di/ ← Hilt 模块
🧠 二、架构理念
| 层级 | 职责 | 示例 |
|---|---|---|
| RemoteDataSource | 负责网络请求 | Retrofit 调用 |
| LocalDataSource | 负责本地缓存 | Room / MMKV |
| Repository | 聚合数据源,隐藏数据实现细节 | 从缓存或网络读取 |
| UseCase | 业务逻辑抽象(单一职责) | GetUserProfileUseCase |
| ViewModel | 控制 UI 状态 | 调用 UseCase 并暴露 StateFlow |
🧩 三、核心示例:获取用户资料(含本地缓存 + 网络回源)
1️⃣ RemoteDataSource.kt
Kotlin
class UserRemoteDataSource @Inject constructor(
private val api: UserApi
) {
suspend fun fetchUser(userId: String): ApiResult<UserDto> {
return try {
val response = api.getUser(userId)
ApiResult.Success(response)
} catch (e: Exception) {
ApiResult.Error(e)
}
}
}
2️⃣ LocalDataSource.kt
Kotlin
class UserLocalDataSource @Inject constructor(
private val dao: UserDao
) {
suspend fun getUser(userId: String): UserEntity? = dao.getUserById(userId)
suspend fun saveUser(user: UserEntity) {
dao.insert(user)
}
}
3️⃣ Repository.kt
Kotlin
class UserRepository @Inject constructor(
private val local: UserLocalDataSource,
private val remote: UserRemoteDataSource
) {
suspend fun getUser(userId: String): Flow<Resource<User>> = flow {
emit(Resource.Loading)
// 优先读取本地缓存
local.getUser(userId)?.let { localUser ->
emit(Resource.Success(localUser.toDomain()))
}
// 同步远程数据
when (val result = remote.fetchUser(userId)) {
is ApiResult.Success -> {
val entity = result.data.toEntity()
local.saveUser(entity)
emit(Resource.Success(entity.toDomain()))
}
is ApiResult.Error -> emit(Resource.Error(result.exception))
}
}
}
4️⃣ UseCase.kt
Kotlin
class GetUserProfileUseCase @Inject constructor(
private val repository: UserRepository
) {
operator fun invoke(userId: String): Flow<Resource<User>> {
return repository.getUser(userId)
}
}
5️⃣ ViewModel.kt
Kotlin
@HiltViewModel
class UserViewModel @Inject constructor(
private val getUserProfileUseCase: GetUserProfileUseCase
) : ViewModel() {
private val _userState = MutableStateFlow<Resource<User>>(Resource.Loading)
val userState: StateFlow<Resource<User>> = _userState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
getUserProfileUseCase(userId).collect { result ->
_userState.value = result
}
}
}
}
6️⃣ UI 示例(Compose)
Kotlin
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
val state by viewModel.userState.collectAsState()
when (state) {
is Resource.Loading -> CircularProgressIndicator()
is Resource.Success -> {
val user = (state as Resource.Success).data
Text("Hello, ${user.name}")
}
is Resource.Error -> Text("加载失败: ${(state as Resource.Error).throwable.message}")
}
}
⚙️ 四、辅助类定义
Kotlin
// Resource.kt
sealed class Resource<out T> {
object Loading : Resource<Nothing>()
data class Success<T>(val data: T) : Resource<T>()
data class Error(val throwable: Throwable) : Resource<Nothing>()
}
// ApiResult.kt
sealed class ApiResult<out T> {
data class Success<T>(val data: T) : ApiResult<T>()
data class Error(val exception: Exception) : ApiResult<Nothing>()
}
🧩 五、扩展与思考
-
✅ 可添加 刷新策略(如 CachePolicy)
-
✅ 支持 分页加载(Paging3)
-
✅ 结合 Room + Flow 实现实时数据更新
-
✅ 与 Hilt 结合实现模块化依赖注入
🎯 六、实践任务
| 周目标 | 说明 |
|---|---|
| 🧱 第 2 周 | 基于上面模板实现一个「用户资料模块」Demo(含 Repository + UseCase + Flow) |
| 🔄 扩展任务 | 再实现一个「消息列表模块」,演示分页 + 缓存策略 |
| 🧩 加分项 | 使用 Hilt 完成依赖注入 + 添加单元测试 |
ChatGPT 说:
是否希望我接下来直接生成「第 3 周:模块化与依赖管理 」?
✅ 是的,继续下一阶段
🚫 暂时不用,先巩固第 2 周
您说:
是的,继续下一阶段
ChatGPT 说:
太棒了 ✅ 我们正式进入 ------ 第 3 周:模块化与依赖管理(进阶架构阶段) 🚀
🧭 第 3 周目标
让项目从单模块演进为「可扩展、可复用、低耦合」的多模块架构。
核心目标是理解 模块划分原则 、依赖反转 、公共库管理 、组件间通信。
📦 第 3 周学习计划总览
| 阶段 | 模块 | 目标 | 关键技术 |
|---|---|---|---|
| 第 1 天 | 模块化基础 | 理解模块划分原则与 Gradle 结构 | multi-module, Gradle buildSrc |
| 第 2 天 | 公共依赖统一管理 | 学会集中管理依赖版本与库 | buildSrc + Versions.kt |
| 第 3 天 | 公共基础模块封装 | 封装 base, common, network, core 模块 |
Kotlin + Hilt + Retrofit |
| 第 4 天 | 模块间通信 | 学习组件间解耦通信方案 | ARouter + LiveEventBus |
| 第 5 天 | 动态特性模块 | 使用 feature module 与动态加载 | Dynamic Feature / Instant App |
| 第 6 天 | 架构整合 Demo | 整合成完整多模块项目 | MVVM + Clean Architecture |
| 第 7 天 | 总结 & 项目实战 | 拆分实际 App 架构 | 模块化设计文档编写 |
🧩 模块划分建议
你的项目(例如聊天/社交 App)可这样划分:
app/ // 主入口 core/ // 核心配置、基础依赖 base/ // BaseActivity, BaseFragment, BaseViewModel common/ // 通用工具类(网络、UI、扩展函数) feature_login/ // 登录注册模块 feature_message/ // 聊天模块 feature_profile/ // 个人资料模块 feature_discover/ // 发现页模块 lib_network/ // 网络层 Retrofit 封装 lib_image/ // 图片加载库封装(Glide/Coil/Fresco) lib_audio/ // 音视频封装(ExoPlayer/IjkPlayer)
⚙️ 核心内容一:统一依赖管理(buildSrc)
📁 结构
buildSrc/ ├── build.gradle.kts └── src/main/kotlin/ ├── Libs.kt ├── Versions.kt └── Configs.kt
🔹 buildSrc/build.gradle.kts
Kotlin
plugins {
`kotlin-dsl`
}
repositories {
google()
mavenCentral()
}
🔹 Versions.kt
Kotlin
object Versions {
const val kotlin = "1.9.0"
const val gradle = "7.4.2"
const val coreKtx = "1.10.1"
const val appcompat = "1.6.1"
const val lifecycle = "2.6.1"
const val retrofit = "2.9.0"
const val hilt = "2.48"
const val coil = "2.4.0"
}
🔹 Libs.kt
Kotlin
object Libs {
const val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx}"
const val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
const val lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}"
const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
const val hilt = "com.google.dagger:hilt-android:${Versions.hilt}"
const val coil = "io.coil-kt:coil:${Versions.coil}"
}
然后在任意模块中直接:
Groovy
dependencies {
implementation(Libs.retrofit)
implementation(Libs.hilt)
}
🧠 模块间通信推荐方案
| 场景 | 推荐方案 | 示例 |
|---|---|---|
| 页面跳转 | ARouter | /login/LoginActivity |
| 事件分发 | LiveEventBus / FlowBus | EventBus.post("logout") |
| 数据共享 | ViewModel (sharedViewModel) | activityViewModels() |
| 接口调用 | 接口 + ServiceLocator | decoupled contract pattern |
📘 实战目标
✅ 创建多模块项目(含 app、base、common、feature_login、feature_message)
✅ 使用 buildSrc 统一依赖
✅ 使用 ARouter 实现模块间跳转
✅ 使用 Hilt + ViewModel 构建依赖体系
✅ 最终生成模块化架构 Demo(结构清晰,可复用)
是否希望我帮你直接生成这个 多模块模板(含依赖管理 + 模块跳转 + MVVM 基础结构) ?
如果是,我可以生成完整的 Android Studio 可运行示例。
🟩 是的,请生成模板
🟥 暂时不用,我先看理论
您说:
是的,请生成模板
ChatGPT 说:
太好了 ✅ 我将为你生成一个 完整的可运行 Android 多模块架构模板,包含:
🔹 模块化结构 + MVVM + Hilt + Retrofit + ARouter + 统一依赖管理(buildSrc)
🧩 一、项目结构总览
k8appnew/ ├── build.gradle(根配置) ├── settings.gradle(仓库配置) ├── buildSrc/ ← 统一管理依赖版本 │ ├── build.gradle.kts │ └── src/main/kotlin/ │ ├── Configs.kt │ ├── Versions.kt │ └── Libs.kt ├── app/ ← 主入口模块(仅导航) ├── base/ ← BaseActivity/BaseViewModel/BaseFragment ├── common/ ← 通用工具类(Log、扩展函数、资源管理) ├── lib_network/ ← Retrofit + OkHttp 网络封装 ├── feature_login/ ← 登录模块 ├── feature_message/ ← 聊天模块
⚙️ 二、settings.gradle(仓库与模块声明)
Kotlin
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url = uri("https://jitpack.io") }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
rootProject.name = "k8appnew"
include(":app", ":base", ":common", ":lib_network", ":feature_login", ":feature_message")
📦 三、buildSrc/build.gradle.kts
Kotlin
plugins { `kotlin-dsl` }
repositories {
google()
mavenCentral()
}
📘 四、buildSrc/src/main/kotlin/Configs.kt
Kotlin
object Configs {
const val compileSdk = 34
const val minSdk = 24
const val targetSdk = 34
const val versionCode = 1
const val versionName = "1.0.0"
const val applicationId = "com.hatio.chat"
}
📘 五、buildSrc/src/main/kotlin/Versions.kt
Kotlin
object Versions {
const val kotlin = "1.9.0"
const val gradle = "7.4.2"
const val hilt = "2.48"
const val lifecycle = "2.6.2"
const val retrofit = "2.9.0"
const val okHttp = "4.11.0"
const val coil = "2.4.0"
const val arouter = "1.5.2"
}
📘 六、buildSrc/src/main/kotlin/Libs.kt
Kotlin
object Libs {
// AndroidX
const val coreKtx = "androidx.core:core-ktx:1.12.0"
const val appcompat = "androidx.appcompat:appcompat:1.6.1"
const val material = "com.google.android.material:material:1.11.0"
// Lifecycle
const val lifecycle = "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle}"
const val viewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Versions.lifecycle}"
// Hilt
const val hilt = "com.google.dagger:hilt-android:${Versions.hilt}"
const val hiltCompiler = "com.google.dagger:hilt-compiler:${Versions.hilt}"
// Retrofit & OkHttp
const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
const val gsonConverter = "com.squareup.retrofit2:converter-gson:${Versions.retrofit}"
const val okHttp = "com.squareup.okhttp3:okhttp:${Versions.okHttp}"
const val logging = "com.squareup.okhttp3:logging-interceptor:${Versions.okHttp}"
// ARouter
const val arouter = "com.alibaba:arouter-api:${Versions.arouter}"
const val arouterCompiler = "com.alibaba:arouter-compiler:${Versions.arouter}"
}
🏗 七、app 模块(主壳模块)
app/build.gradle
Groovy
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'dagger.hilt.android.plugin'
id 'kotlin-kapt'
}
android {
namespace Configs.applicationId
compileSdk Configs.compileSdk
defaultConfig {
applicationId Configs.applicationId
minSdk Configs.minSdk
targetSdk Configs.targetSdk
versionCode Configs.versionCode
versionName Configs.versionName
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation project(':base')
implementation project(':common')
implementation project(':lib_network')
implementation project(':feature_login')
implementation project(':feature_message')
implementation(Libs.coreKtx)
implementation(Libs.appcompat)
implementation(Libs.material)
implementation(Libs.hilt)
kapt(Libs.hiltCompiler)
}
app/src/main/java/com/hatio/chat/MainActivity.kt
Kotlin
package com.hatio.chat
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.android.arouter.launcher.ARouter
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().build("/login/LoginActivity").navigation()
finish()
}
}
🔧 八、base 模块
Groovy
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk Configs.compileSdk
defaultConfig { minSdk Configs.minSdk }
}
dependencies {
implementation(Libs.coreKtx)
implementation(Libs.lifecycle)
implementation(Libs.viewModel)
}
示例:
Kotlin
abstract class BaseViewModel : ViewModel()
🌐 九、lib_network 模块(Retrofit 封装)
Groovy
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk Configs.compileSdk
defaultConfig { minSdk Configs.minSdk }
}
dependencies {
implementation(Libs.retrofit)
implementation(Libs.gsonConverter)
implementation(Libs.okHttp)
implementation(Libs.logging)
}
Kotlin
object ApiServiceFactory {
fun <T> create(service: Class<T>): T {
val client = OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}).build()
return Retrofit.Builder()
.baseUrl("https://api.github.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(service)
}
}
🔐 十、feature_login 模块
Groovy
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
android {
compileSdk Configs.compileSdk
defaultConfig { minSdk Configs.minSdk }
}
dependencies {
implementation project(':base')
implementation project(':common')
implementation(Libs.arouter)
kapt(Libs.arouterCompiler)
}
Kotlin
@Route(path = "/login/LoginActivity")
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("进入登录页")
}
}
💬 十一、feature_message 模块
Kotlin
@Route(path = "/message/MessageActivity")
class MessageActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("进入消息页")
}
}
🚀 十二、初始化(在 Application 中)
Kotlin
@HiltAndroidApp
class App : Application() {
override fun onCreate() {
super.onCreate()
ARouter.openLog()
ARouter.openDebug()
ARouter.init(this)
}
}
✅ 运行效果
-
启动 App → 自动跳转至
feature_login登录模块 -
可通过:
KotlinARouter.getInstance().build("/message/MessageActivity").navigation()直接跳转消息模块。
是否希望我 帮你打包成完整 zip 工程目录(Android Studio 可直接运行) ?
如果是,我会生成所有文件结构 + 可直接导入的 Gradle 构建配置。
🟩 是的,请生成 zip
🟥 不用,我自己复制
您说:
不用,我自己复制
ChatGPT 说:
非常好 👍
那你自己复制即可,这个模板已经是 可直接落地的模块化工程骨架,适合你之后:
🧱 拆分更多业务模块(如
feature_home、feature_user等)🔄 在
buildSrc中统一依赖和版本🧩 使用
ARouter在模块间导航💉 使用 Hilt 注入跨模块依赖(例如
Repository)
如果你接下来想进入
「第 4 周:网络层封装与数据流(Retrofit + Flow + Repository)」
我可以帮你基于当前架构直接加上:
-
✅ 基础
ApiService模板 -
✅ Repository + UseCase 层结构
-
✅ Flow 状态封装(
UiState、Result) -
✅ 统一异常处理
是否要我继续生成 第 4 周模板:网络层与数据流架构?
您说:
是
ChatGPT 说:
太棒了 ✅ 我会直接给你 第 4 周:网络层封装与数据流(Retrofit + OkHttp + Flow + Repository + UseCase + 统一异常处理 + Hilt 注入) 的完整可落地模板代码片段和说明------你可以直接把这些文件拷进之前的模块化项目(lib_network / base / feature_*)中使用。
第 4 周目标(概览)
统一网络层:
Retrofit+OkHttp+ 日志/鉴权/重试 拦截器使用
Flow作为数据流传递(Repository -> ViewModel -> UI)用
Result/UiState/Resource封装加载/成功/失败状态统一异常映射(网络/解析/业务)
分离 Repository 与 UseCase(可测、可复用)
用 Hilt 做依赖注入
依赖(build.gradle 中加)
(放到 lib_network 或公共 base 模块的 gradle)
Groovy
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
implementation "com.google.dagger:hilt-android:2.48"
kapt "com.google.dagger:hilt-compiler:2.48"
目录建议(模块内)
lib_network/ ├─ di/ # Hilt module: 提供 Retrofit/OkHttp/ApiService ├─ model/ # DTO / Response wrappers ├─ network/ # 拦截器、ExceptionMapper ├─ repository/ # 网络仓库接口/实现 └─ usecase/ # UseCase(业务用例)
一、统一的 Response / Resource / UiState 封装
Resource.kt
Kotlin
sealed class Resource<out T> {
object Loading : Resource<Nothing>()
data class Success<T>(val data: T) : Resource<T>()
data class Error(val throwable: Throwable, val code: Int? = null, val message: String? = null) : Resource<Nothing>()
}
UiState.kt(在 UI 层可选)
Kotlin
data class UiState<T>(
val loading: Boolean = false,
val data: T? = null,
val error: String? = null
)
二、统一异常映射器(将各种异常转换为友好错误)
NetworkException.kt
Kotlin
sealed class NetworkException(message: String? = null, val code: Int? = null) : Exception(message) {
class HttpError(code: Int, message: String?) : NetworkException(message, code)
class Timeout(message: String? = null) : NetworkException(message)
class NoNetwork(message: String? = null) : NetworkException(message)
class ParseError(message: String? = null) : NetworkException(message)
class Unknown(message: String? = null) : NetworkException(message)
}
ExceptionMapper.kt
Kotlin
object ExceptionMapper {
fun map(e: Throwable): NetworkException {
return when (e) {
is retrofit2.HttpException -> NetworkException.HttpError(e.code(), e.message())
is java.net.SocketTimeoutException -> NetworkException.Timeout(e.message)
is java.io.IOException -> NetworkException.NoNetwork(e.message)
is com.google.gson.JsonParseException, is java.lang.IllegalStateException -> NetworkException.ParseError(e.message)
else -> NetworkException.Unknown(e.message)
}
}
}
三、OkHttp 拦截器(鉴权、重试、日志)
AuthInterceptor.kt
Kotlin
class AuthInterceptor(private val tokenProvider: () -> String?) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val builder = original.newBuilder()
tokenProvider()?.let { token ->
builder.header("Authorization", "Bearer $token")
}
return chain.proceed(builder.build())
}
}
Logging 使用 HttpLoggingInterceptor(不展示代码,直接用库)
四、Retrofit & OkHttp 提供(Hilt Module)
NetworkModule.kt
Kotlin
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(tokenProvider: TokenProvider): OkHttpClient {
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
return OkHttpClient.Builder()
.addInterceptor(AuthInterceptor { tokenProvider.getToken() })
.addInterceptor(logging)
.callTimeout(30, TimeUnit.SECONDS)
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
@Provides
@Singleton
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
}
@Provides
@Singleton
fun provideYourApi(retrofit: Retrofit): YourApiService = retrofit.create(YourApiService::class.java)
}
(TokenProvider 是你自己用 Hilt 注入的接口,用来提供当前 token。)
五、ApiService 示例
YourApiService.kt
Kotlin
interface YourApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): Response<UserDto>
@POST("login")
suspend fun login(@Body req: LoginRequest): Response<LoginResponse>
}
注意:上面用的是
suspend fun+Response<T>,便于你检查 HTTP code 并统一处理。
六、Repository 层(接口 + 实现)
UserRepository.kt(接口)
Kotlin
interface UserRepository {
fun getUserFlow(id: String): Flow<Resource<UserDto>>
suspend fun login(username: String, password: String): Resource<LoginResponse>
}
UserRepositoryImpl.kt
Kotlin
class UserRepositoryImpl @Inject constructor(private val api: YourApiService) : UserRepository {
override fun getUserFlow(id: String): Flow<Resource<UserDto>> = flow {
emit(Resource.Loading)
try {
val resp = api.getUser(id)
if (resp.isSuccessful) {
val body = resp.body()
if (body != null) {
emit(Resource.Success(body))
} else {
emit(Resource.Error(NetworkException.ParseError("Empty body")))
}
} else {
emit(Resource.Error(NetworkException.HttpError(resp.code(), resp.message())))
}
} catch (e: Throwable) {
emit(Resource.Error(ExceptionMapper.map(e)))
}
}.flowOn(Dispatchers.IO)
override suspend fun login(username: String, password: String): Resource<LoginResponse> {
return try {
val resp = api.login(LoginRequest(username, password))
if (resp.isSuccessful && resp.body() != null) {
Resource.Success(resp.body()!!)
} else {
Resource.Error(NetworkException.HttpError(resp.code(), resp.message()))
}
} catch (e: Throwable) {
Resource.Error(ExceptionMapper.map(e))
}
}
}
Hilt 绑定(RepositoryModule):
Kotlin
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
}
七、UseCase(可选层,便于测试/复用)
GetUserUseCase.kt
Kotlin
class GetUserUseCase @Inject constructor(private val repo: UserRepository) {
operator fun invoke(userId: String): Flow<Resource<UserDto>> {
return repo.getUserFlow(userId)
}
}
LoginUseCase.kt
Kotlin
class LoginUseCase @Inject constructor(private val repo: UserRepository) {
suspend operator fun invoke(username: String, password: String): Resource<LoginResponse> {
return repo.login(username, password)
}
}
八、ViewModel(Flow -> State)
UserViewModel.kt
Kotlin
@HiltViewModel
class UserViewModel @Inject constructor(private val getUserUseCase: GetUserUseCase) : ViewModel() {
private val _userState = MutableStateFlow<Resource<UserDto>>(Resource.Loading)
val userState: StateFlow<Resource<UserDto>> = _userState
fun loadUser(id: String) {
getUserUseCase(id)
.onEach { _userState.value = it }
.catch { throwable -> _userState.value = Resource.Error(ExceptionMapper.map(throwable)) }
.launchIn(viewModelScope)
}
}
在 Activity/Fragment 中观察:
Kotlin
lifecycleScope.launchWhenStarted {
viewModel.userState.collect { resource ->
when (resource) {
is Resource.Loading -> showLoading()
is Resource.Success -> showData(resource.data)
is Resource.Error -> showError(resource.message ?: "未知错误")
}
}
}
九、统一错误展示(UI 层建议)
对
NetworkException.HttpError可根据code做不同处理(401 -> 跳登录,500 -> 服务错误)
NoNetwork提示检查网络并提供重试
ParseError记录日志并提示「数据异常」
十、分页 / 列表建议(Flow + Paging 可结合)
若需要列表分页,强烈建议使用 Android Paging3(与 Flow 很好配合)。此处不展开,如需我可以补分页模板。
十一、缓存策略(选配)
简单:在 Repository 内先读本地缓存(Room / SharedPref),然后
emit缓存,随后从网络刷新并emit新值。复杂:使用
NetworkBoundResource模式(经典实现,能优雅处理缓存/刷新)。
简易示例(伪代码):
Kotlin
flow {
emit(Resource.Loading)
val cached = local.query(id)
if (cached != null) emit(Resource.Success(cached))
val network = api.getUser(id)
if (network.isSuccessful) {
local.insert(network.body())
emit(Resource.Success(network.body()!!))
}
}.flowOn(Dispatchers.IO)
十二、测试建议
UseCase / Repository 的单元测试 -> 用
MockWebServer模拟后端,断言 Flow 发射的 Resource 顺序(Loading -> Success/Error)。ViewModel -> 使用
Turbine或runBlockingTest收集 StateFlow。
最后(交付内容)
我已经把核心模板代码给出,接下来你可以选择:
-
我帮你把这些文件 生成成完整工程结构并打包为 zip(我会立即生成)。
-
你把上面代码直接拷贝到项目中,我再帮你调整/修复集成问题。
-
我再补充:Paging3 模板 / NetworkBoundResource 缓存实现 / MockWebServer 单元测试示例。
你想先要哪个?
您说:
Paging3 模板 + NetworkBoundResource 缓存实现 + MockWebServer 单元测试示例
ChatGPT 说:
非常棒 🔥 --- 你现在正式进入第 4 周进阶阶段:
「分页 + 网络缓存 + 单元测试」完整架构模板
以下是为你量身定制的 Kotlin + Paging3 + NetworkBoundResource + MockWebServer 架构模板,遵循 MVVM + Repository + Flow + Hilt 架构,可直接落地使用。
🧱 一、项目结构
app/ ├─ data/ │ ├─ local/ │ │ ├─ AppDatabase.kt │ │ └─ UserDao.kt │ ├─ remote/ │ │ ├─ ApiService.kt │ │ └─ MockInterceptor.kt │ ├─ repository/ │ │ ├─ UserRepository.kt │ │ └─ NetworkBoundResource.kt │ └─ model/ │ └─ User.kt ├─ ui/ │ ├─ UserListViewModel.kt │ └─ UserListFragment.kt ├─ di/ │ └─ AppModule.kt ├─ MainActivity.kt └─ test/ └─ UserRepositoryTest.kt
⚙️ 二、Gradle 依赖(build.gradle)
Groovy
dependencies {
// Jetpack
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.6"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6"
// Paging 3
implementation "androidx.paging:paging-runtime:3.3.2"
// 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"
// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.11.0"
implementation "com.squareup.retrofit2:converter-gson:2.11.0"
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12"
// Hilt
implementation "com.google.dagger:hilt-android:2.52"
kapt "com.google.dagger:hilt-compiler:2.52"
// Coroutine
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
// Test
testImplementation "junit:junit:4.13.2"
testImplementation "androidx.arch.core:core-testing:2.2.0"
testImplementation "com.squareup.okhttp3:mockwebserver:5.0.0-alpha.12"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0"
}
🧩 三、NetworkBoundResource.kt
Kotlin
package com.example.data.repository
import kotlinx.coroutines.flow.*
/**
* 通用的网络 + 本地缓存逻辑
*/
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline shouldFetch: (ResultType?) -> Boolean = { true }
): Flow<ResultType> = flow {
val data = query().firstOrNull()
val flow = if (shouldFetch(data)) {
try {
val remote = fetch()
saveFetchResult(remote)
query()
} catch (t: Throwable) {
query()
}
} else {
query()
}
emitAll(flow)
}
🧠 四、UserRepository.kt
Kotlin
package com.example.data.repository
import androidx.paging.*
import com.example.data.local.UserDao
import com.example.data.remote.ApiService
import com.example.data.model.User
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class UserRepository @Inject constructor(
private val api: ApiService,
private val dao: UserDao
) {
fun getUsersPaged(): Flow<PagingData<User>> {
return Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false),
pagingSourceFactory = { dao.pagingSource() }
).flow
}
fun getUsersWithCache() = networkBoundResource(
query = { dao.getAllFlow() },
fetch = { api.getUsers() },
saveFetchResult = { dao.insertAll(it) }
)
}
💾 五、Room 数据层
AppDatabase.kt
Kotlin
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
UserDao.kt
Kotlin
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAllFlow(): Flow<List<User>>
@Query("SELECT * FROM user")
fun pagingSource(): PagingSource<Int, User>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(users: List<User>)
}
🌐 六、ApiService.kt
Kotlin
interface ApiService {
@GET("users")
suspend fun getUsers(): List<User>
}
🧰 七、ViewModel + UI
UserListViewModel.kt
Kotlin
@HiltViewModel
class UserListViewModel @Inject constructor(
private val repo: UserRepository
) : ViewModel() {
val userPaging = repo.getUsersPaged().cachedIn(viewModelScope)
val userCacheFlow = repo.getUsersWithCache()
}
UserListFragment.kt
Kotlin
@AndroidEntryPoint
class UserListFragment : Fragment(R.layout.fragment_user_list) {
private val viewModel: UserListViewModel by viewModels()
private val adapter = UserPagingAdapter()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.adapter = adapter
lifecycleScope.launchWhenStarted {
viewModel.userPaging.collectLatest { adapter.submitData(it) }
}
}
}
🧪 八、MockWebServer 单元测试
UserRepositoryTest.kt
Kotlin
@ExperimentalCoroutinesApi
class UserRepositoryTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var api: ApiService
@Before
fun setup() {
mockWebServer = MockWebServer()
mockWebServer.start()
api = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
@Test
fun testFetchUsers() = runTest {
val mockResponse = MockResponse()
.setResponseCode(200)
.setBody("""[{"id":1,"name":"Alice"}]""")
mockWebServer.enqueue(mockResponse)
val result = api.getUsers()
assert(result.first().name == "Alice")
}
@After
fun teardown() {
mockWebServer.shutdown()
}
}
✅ 九、总结
| 功能 | 实现 |
|---|---|
| 分页加载 | Paging3 + Room |
| 缓存机制 | NetworkBoundResource |
| 网络请求 | Retrofit |
| 测试 | MockWebServer |
| 架构模式 | MVVM + Repository + Flow + Hilt |