第三十章:Jetpack综合实战二:性能优化、测试策略、发布流程、持续集成、总结回顾、实战:完整项目发布

好,终于到了最后一章。说实话,写到这里我还有点不舍。不过该来的总会来,这一章我们不讲新组件,而是把前面学过的所有东西串起来,走一遍完整的项目发布流程。

你可能会问:「性能优化和测试,真的有那么重要吗?」

嗯,我在早期带团队时吃过亏。一个功能开发完就急着上线,结果用户反馈卡顿、崩溃,最后不得不紧急回滚。从那以后,我养成了一个习惯:没有经过性能验证和测试覆盖的代码,绝不进发布分支

30.1 性能优化:不只是跑得快

性能优化这件事,说白了就是「让用户觉得爽」。我见过太多项目,功能做得很全,但滑动列表掉帧、页面打开慢半拍,用户直接就卸载了。

30.1.1 启动优化

应用启动是用户的第一印象。我个人习惯用 Startup 库来管理初始化任务,而不是一股脑全塞在 Application 里。

// 使用 Startup 库按需初始化
class MyInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        // 只在需要时初始化
        Log.d("Startup", "按需初始化完成")
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return emptyList()
    }
}
我的经验:adb shell am start -W 包名/Activity 测量冷启动时间。如果超过 2 秒,就得排查了。

30.1.2 内存与布局优化

内存泄漏是性能杀手。我曾经在一个项目中,因为 Fragment 没在 onDestroy 里释放监听器,导致内存暴涨。后来我强制团队使用 LeakCanary,问题才彻底解决。

布局方面,我建议多用 ConstraintLayout 减少嵌套。你想想看,一个界面嵌套五六层 LinearLayout,GPU 渲染压力能不大吗?

// 使用 Profile GPU Rendering 工具
// 在开发者选项中开启,观察柱状图是否超过绿线

30.1.3 网络与数据缓存

网络请求是另一个重灾区。我习惯用 Room 做本地缓存,配合 WorkManager 做后台同步。这样即使用户断网,也能看到历史数据。

注意:不要在主线程做网络请求。我曾经见过一个同事直接在 UI 线程调 API,结果 ANR 弹个不停。

30.2 测试策略:别等上线才发现问题

测试这件事,我把它分成三层:单元测试、集成测试、UI 测试。每一层都有它的价值,缺一不可。

30.2.1 单元测试

单元测试测的是「最小单元」的逻辑。比如 ViewModel 里的数据转换、Repository 里的缓存策略。我用 JUnit + Mockito 来写。

@Test
fun testUserRepository_cacheFirst() {
    val mockApi = mock(UserApi::class.java)
    val repo = UserRepository(mockApi)

    // 模拟网络异常,验证是否返回缓存
    `when`(mockApi.fetchUser()).thenThrow(IOException())
    val result = repo.getUser()

    assertNotNull(result)
}

30.2.2 集成测试

集成测试测的是「组件之间的协作」。比如 Room 数据库的 DAO 操作、Navigation 的页面跳转。我用 AndroidX Test 来跑。

@RunWith(AndroidJUnit4::class)
class UserDaoTest {
    @get:Rule
    val dbRule = InstantTaskExecutorRule()

    @Test
    fun testInsertAndQuery() {
        val db = Room.inMemoryDatabaseBuilder(
            ApplicationProvider.getApplicationContext(),
            AppDatabase::class.java
        ).build()

        db.userDao().insert(User(1, "测试用户"))
        val user = db.userDao().getUser(1)

        assertEquals("测试用户", user.name)
    }
}

30.2.3 UI 测试

UI 测试我用 Espresso。说实话,写 UI 测试挺烦的,但每次上线前跑一遍,能发现很多肉眼看不到的问题。

@Test
fun testLoginButton() {
    onView(withId(R.id.btn_login))
        .perform(click())

    onView(withText("登录成功"))
        .check(matches(isDisplayed()))
}
核心原则:测试不是为了覆盖率数字好看,而是为了「出了问题能快速定位」。我一般要求核心逻辑覆盖率达到 80% 以上。

30.3 发布流程:从代码到用户

发布流程我总结为三步:构建、签名、分发。每一步都有坑,我一个个说。

30.3.1 构建配置

用 Gradle 配置不同的构建类型。我习惯分 debug、release、staging 三种。

android {
    buildTypes {
        debug {
            applicationIdSuffix ".debug"
            versionNameSuffix "-debug"
        }
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
        }
        staging {
            initWith debug
            applicationIdSuffix ".staging"
        }
    }
}

30.3.2 签名与加固

签名文件一定要保管好。我曾经有个同事把 keystore 放到了 Git 仓库里,结果被安全团队通报批评。现在我都用环境变量来管理密码。

// 在 gradle.properties 中引用环境变量
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyPassword = System.getenv("KEY_PASSWORD")

30.3.3 分发渠道

我一般用 Firebase App Distribution 做内测,Google Play 做正式发布。内测版本要打上「测试版」的标识,避免用户混淆。

30.4 持续集成:自动化一切

持续集成(CI)是我最推崇的工程实践。说白了,就是让机器帮你做重复劳动。我用 GitHub Actions 来做。

30.4.1 配置 CI 流水线

name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
      - name: Build with Gradle
        run: ./gradlew assembleRelease
      - name: Run tests
        run: ./gradlew test
我的习惯:每次提交代码前,先在本地跑一遍 lint 和单元测试。CI 只是最后一道防线,不是第一道。

30.4.2 自动化测试与报告

CI 跑完后,我会自动生成测试报告,并发送到团队群里。这样谁写的测试挂了,一目了然。

30.5 总结回顾:这 30 章我们学了什么

回顾整个课程,我们从 Jetpack 的基础组件开始,一路走到架构设计、性能优化、测试和发布。你想想看,是不是已经能独立搭建一个完整的 Android 项目了?

我整理了一张知识体系图,帮你把零散的知识点串起来。

Jetpack 综合实战 架构组件 ViewModel / LiveData UI组件 Compose / Navigation 数据层 Room / DataStore 后台任务 WorkManager 性能优化 & 测试 发布流程 & CI 知识体系全景图

30.6 实战:完整项目发布

理论说再多,不如动手做一遍。下面我带你走一遍完整项目发布的流程。

30.6.1 项目结构

我习惯用多模块结构。每个模块职责单一,方便测试和复用。

app/
├── app/                 # 主模块
├── core/                # 核心工具
├── data/                # 数据层
├── domain/              # 业务逻辑
├── feature/             # 功能模块
│   ├── login/
│   ├── home/
│   └── profile/
└── build.gradle.kts     # 根配置

30.6.2 发布前检查清单

检查项 说明 工具/方法
性能测试 启动时间、内存占用、帧率 Profiler / Perfetto
测试覆盖 核心逻辑单元测试通过 JUnit / Mockito
代码规范 Lint 无严重错误 ./gradlew lint
签名配置 Release 签名正确 检查 keystore
版本号 versionCode 递增 Gradle 配置

30.6.3 执行发布

一切检查无误后,执行以下命令:

# 清理构建
./gradlew clean

# 构建 Release 包
./gradlew assembleRelease

# 运行所有测试
./gradlew test

# 生成测试报告
./gradlew jacocoTestReport
最后提醒:发布前一定要在真机上跑一遍核心流程。模拟器跑得再顺,真机也可能翻车。我曾经就因为模拟器没遇到问题,结果在华为手机上闪退,原因是某个权限在模拟器上默认开启,真机需要动态申请。

好了,整个课程到这里就结束了。从第一章的 Jetpack 基础,到最后一章的完整发布,你跟着我一步步走完了 Android 开发的完整链路。说实话,能坚持看到这里,你已经比大多数开发者强了。

以后在项目中遇到问题,记得回来翻翻这些章节。代码是死的,但思路是活的。希望这些经验能帮你少踩一些坑,多做一些好产品。

加油,未来的架构师。

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