30、项目实战:完整App开发流程
终于到了最后一章。说实话,写到这里我挺感慨的。
前面29章,我们聊了Kotlin语法、Jetpack组件、性能优化、安全加固……但你可能一直有个疑问:这些东西到底怎么串起来?
这一章,我们就用一个完整的项目实战,把前面所有知识点揉在一起。我会带你走一遍真实App从0到1的全流程:需求分析、架构设计、编码实现、测试发布。顺便聊聊我这些年踩过的坑。
本章核心目标:掌握MVVM + Clean Architecture的落地方式,理解模块化与组件化的拆分思路,学会配置CI/CD流水线。
30.1 需求分析:别急着写代码
我见过太多人,拿到需求就开干。结果呢?写到一半发现设计不合理,推倒重来。
我自己早期也犯过这个错。有一次做电商App,产品说“加个收藏功能”,我直接往数据库里加字段。后来发现用户需要收藏夹分组、标签、排序……整个数据模型全得改。嗯,那次加班到凌晨三点。
30.1.1 需求文档怎么写?
我个人习惯用用户故事来描述需求。举个例子:
作为:普通用户
我想要:收藏喜欢的商品
以便于:以后快速找到它们
然后拆成功能点:
- 点击商品详情页的收藏按钮
- 在个人中心查看收藏列表
- 支持取消收藏
- 收藏列表支持分页加载
你看,这样写出来,开发的时候思路就清晰多了。
30.1.2 技术可行性评估
需求确定后,我会做一次技术预研。比如:
- 收藏数据存在本地还是云端?
- 是否需要离线缓存?
- 分页用Paging3还是自己写?
这些决策会影响后面的架构设计。别小看这一步,我曾经因为没做预研,直接用了Room做本地存储,结果发现用户在不同设备登录,收藏数据不同步……最后加了个远程数据源,重构了两周。
30.2 架构设计:MVVM + Clean Architecture
架构这东西,说白了就是让代码好改、好测、好扩展。
我个人最推荐的是MVVM + Clean Architecture的组合。为什么?因为Google官方推荐MVVM,而Clean Architecture帮我们划清了业务逻辑和框架的边界。
30.2.1 分层结构
我们分成三层:
| 层级 | 职责 | 典型类 |
|---|---|---|
| Data层 | 数据来源(网络、本地、数据库) | Repository、DataSource |
| Domain层 | 业务逻辑(纯Kotlin,不依赖Android) | UseCase、Entity |
| Presentation层 | UI展示(ViewModel、Compose/XML) | ViewModel、Screen |
这里有个关键点:Domain层不依赖任何Android框架。这样你换UI库、换数据库,业务逻辑完全不用动。
我的经验:Domain层用纯Kotlin写,连Context都不要传进去。这样单元测试跑得飞快。
30.2.2 依赖注入
我用Hilt做依赖注入。举个例子:
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@Provides
@Singleton
fun provideFavoriteRepository(
localDataSource: FavoriteLocalDataSource,
remoteDataSource: FavoriteRemoteDataSource
): FavoriteRepository {
return FavoriteRepositoryImpl(localDataSource, remoteDataSource)
}
}
这样写的好处是:换数据源只需要改Module,业务层完全感知不到。
30.3 模块化与组件化
项目大了以后,所有代码写在一个module里,编译慢、耦合高。我建议拆成多module。
30.3.1 怎么拆?
我一般按功能和层级两个维度拆:
- 功能模块:home、favorite、profile、search
- 基础模块:core(网络、数据库、工具类)、common-ui(公共组件)
举个例子:
app/
├── app/ # 主工程,负责组装
├── core/
│ ├── network/ # Retrofit封装
│ ├── database/ # Room封装
│ └── common-ui/ # 公共UI组件
├── feature/
│ ├── home/ # 首页
│ ├── favorite/ # 收藏
│ └── profile/ # 个人中心
注意:模块之间不要循环依赖。我曾经在core模块里引用了feature模块的类,结果编译报错查了一整天。后来用接口隔离解决了。
30.3.2 组件化通信
模块之间怎么通信?我推荐两种方式:
- 接口下沉:把接口定义在core模块,各feature模块实现
- 路由框架:用ARouter或系统DeepLink
我个人更倾向接口下沉,因为类型安全,编译期就能发现问题。
30.4 编码实现:从ViewModel到UI
架构搭好了,开始写代码。我以收藏功能为例。
30.4.1 ViewModel
@HiltViewModel
class FavoriteViewModel @Inject constructor(
private val getFavoriteUseCase: GetFavoriteUseCase,
private val toggleFavoriteUseCase: ToggleFavoriteUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow(FavoriteUiState())
val uiState: StateFlow<FavoriteUiState> = _uiState.asStateFlow()
fun loadFavorites() {
viewModelScope.launch {
getFavoriteUseCase()
.collect { result ->
_uiState.update { it.copy(favorites = result) }
}
}
}
fun toggleFavorite(productId: String) {
viewModelScope.launch {
toggleFavoriteUseCase(productId)
}
}
}
这里用了StateFlow,配合Compose的collectAsState,UI会自动更新。
30.4.2 UI层(Jetpack Compose)
@Composable
fun FavoriteScreen(
viewModel: FavoriteViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
LazyColumn {
items(uiState.favorites) { product ->
FavoriteItem(
product = product,
onToggle = { viewModel.toggleFavorite(product.id) }
)
}
}
}
你看,代码很简洁。ViewModel只管状态,UI只管渲染。
30.5 测试:别等上线再后悔
我见过太多项目,测试覆盖率几乎为零。结果上线后崩溃率飙升。
我个人习惯:每个UseCase都要写单元测试。
30.5.1 单元测试示例
@Test
fun `toggleFavorite should add favorite when not exists`() = runTest {
// Given
val productId = "123"
`when`(localDataSource.isFavorite(productId)).thenReturn(false)
// When
toggleFavoriteUseCase(productId)
// Then
verify(localDataSource).addFavorite(productId)
}
这里用了MockK做mock,配合Turbine测试Flow。跑起来很快,几十秒搞定。
避坑指南:我曾经在测试里直接用了真实数据库,结果每次跑测试都要清数据。后来改用FakeDataSource,测试速度提升了10倍。
30.6 CI/CD集成:自动化解放双手
手动打包、手动测试?太累了。我建议用GitHub Actions或GitLab CI。
30.6.1 流水线配置
一个典型的CI/CD流程:
- 代码提交触发构建
- 运行单元测试
- 运行静态代码检查(Detekt、Lint)
- 构建APK/AAB
- 上传到分发平台(如Firebase App Distribution)
这是我常用的GitHub Actions配置片段:
name: Android CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tests
run: ./gradlew test
- name: Build APK
run: ./gradlew assembleDebug
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: app-debug.apk
path: app/build/outputs/apk/debug/
配置好以后,每次提交代码,自动跑测试、自动打包。你想想看,省了多少人工操作。
注意:CI环境的内存和磁盘有限。我遇到过Gradle构建OOM的情况,后来在gradle.properties里加了org.gradle.jvmargs=-Xmx2048m才解决。
30.7 本章知识体系
下面这张图,总结了本章的核心脉络:
这张图展示了从需求到发布的完整链路。你会发现,模块化和组件化不是某个阶段的事,而是贯穿始终的设计思想。
30.8 写在最后
30章,从入门到实战,我们终于走完了。
说实话,写这个课程的过程中,我翻了很多以前的代码,也重构了一些设计。每次回头看自己写的东西,都能发现可以改进的地方。这就是成长吧。
如果你能把这一章的内容真正落地到项目中,我相信你的Android开发能力会上一个台阶。记住:架构不是炫技,是为了让代码更好维护。
最后送你一句话:写代码容易,写好代码难。但只要你坚持用正确的方法,时间会给你答案。
公众号:蓝海资料掘金营,微信deep3321