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 ActionsGitLab CI

30.6.1 流水线配置

一个典型的CI/CD流程:

  1. 代码提交触发构建
  2. 运行单元测试
  3. 运行静态代码检查(Detekt、Lint)
  4. 构建APK/AAB
  5. 上传到分发平台(如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 本章知识体系

下面这张图,总结了本章的核心脉络:

完整App开发流程 需求分析 用户故事 · 技术预研 架构设计 MVVM + Clean Architecture 编码实现 ViewModel · Compose 测试 单元测试 · 集成测试 CI/CD集成 自动化构建 · 发布 发布 Google Play · 内测 贯穿全程:模块化 + 组件化

这张图展示了从需求到发布的完整链路。你会发现,模块化和组件化不是某个阶段的事,而是贯穿始终的设计思想。

30.8 写在最后

30章,从入门到实战,我们终于走完了。

说实话,写这个课程的过程中,我翻了很多以前的代码,也重构了一些设计。每次回头看自己写的东西,都能发现可以改进的地方。这就是成长吧。

如果你能把这一章的内容真正落地到项目中,我相信你的Android开发能力会上一个台阶。记住:架构不是炫技,是为了让代码更好维护

最后送你一句话:写代码容易,写好代码难。但只要你坚持用正确的方法,时间会给你答案。


公众号:蓝海资料掘金营,微信deep3321