Now In Android 精讲 7 - 你的代码谁来守护?

前言

相信我们经常写代码的同学都会有这样的感觉,团队协作、代码提交、检查变得越来越重要。在某些团队里面,code review 甚至会花出相当大比例的时间与精力来保证代码质量。本文来学习借鉴一下 now in android 项目是如何保证稳定性。

切入点

在大型项目中,质量保障机制往往深度集成于CI流程。通过分析.github/workflows/Build.yaml 配置文件,我们可以系统性拆解nowinandroid项目的质量保障策略。该CI流程包含多个关键任务节点,形成覆盖代码规范、依赖管理、自动化测试等多维度的质量防护网。

GitHub CI 文件介绍

GitHub 的 CI 文件一般会配置在 .github 文件夹里面。今天我们要学习的在 .github/workflows/Build.yaml 这个文件里面。下面我们就来逐步分析里面关于保证代码质量的相关模块吧。

项目插件目录检查

yaml 复制代码
- name: Check build-logic
  run: ./gradlew :build-logic:convention:check

这个任务比较简单,是使用 Android Lint 来校验 :build-logic 模块代码质量。由于 build-logic 里面会配置一些 convention plugin,包含variant定义、依赖版本控制等关键配置,需优先确保其规范性

格式化风格检查

yaml 复制代码
- name: Check spotless
  run: ./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache

init.gradle 文件详见这里,这个脚本值得我们学习借鉴,建议大家都试一下。首先找个 src 目录下面建一个 test.kt 文件,然后执行上面的脚本,看看是不是报错了,然后按照它报错的地方进行修改。./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache 这个脚本的用法确实是我第一次见到这么实用的,这脚本可以看成是两步:

  1. 配置 Spotless 初始化脚本
  2. 执行 spotlessCheck

有关 Spotless 的介绍可以看这里。首先呢,Spotless check 类似于 Lint 的 check,它会根据配置文件里面的参数来进行 check,如果遇到了规则之外的代码,它会 report 一个错误出来,并且终止当前 task。Spotless 还有一个 apply 任务,一般用于在代码提交之前,用于按照设定的规则对文件进行修改。目前 Spotless 里面配置的 Kotlin 的规则是 ktlint,如果是以纯 Kotlin 开发的项目,建议直接使用 ktlint 插件,使用起来会更简单。对于 now in android 里面这个 spotlessCheck 主要作用是校验 Kotlin 格式,以及 xml,kt,kts 的 copyright 的校验。

依赖检查

yaml 复制代码
- name: Check Dependency Guard
  id: dependencyguard_verify
  continue-on-error: true
  run: ./gradlew dependencyGuard

在我们开发过程中依赖库版本变化经常会让我们头疼不已,一不小心一个下午就偷偷溜走。dependencyGuard 任务会检查当前依赖库有没有发生改变,如果发生改变,它会终止当前任务。好奇的宝宝可以尝试在项目里面修改一下某个 library 的版本号,然后执行一下 ./gradlew dependencyGuard 看看会发生什么。当然细心的同学肯定会问,如果我想更新依赖怎么办呢?我们可以通过 ./gradlew dependencyGuardBaseline 来更新依赖文件。如果想了解更多关于 dependencyguard 的知识请移步至这里

重新生成依赖文件,如果上个任务失败,并且这个任务是 PR 触发的

yaml 复制代码
- name: Generate new Dependency Guard baselines if verification failed and it's a PR
  id: dependencyguard_baseline
  if: steps.dependencyguard_verify.outcome == 'failure' && github.event_name == 'pull_request'
  run: |
    ./gradlew dependencyGuardBaseline
      
- name: Push new Dependency Guard baselines if available
  uses: stefanzweifel/git-auto-commit-action@v5
  if: steps.dependencyguard_baseline.outcome == 'success'
  with:
    file_pattern: '**/dependencies/*.txt'
    disable_globbing: true
    commit_message: "🤖 Updates baselines for Dependency Guard"

有了上一节的知识,这个任务不必多说了。

验证截图

yaml 复制代码
- name: Run all local screenshot tests (Roborazzi)
  id: screenshotsverify
  continue-on-error: true
  run: ./gradlew verifyRoborazziDemoDebug

对于这个任务,我个人认为其偏向于自动化测试方面的。当然,Android 开发如果想了解更多我觉得这是一个不错的知识点。它的介绍可以看这里。对于某些不经常变更又非常重要的页面,做 screenshot test 是一件极其有意义的事情,它直观地反映出修改,以免在发布之前出现行为不一致。

运行本地测试样例

yaml 复制代码
- name: Run local tests
  run: ./gradlew testDemoDebug :lint:test

这步主要是为了执行一些自定义的 lint 任务,在项目这个 test 主要是调用 DesignSystemDetector 这个文件来检查项目里面有没有使用 design system 里面的组件,如果使用了系统自带的有关组件,它会 report 一个 issue 来提醒使用方使用正确的组件。当然,Android Lint 的功能非常强大,远远不止校验组件这一个功能,在实际开发过程中我们可以按照自己需求灵活使用。

Build 所有变种组合然后上传到指定文件夹

yaml 复制代码
- name: Build all build type and flavor permutations
  run: ./gradlew :app:assemble

- name: Upload build outputs (APKs)
  uses: actions/upload-artifact@v4
  with:
    name: APKs
    path: '**/build/outputs/apk/**/*.apk'

这个任务堪称整个 CI 的核心,俗称 丐中丐,即便我们编写一个最简单的 CI 任务,我们也需要运行这个任务,保证我们上传的代码能够正常地通过编译,同时还要生成产物方便后续 QA 下载测试。

上传产物

yaml 复制代码
- name: Upload JVM local results (XML)
  if: ${{ !cancelled() }}
  uses: actions/upload-artifact@v4
  with:
    name: local-test-results
    path: '**/build/test-results/test*UnitTest/**.xml'

- name: Upload screenshot results (PNG)
  if: ${{ !cancelled() }}
  uses: actions/upload-artifact@v4
  with:
    name: screenshot-test-results
    path: '**/build/outputs/roborazzi/*_compare.png'

上传本地测试任务和截图到 outputs

执行 Lint 任务

yaml 复制代码
- name: Check lint
  run: ./gradlew :app:lintProdRelease :app-nia-catalog:lintRelease :lint:lint

- name: Upload lint reports (HTML)
  if: ${{ !cancelled() }}
  uses: actions/upload-artifact@v4
  with:
    name: lint-reports
    path: '**/build/reports/lint-results-*.html'

- name: Upload lint reports (SARIF)
  if: ${{ !cancelled() && hashFiles('**/*.sarif') != '' }}
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: './'

执行 lint 任务并且上传到产物,和 codeql,其中 codeql 是一个静态分析的工具,主要用于检测代码中的安全漏洞和潜在缺陷。

检查测试覆盖率

androidTest 这个 job 主要作用是使用 jacoco 来检查各种 test 的覆盖率,以此来保证尽可能多的场景被测试到。

其他措施

Code Style 配置

Android Studio 项目可以配置 code style,默认目录是在 .idea 目录下面,在这里我们可以看到 now in android 项目,也配置自己的 code style。

Commit Hook

tools 目录下面有两个 sh 文件,setup.shpre-push,它们的主要作用就是用来 hook commit,在本项目里面这个 hook 的主要目的是:

  • 校验 branch 名称
  • 检查 WIP or stash
  • 执行 spotless check 任务,确保 push 的文件格式正确

在团队协作的时候,每个团队可能都会有一些要求,比如提交的 branch、commit 命名需要满足特定的规则,这时候 git hook 就能通过设置规则,使得代码的提交更为规范,尽可能地让代码风格保持一致。

巧用 Android Studio 配置

AS 本身也带有 commit checks,建议需要的小伙伴可以按需选择。

总结

结语

nowinandroid项目展示了Google Android团队在代码质量管理方面的最佳实践。其CI体系通过分层校验、自动化防护、智能基线管理等手段,构建了完整质量保障生态。建议开发团队结合自身项目特点,选择性引入相关实践,逐步建立适应自身需求的质量保障体系。值得注意的是,任何自动化检查都应服务于开发效率与代码质量的平衡,避免过度校验导致的开发流程僵化。

相关推荐
QING6181 小时前
一文带你了解Android中常见的跨组件通信方案及其适用场景
android·架构·app
hrrrrb1 小时前
【MySQL】多表操作 —— 外键约束
android·数据库·mysql
QING6181 小时前
一文带你吃透接口(Interface)结合 @AutoService 与 ServiceLoader 详解
android·kotlin·app
二十四桥明月夜ya1 小时前
如何配置Clion编写aosp的c++程序
android·intellij idea
QING6181 小时前
一文带你吃透Android 中 AIDL 与 bindService 的核心区别
android·kotlin·app
九思x3 小时前
解决 Android Studio “waiting for all target devices to come online“ 卡住问题
android·ide·android studio
QING6185 小时前
Android 冷启动优化实践:含主线程优化、资源预加载与懒加载、跨进程预热等
android·性能优化·app
浩宇软件开发5 小时前
Androidstudio实现一个app引导页(超详细)
android·android studio·android开发
左小左5 小时前
用Compose撸一个CoordinatorLayout 🔥🔥🔥
android·android jetpack·composer