# Android 应用 Workflow 模板 适用于 Android 应用的 CI/CD workflow,支持 APK 构建、签名和发布。 ## 适用场景 - Kotlin / Java Android 应用 - Jetpack Compose 项目 - 需要签名发布的 APK - 需要创建 Release 的项目 ## 环境要求 | 依赖 | Runner 要求 | |------|------------| | JDK 17+ | Runner 主机已安装 | | Android SDK | Runner 主机已安装 | | Gradle | 由 wrapper 管理 | **重要**:必须使用 `darwin-arm64` 标签的 macOS Runner,因为 Google 不提供 Linux ARM64 版本的 Android SDK。 ## Workflow 骨架模板 ```yaml name: Android - Build & Release APK on: push: paths: - 'android/**' # 修改为实际目录 - '.gitea/workflows/android.yml' tags: - 'android-*' # 修改为实际 tag 前缀 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: SERVICE_PREFIX: android # 修改为实际服务名 SERVICE_DIR: android # 修改为实际目录名 APP_NAME: myapp-android # 修改为实际应用名 jobs: build: name: Build Release APK runs-on: darwin-arm64 # macOS ARM64(必须) permissions: contents: read packages: write outputs: version_name: ${{ steps.vars.outputs.version_name }} version_code: ${{ steps.vars.outputs.version_code }} apk_name: ${{ steps.vars.outputs.apk_name }} steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Verify Java environment run: | java -version echo "JAVA_HOME=$JAVA_HOME" - name: Setup Android SDK run: | if [ -n "$ANDROID_HOME" ] && [ -d "$ANDROID_HOME" ]; then echo "Using ANDROID_HOME: $ANDROID_HOME" else for SDK_PATH in ~/Library/Android/sdk ~/android-sdk /opt/homebrew/share/android-commandlinetools; do if [ -d "$SDK_PATH" ]; then export ANDROID_HOME=$SDK_PATH echo "Found Android SDK: $SDK_PATH" break fi done fi if [ -z "$ANDROID_HOME" ] || [ ! -d "$ANDROID_HOME" ]; then echo "ERROR: Android SDK not found" exit 1 fi echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV echo "$ANDROID_HOME/cmdline-tools/latest/bin" >> $GITHUB_PATH echo "$ANDROID_HOME/platform-tools" >> $GITHUB_PATH - name: Get gradle-hashfiles id: hash-gradle working-directory: ${{ env.SERVICE_DIR }} run: | HASH=$(sha256sum gradle/libs.versions.toml app/build.gradle.kts build.gradle.kts settings.gradle.kts 2>/dev/null | sha256sum | awk '{print $1}' | head -c 16) echo "hash=${HASH}" >> "$GITHUB_OUTPUT" - name: Cache Gradle uses: https://github.com/actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper key: gradle-${{ env.SERVICE_PREFIX }}-${{ steps.hash-gradle.outputs.hash }} restore-keys: gradle-${{ env.SERVICE_PREFIX }}- - name: Set variables id: vars run: | # 从 build.gradle.kts 提取版本信息(按需修改路径和正则) version_name=$(grep 'versionName' ${{ env.SERVICE_DIR }}/app/build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/') version_code=$(grep 'versionCode' ${{ env.SERVICE_DIR }}/app/build.gradle.kts | head -1 | sed 's/[^0-9]*//g') git_tag=$(git describe --tags --abbrev=0 --always) apk_name="${{ env.APP_NAME }}-${version_name}" { echo "version_name=${version_name}" echo "version_code=${version_code}" echo "git_tag=${git_tag}" echo "apk_name=${apk_name}" } >> $GITHUB_ENV echo "version_name=${version_name}" >> $GITHUB_OUTPUT echo "version_code=${version_code}" >> $GITHUB_OUTPUT echo "apk_name=${apk_name}" >> $GITHUB_OUTPUT - name: Setup signing env: KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | # 创建 local.properties echo "sdk.dir=$ANDROID_HOME" > ${{ env.SERVICE_DIR }}/local.properties # 配置签名(如果提供了 keystore) if [ -n "$KEYSTORE_BASE64" ]; then echo "$KEYSTORE_BASE64" | base64 -d > ${{ env.SERVICE_DIR }}/release.keystore { echo "KEYSTORE_FILE=../release.keystore" echo "KEYSTORE_PASSWORD=$KEYSTORE_PASSWORD" echo "KEY_ALIAS=$KEY_ALIAS" echo "KEY_PASSWORD=$KEY_PASSWORD" } >> ${{ env.SERVICE_DIR }}/local.properties echo "Signing configured" else echo "WARNING: No signing key provided, building unsigned APK" fi - name: Build Release APK working-directory: ${{ env.SERVICE_DIR }} run: | chmod +x gradlew # ============================================ # 用户自定义构建步骤(按项目需求修改) # ============================================ # 清理(按需启用) # ./gradlew clean # 单元测试(按需启用) # ./gradlew test # 构建 Release APK(必须) ./gradlew assembleRelease --no-daemon - name: Rename APK run: | mkdir -p dist cp ${{ env.SERVICE_DIR }}/app/build/outputs/apk/release/app-release.apk dist/${{ env.apk_name }}.apk cd dist sha256sum ${{ env.apk_name }}.apk > ${{ env.apk_name }}.apk.sha256 - name: Upload artifact uses: actions/upload-artifact@v3 with: name: ${{ env.SERVICE_PREFIX }}-apk path: dist/ - name: Notify if: always() continue-on-error: true env: WEBHOOK_URL: ${{ vars.WEBHOOK_URL }} run: | status="${{ job.status }}" [ "$status" = "success" ] && status_text="Build Success" || status_text="Build Failed" curl -s -H "Content-Type: application/json" -X POST -d \ "{\"msg_type\":\"text\",\"content\":{\"text\":\"${{ env.APP_NAME }} ${status_text}\\nVersion: ${{ env.version_name }}\"}}" \ "$WEBHOOK_URL" || true release: name: Create Release runs-on: darwin-arm64 needs: build if: startsWith(github.ref, 'refs/tags/') steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Download artifact uses: actions/download-artifact@v3 with: name: ${{ env.SERVICE_PREFIX }}-apk path: dist - name: Create Release env: GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }} VERSION_NAME: ${{ needs.build.outputs.version_name }} APK_NAME: ${{ needs.build.outputs.apk_name }} run: | git_tag=$(git describe --tags --abbrev=0) api_url="${{ github.server_url }}/api/v1" repo="${{ github.repository }}" # 创建 Release release_id=$(curl -s -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"${git_tag}\",\"name\":\"Release ${git_tag} (v${VERSION_NAME})\"}" \ "${api_url}/repos/${repo}/releases" | jq -r '.id') # 上传 APK curl -s -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -F "attachment=@dist/${APK_NAME}.apk" \ "${api_url}/repos/${repo}/releases/${release_id}/assets" # 上传校验和 curl -s -X POST \ -H "Authorization: token $GITEA_TOKEN" \ -F "attachment=@dist/${APK_NAME}.apk.sha256" \ "${api_url}/repos/${repo}/releases/${release_id}/assets" ``` --- ## 签名配置 ### 1. 生成签名密钥 ```bash keytool -genkey -v -keystore release.keystore \ -alias myapp \ -keyalg RSA \ -keysize 2048 \ -validity 10000 ``` ### 2. Base64 编码 ```bash base64 -i release.keystore -o keystore.base64 # 将 keystore.base64 内容复制到 ANDROID_KEYSTORE_BASE64 secret ``` ### 3. build.gradle.kts 签名配置 ```kotlin android { signingConfigs { create("release") { val props = Properties() val propsFile = rootProject.file("local.properties") if (propsFile.exists()) { props.load(propsFile.inputStream()) val keystoreFile = props.getProperty("KEYSTORE_FILE", "") if (keystoreFile.isNotEmpty()) { storeFile = file(keystoreFile) storePassword = props.getProperty("KEYSTORE_PASSWORD", "") keyAlias = props.getProperty("KEY_ALIAS", "") keyPassword = props.getProperty("KEY_PASSWORD", "") } } } } buildTypes { release { isMinifyEnabled = true isShrinkResources = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) signingConfig = signingConfigs.getByName("release") } } } ``` --- ## 缓存配置 ### 缓存路径 ```yaml path: | ~/.gradle/caches # Gradle 依赖缓存 ~/.gradle/wrapper # Gradle wrapper ``` ### Key 计算 ```bash HASH=$(sha256sum gradle/libs.versions.toml app/build.gradle.kts build.gradle.kts settings.gradle.kts | sha256sum | awk '{print $1}' | head -c 16) ``` --- ## Secrets 配置 | Secret | 用途 | |--------|------| | `ANDROID_KEYSTORE_BASE64` | Base64 编码的 keystore 文件 | | `ANDROID_KEYSTORE_PASSWORD` | keystore 密码 | | `ANDROID_KEY_ALIAS` | 密钥别名 | | `ANDROID_KEY_PASSWORD` | 密钥密码 | | `RELEASE_TOKEN` | Gitea API 令牌(创建 Release) | --- ## Gradle 优化 ### gradle.properties ```properties # CI 环境优化 org.gradle.daemon=false org.gradle.parallel=true org.gradle.caching=true org.gradle.configureondemand=true # Android 配置 android.useAndroidX=true android.nonTransitiveRClass=true ``` ### 国内镜像(可选) ```properties # gradle-wrapper.properties distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip ``` --- ## 版本提取 从 `build.gradle.kts` 提取版本信息: ```bash # versionName version_name=$(grep 'versionName' app/build.gradle.kts | head -1 | sed 's/.*"\(.*\)".*/\1/') # versionCode version_code=$(grep 'versionCode' app/build.gradle.kts | head -1 | sed 's/[^0-9]*//g') ``` --- ## 常见 Gradle 任务 | 任务 | 说明 | |------|------| | `./gradlew assembleRelease` | 构建 Release APK | | `./gradlew assembleDebug` | 构建 Debug APK | | `./gradlew bundleRelease` | 构建 AAB(App Bundle)| | `./gradlew test` | 运行单元测试 | | `./gradlew lint` | 运行 Lint 检查 | | `./gradlew clean` | 清理构建产物 | --- ## 使用步骤 1. 复制上方 workflow 模板到 `.gitea/workflows/android.yml` 2. 修改 `SERVICE_PREFIX`、`SERVICE_DIR`、`APP_NAME` 为实际值 3. 修改 `paths` 和 `tags` 触发条件 4. 配置 `build.gradle.kts` 签名(参考上方示例) 5. 配置 Secrets(keystore、密码等) 6. 推送代码触发 workflow --- ## 版本发布 ```bash # 创建并推送 tag git tag android-1.0.0 git push origin android-1.0.0 ```