Files
Voson 5a05d5ab53 chore: 重构 OpenCode 命令和技能文档体系
- 新增:统一的 git 命令文档(add/commit/push/pull 等)
- 新增:整合的 Gitea 技能文档(API、运行器、工作流等)
- 新增:工作流模板(Android、Go、Node.js 等)
- 移除:已弃用的旧命令脚本和发布脚本
- 改进:.gitignore 添加敏感文件保护规则
- 改进:AGENTS.md 完善了开发规范和示例

此次重组统一了命令和技能的文档结构,便于后续维护和扩展。
2026-01-13 00:27:21 +08:00

400 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | 构建 AABApp 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. 配置 Secretskeystore、密码等
6. 推送代码触发 workflow
---
## 版本发布
```bash
# 创建并推送 tag
git tag android-1.0.0
git push origin android-1.0.0
```