feat(android): T-AND-001 工程骨架(AGP 8.7.3 + Gradle 8.9 + Compose)
This commit is contained in:
Executable
+129
@@ -0,0 +1,129 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# OpenDance Studio —— Android APK 打包(T-AND-009)
|
||||
#
|
||||
# 文档 §6.3 方式二:命令行 Gradle
|
||||
#
|
||||
# 前置:
|
||||
# JAVA_HOME 指向 JDK 17(AGP 8.7 要求)
|
||||
# ANDROID_HOME / ANDROID_SDK_ROOT 指向 Android SDK
|
||||
# 需要 platforms;android-34 与 build-tools;34.0.0
|
||||
#
|
||||
# 用法:
|
||||
# bash build/build-apk.sh # 同时产出 debug 与 release
|
||||
# bash build/build-apk.sh debug
|
||||
# bash build/build-apk.sh release
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
TARGET="${1:-all}"
|
||||
case "$TARGET" in
|
||||
debug|release|all) ;;
|
||||
*) echo "用法:bash build/build-apk.sh [debug|release|all]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
log() { printf '\033[1;34m[apk]\033[0m %s\n' "$*"; }
|
||||
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# ---- 环境检查 ----
|
||||
# 优先使用 JAVA_HOME 指定的 JDK:PATH 上的 java 可能是更新的版本,
|
||||
# AGP 8.7 需要 17~21,检测时必须与 Gradle 实际使用的 JVM 一致。
|
||||
JAVA_BIN="java"
|
||||
if [ -n "${JAVA_HOME:-}" ] && [ -x "$JAVA_HOME/bin/java" ]; then
|
||||
JAVA_BIN="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
command -v "$JAVA_BIN" >/dev/null 2>&1 || die "未找到 java,请安装 JDK 17 或设置 JAVA_HOME"
|
||||
JAVA_MAJOR="$("$JAVA_BIN" -version 2>&1 | head -1 | sed -E 's/.*version "([0-9]+).*/\1/')"
|
||||
log "Java:$JAVA_BIN (主版本 $JAVA_MAJOR,AGP 8.7 需要 17+)"
|
||||
if [ "$JAVA_MAJOR" -lt 17 ] 2>/dev/null; then
|
||||
die "JDK 版本过低:需要 17 或更高(当前 $JAVA_MAJOR)"
|
||||
fi
|
||||
|
||||
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
|
||||
if [ -z "$SDK" ]; then
|
||||
for candidate in "$HOME/Android/Sdk" /opt/android-sdk /usr/lib/android-sdk; do
|
||||
[ -d "$candidate" ] && SDK="$candidate" && break
|
||||
done
|
||||
fi
|
||||
[ -n "$SDK" ] && [ -d "$SDK" ] || die "未找到 Android SDK,请设置 ANDROID_HOME"
|
||||
log "Android SDK:$SDK"
|
||||
|
||||
if [ ! -f "$PROJECT_DIR/local.properties" ]; then
|
||||
echo "sdk.dir=$SDK" > "$PROJECT_DIR/local.properties"
|
||||
log "已写入 local.properties"
|
||||
fi
|
||||
|
||||
[ -f "$PROJECT_DIR/gradlew" ] || die "缺少 gradlew(请先执行 gradle wrapper)"
|
||||
|
||||
# ---- 签名检查 ----
|
||||
if [ "$TARGET" != "debug" ]; then
|
||||
if [ -f "$PROJECT_DIR/keystore.properties" ]; then
|
||||
log "使用 keystore.properties 中的发布签名"
|
||||
else
|
||||
log "提示:未找到 keystore.properties,release 将回退为 debug 签名"
|
||||
log " 正式发布请先执行:bash build/gen-keystore.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- 构建 ----
|
||||
if [ "$TARGET" = "debug" ] || [ "$TARGET" = "all" ]; then
|
||||
log "构建 debug APK…"
|
||||
./gradlew assembleDebug --console=plain
|
||||
fi
|
||||
if [ "$TARGET" = "release" ] || [ "$TARGET" = "all" ]; then
|
||||
log "构建 release APK…"
|
||||
./gradlew assembleRelease --console=plain
|
||||
fi
|
||||
|
||||
# ---- 校验产物 ----
|
||||
DEBUG_APK="app/build/outputs/apk/debug/app-debug.apk"
|
||||
RELEASE_APK="app/build/outputs/apk/release/app-release.apk"
|
||||
|
||||
verify_apk() {
|
||||
local apk="$1"
|
||||
local label="$2"
|
||||
[ -f "$apk" ] || { echo " ✗ 未生成 $label:$apk"; return 1; }
|
||||
|
||||
local size
|
||||
size=$(stat -c%s "$apk")
|
||||
|
||||
# APK 是 zip,校验魔数 PK
|
||||
local magic
|
||||
magic=$(head -c 2 "$apk" | od -An -tx1 | tr -d ' \n')
|
||||
[ "$magic" = "504b" ] || { echo " ✗ $label 不是合法 APK(魔数 $magic)"; return 1; }
|
||||
|
||||
# 校验是否已签名:APK v2 签名块附近含 "APK Sig Block 42"
|
||||
local signed="未检出 v2 签名块"
|
||||
if grep -qa "APK Sig Block 42" "$apk"; then
|
||||
signed="已签名(APK Signature Scheme v2)"
|
||||
elif unzip -l "$apk" 2>/dev/null | grep -q "META-INF/.*\.RSA"; then
|
||||
signed="已签名(v1 JAR 签名)"
|
||||
fi
|
||||
|
||||
echo " ✓ $label:$apk"
|
||||
echo " 大小 $((size / 1024)) KiB | $signed"
|
||||
return 0
|
||||
}
|
||||
|
||||
echo ""
|
||||
log "产物校验:"
|
||||
FAILED=0
|
||||
if [ "$TARGET" = "debug" ] || [ "$TARGET" = "all" ]; then
|
||||
verify_apk "$DEBUG_APK" "Debug APK" || FAILED=1
|
||||
fi
|
||||
if [ "$TARGET" = "release" ] || [ "$TARGET" = "all" ]; then
|
||||
verify_apk "$RELEASE_APK" "Release APK" || FAILED=1
|
||||
fi
|
||||
|
||||
[ "$FAILED" -eq 0 ] || die "有产物未通过校验"
|
||||
|
||||
echo ""
|
||||
log "安装到已连接设备:"
|
||||
echo " adb install -r $DEBUG_APK"
|
||||
echo " adb install -r $RELEASE_APK"
|
||||
echo ""
|
||||
log "查看签名信息:"
|
||||
echo " \$ANDROID_HOME/build-tools/34.0.0/apksigner verify --print-certs $RELEASE_APK"
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# OpenDance Studio —— 生成发布签名密钥库
|
||||
#
|
||||
# 生成的 keystore 与 keystore.properties **不会入库**(见 .gitignore),
|
||||
# 请自行妥善备份;丢失后无法对已发布 APK 做覆盖安装升级。
|
||||
#
|
||||
# 用法:
|
||||
# bash build/gen-keystore.sh # 交互式,使用默认文件名
|
||||
# CN=yourname bash build/gen-keystore.sh # 指定证书 CN
|
||||
# KEYSTORE_PASSWORD=xxx KEY_PASSWORD=yyy bash build/gen-keystore.sh
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
KEYSTORE_DIR="$PROJECT_DIR/keystore"
|
||||
KEYSTORE_FILE="$KEYSTORE_DIR/opendance-release.jks"
|
||||
PROPS_FILE="$PROJECT_DIR/keystore.properties"
|
||||
|
||||
ALIAS="${KEY_ALIAS:-opendance}"
|
||||
STORE_PASS="${KEYSTORE_PASSWORD:-}"
|
||||
KEY_PASS="${KEY_PASSWORD:-}"
|
||||
CN="${CN:-OpenDance Studio}"
|
||||
DNAME="${DNAME:-CN=$CN, OU=OpenDance, O=OpenDance Studio, L=Unknown, ST=Unknown, C=CN}"
|
||||
VALIDITY_DAYS="${VALIDITY_DAYS:-10000}"
|
||||
|
||||
if ! command -v keytool >/dev/null 2>&1; then
|
||||
echo "错误:未找到 keytool。请安装 JDK(17+)并确保 keytool 在 PATH 中。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$KEYSTORE_FILE" ]; then
|
||||
echo "密钥库已存在:$KEYSTORE_FILE"
|
||||
echo "如需重新生成,请先删除该文件(注意:会导致已发布 APK 无法升级安装)。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$STORE_PASS" ]; then
|
||||
read -r -s -p "请输入密钥库口令(至少 6 位):" STORE_PASS; echo
|
||||
read -r -s -p "请再次输入: " STORE_PASS_CONFIRM; echo
|
||||
[ "$STORE_PASS" = "$STORE_PASS_CONFIRM" ] || { echo "两次输入不一致" >&2; exit 1; }
|
||||
fi
|
||||
|
||||
if [ -z "$KEY_PASS" ]; then
|
||||
KEY_PASS="$STORE_PASS"
|
||||
fi
|
||||
|
||||
if [ ${#STORE_PASS} -lt 6 ]; then
|
||||
echo "错误:密钥库口令至少 6 位。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$KEYSTORE_DIR"
|
||||
|
||||
echo "[keystore] 生成 $KEYSTORE_FILE"
|
||||
keytool -genkeypair \
|
||||
-keystore "$KEYSTORE_FILE" \
|
||||
-alias "$ALIAS" \
|
||||
-keyalg RSA \
|
||||
-keysize 2048 \
|
||||
-validity "$VALIDITY_DAYS" \
|
||||
-storetype PKCS12 \
|
||||
-storepass "$STORE_PASS" \
|
||||
-keypass "$KEY_PASS" \
|
||||
-dname "$DNAME" \
|
||||
-v >/dev/null
|
||||
|
||||
cat > "$PROPS_FILE" <<EOF
|
||||
# 由 build/gen-keystore.sh 生成;**禁止提交到仓库**(已在 .gitignore 中)
|
||||
storeFile=$KEYSTORE_FILE
|
||||
storePassword=$STORE_PASS
|
||||
keyAlias=$ALIAS
|
||||
keyPassword=$KEY_PASS
|
||||
EOF
|
||||
chmod 600 "$PROPS_FILE" "$KEYSTORE_FILE"
|
||||
|
||||
echo ""
|
||||
echo "[keystore] 完成:"
|
||||
echo " 密钥库:$KEYSTORE_FILE"
|
||||
echo " 配置: $PROPS_FILE"
|
||||
echo " 别名: $ALIAS"
|
||||
echo " 有效期:$VALIDITY_DAYS 天"
|
||||
echo ""
|
||||
echo "请备份这两个文件。之后 ./gradlew assembleRelease 会自动使用它们签名。"
|
||||
Executable
+50
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# OpenDance Studio —— Android 辅助 App 发布打包
|
||||
#
|
||||
# 产物(文档 §6.3):
|
||||
# opendance-android-v0.1.0.zip
|
||||
# ├── app-release.apk
|
||||
# ├── THIRD_PARTY.md
|
||||
# ├── README.txt
|
||||
# └── contracts/ 契约快照
|
||||
#
|
||||
# 用法:bash build/package-release.sh [版本号]
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
VERSION="${1:-0.1.0}"
|
||||
NAME="opendance-android-v${VERSION}"
|
||||
OUT_DIR="$PROJECT_DIR/dist-release"
|
||||
STAGE="$OUT_DIR/$NAME"
|
||||
APK="app/build/outputs/apk/release/app-release.apk"
|
||||
|
||||
log() { printf '\033[1;34m[package]\033[0m %s\n' "$*"; }
|
||||
die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
[ -f "$APK" ] || die "未找到 $APK,请先执行 bash build/build-apk.sh release"
|
||||
|
||||
# APK 是 zip,校验魔数
|
||||
MAGIC=$(head -c 2 "$APK" | od -An -tx1 | tr -d ' \n')
|
||||
[ "$MAGIC" = "504b" ] || die "$APK 不是合法 APK(魔数 $MAGIC)"
|
||||
|
||||
log "整理产物到 $STAGE …"
|
||||
rm -rf "$STAGE"
|
||||
mkdir -p "$STAGE"
|
||||
|
||||
cp "$APK" "$STAGE/app-release.apk"
|
||||
cp THIRD_PARTY.md README.txt "$STAGE/"
|
||||
cp -r contracts "$STAGE/contracts"
|
||||
|
||||
log "生成压缩包…"
|
||||
( cd "$OUT_DIR" && zip -qr "$NAME.zip" "$NAME" )
|
||||
|
||||
log "完成:$OUT_DIR/$NAME.zip($(du -h "$OUT_DIR/$NAME.zip" | cut -f1))"
|
||||
echo ""
|
||||
echo "产物内容:"
|
||||
unzip -l "$OUT_DIR/$NAME.zip" | sed -n '4,20p'
|
||||
echo ""
|
||||
echo "分发:把 APK 交给用户(Android 10+),或解压后 adb install -r app-release.apk"
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# OpenDance Studio —— Android 辅助 App 一键验证
|
||||
#
|
||||
# 依次执行:单元测试 → debug 构建 → release 构建 → 产物校验
|
||||
# 任一环节失败即退出非 0。
|
||||
#
|
||||
# 用法:
|
||||
# JAVA_HOME=/path/to/jdk17 ANDROID_HOME=/path/to/sdk bash build/verify.sh
|
||||
# ============================================================
|
||||
set -uo pipefail
|
||||
|
||||
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
FAILED=0
|
||||
|
||||
echo "============================================================"
|
||||
echo " 1/3 单元测试(JVM,无需设备)"
|
||||
echo "============================================================"
|
||||
if ./gradlew testDebugUnitTest --console=plain -q 2>&1 | tail -20; then
|
||||
echo "✓ 单元测试通过"
|
||||
else
|
||||
echo "✗ 单元测试失败"
|
||||
FAILED=1
|
||||
fi
|
||||
echo " 测试报告:app/build/reports/tests/testDebugUnitTest/index.html"
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " 2/3 构建 debug + release APK"
|
||||
echo "============================================================"
|
||||
if bash build/build-apk.sh all 2>&1 | grep -vE "^> Task|^$" | tail -20; then
|
||||
:
|
||||
else
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " 3/3 产物校验(签名 / 包信息 / 模型资源)"
|
||||
echo "============================================================"
|
||||
SDK="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-$HOME/Android/Sdk}}"
|
||||
# 优先选用与 compileSdk 匹配的 build-tools,缺失时退回最新版
|
||||
if [ -d "$SDK/build-tools/34.0.0" ]; then
|
||||
BT="$SDK/build-tools/34.0.0"
|
||||
else
|
||||
BT="$(ls -d "$SDK"/build-tools/* 2>/dev/null | sort -V | tail -1)"
|
||||
fi
|
||||
RELEASE_APK="app/build/outputs/apk/release/app-release.apk"
|
||||
|
||||
if [ ! -f "$RELEASE_APK" ]; then
|
||||
echo "✗ 未生成 $RELEASE_APK"
|
||||
FAILED=1
|
||||
elif [ -z "$BT" ]; then
|
||||
echo "✗ 未找到 build-tools,无法校验"
|
||||
FAILED=1
|
||||
else
|
||||
# 以**退出码**为准:不同 build-tools 版本的 apksigner 输出文案不一致
|
||||
# (34.0.0 会打印 "Verifies",36.0.0 配 --print-certs 时只打印证书信息),
|
||||
# 依赖日志字符串判断会导致误判。
|
||||
"$BT/apksigner" verify "$RELEASE_APK" > /tmp/apk_verify.txt 2>&1
|
||||
VERIFY_STATUS=$?
|
||||
"$BT/apksigner" verify --print-certs "$RELEASE_APK" > /tmp/apk_sign.txt 2>/dev/null
|
||||
|
||||
if [ "$VERIFY_STATUS" -eq 0 ]; then
|
||||
echo "✓ 签名校验通过(apksigner 退出码 0)"
|
||||
grep -E "Signer #1 certificate DN" /tmp/apk_sign.txt | sed 's/^/ /' || true
|
||||
if grep -q "APK Signature Scheme v2" /tmp/apk_verify.txt 2>/dev/null; then
|
||||
echo " v2 签名方案:已启用"
|
||||
fi
|
||||
else
|
||||
echo "✗ 签名校验失败(apksigner 退出码 $VERIFY_STATUS)"
|
||||
cat /tmp/apk_verify.txt
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
"$BT/aapt2" dump badging "$RELEASE_APK" 2>/dev/null | head -1 | sed 's/^/ /'
|
||||
|
||||
if unzip -l "$RELEASE_APK" | grep -q "assets/pose_landmarker_lite.task"; then
|
||||
echo "✓ MediaPipe 模型已随包分发"
|
||||
else
|
||||
echo "✗ APK 中缺少 MediaPipe 模型"
|
||||
FAILED=1
|
||||
fi
|
||||
|
||||
ABIS=$(unzip -l "$RELEASE_APK" | grep -oE "lib/[^/]+/" | sort -u | tr '\n' ' ')
|
||||
echo "✓ 包含 ABI:$ABIS"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ "$FAILED" -ne 0 ]; then
|
||||
echo "============================================================"
|
||||
echo " 验证失败"
|
||||
echo "============================================================"
|
||||
exit 1
|
||||
fi
|
||||
echo "============================================================"
|
||||
echo " 验证全部通过"
|
||||
echo "============================================================"
|
||||
Reference in New Issue
Block a user