Skip to content

feat(android): 对齐移动端构建与发布流水线#658

Open
HKLHaoBin wants to merge 6 commits into
Open-Less:betafrom
HKLHaoBin:feat/android-release-pipeline
Open

feat(android): 对齐移动端构建与发布流水线#658
HKLHaoBin wants to merge 6 commits into
Open-Less:betafrom
HKLHaoBin:feat/android-release-pipeline

Conversation

@HKLHaoBin

@HKLHaoBin HKLHaoBin commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

User description

摘要

Fixes #(无关联 issue;如需可补链 Android 发版跟踪 issue)。

将 Android 移动端构建与发布规则与桌面端 release-tauri.yml 对齐:v*-tauri / v*-beta-tauri tag 触发 release APK、minisign 更新清单与应用内更新(Tauri plugin-updater 不支持 Android,故实现自有 updater 通道)。

修复 / 新增 / 改进

  • CI / 发版:扩展 android-apk.yml(Stable/Beta 渠道、OPENLESS_RELEASE_CHANNEL、tag release 签名检查、debug dispatch vs release tag 双模式、产物命名 OpenLess_<version>_<abi>.apk、上传 .apk.siglatest-android-{arch}[-beta].json)。
  • 脚本:新增 configure-android-release-signing.mjssign-android-apks.mjsmerge-android-updater-manifest.mjs;扩展 write-updater-manifest.mjs 支持 Android ABI。
  • Runtime:新增 android/updater.rs + Kotlin OpenLessUpdateInstaller;Android 启用 supportsAutoUpdate;前端 AutoUpdate.tsx / ipc.ts 走 Android 安装 IPC。
  • 门禁ci.yml 增加 aarch64-linux-android cargo check(含 frontend build 前置)。
  • 文档:更新 README、维护者清单、docs/android-mobile-apk-overlay-plan.md
  • Updater 公钥轮换tauri.conf.json 更新 plugins.updater.pubkey(需上游在 Actions 配置 TAURI_SIGNING_PRIVATE_KEYANDROID_KEYSTORE_*;私钥通过 maintainer 私信交接,不进仓库)。

兼容

  • 不包含:Play Store 上架、iOS、桌面端 release 流程改动(仅共用 tag 与 manifest 命名约定)。
  • 对现有用户 / 本地环境 / 构建流程的影响:
    • 桌面用户:无行为变化;in-app updater 公钥轮换后,旧安装包需通过新安装包升级一次方可继续自动更新。
    • Android:此前无 release 流水线;tag 发版需仓库配置 Android keystore secrets,否则 release 模式 fail-fast。
    • 本地开发workflow_dispatch 仍产出 debug APK;新增 tauri:android:build:debug / tauri:android:build:release 脚本。

测试计划

  • 命令:gh workflow run CI --ref feat/android-release-pipeline + gh workflow run "Android APK (debug)" --ref feat/android-release-pipeline
  • 结果:两条 workflow dispatch 均 success(fork HKLHaoBin/openless
  • 证据路径:Actions run 27467079119(CI)、27467079900(Android debug APK)
  • 命令:git push origin v1.3.6-4-beta-tauri(配置 Secrets 后)
  • 结果:Release Tauri (cross-platform) run 27468473394 + Android APK (debug) run 27468473395 均 success;Release 含 OpenLess_1.3.6-4_arm64-v8a.apklatest-android-aarch64-beta.json
  • 证据路径:https://github.com/HKLHaoBin/openless/releases/tag/v1.3.6-4-beta-tauri
  • 命令:gh workflow run "Release Tauri (cross-platform)" --ref feat/android-release-pipeline
  • 结果:四平台 matrix success(run 27467468100

PR Type

Enhancement, Tests, Documentation


Description

  • Add custom Android in-app updater (minisign verified, system APK install)

  • Extend CI workflows for signed release APKs, updater manifests, and Android cargo check

  • Update frontend, scripts, and documentation for mobile release support


Diagram Walkthrough

flowchart LR
  A[Android APK CI] --> B["Build mode: debug / release"]
  B --> C["Tag v*-tauri: release (signed APK + updater manifest)"]
  B --> D["workflow_dispatch: debug APK"]
  C --> E["minisign signature (.sig)"]
  C --> F["latest-android-{arch}[-beta].json"]
  C --> G["Attach to GitHub Release"]
  H["ci.yml android-check"] --> I["cargo check --target aarch64-linux-android"]
  J["Frontend AutoUpdate.tsx"] --> K["appDownloadAndInstallAndroidUpdate"]
  K --> L["Rust updater: download + verify + install via system installer"]
Loading

File Walkthrough

Relevant files
Enhancement
17 files
jni.rs
Expose JNI helpers for updater                                                     
+17/-2   
mod.rs
Declare updater module                                                                     
+2/-0     
updater.rs
Add Android custom in-app updater                                               
+271/-0 
settings.rs
Add mobile update commands                                                             
+42/-3   
lib.rs
Register mobile update commands                                                   
+5/-0     
types.rs
Enable auto update for Android                                                     
+1/-1     
AutoUpdate.tsx
Support Android update flow                                                           
+83/-17 
ipc.ts
Add Android update IPC                                                                     
+17/-0   
platform.ts
Enable auto update for Android                                                     
+1/-1     
OpenLessUpdateInstaller.kt
Kotlin system APK installer                                                           
+37/-0   
android-apk.yml
Support signed release APK builds                                               
+166/-21
package.json
Add release build script                                                                 
+2/-0     
configure-android-release-signing.mjs
Script for Android release signing config                               
+83/-0   
copy-android-scaffolding.mjs
Copy update installer Kotlin file                                               
+1/-0     
merge-android-updater-manifest.mjs
Merge install permissions into manifest                                   
+98/-0   
sign-android-apks.mjs
Minisign APK signing script                                                           
+61/-0   
write-updater-manifest.mjs
Support Android updater manifests                                               
+47/-12 
Tests
1 files
ci.yml
Add Android cargo check job                                                           
+63/-0   
Documentation
3 files
README.md
Update Android release instructions                                           
+8/-6     
README.zh.md
Update Chinese Android release instructions                           
+18/-3   
android-mobile-apk-overlay-plan.md
Update overlay plan for release pipeline                                 
+36/-26 
Dependencies
1 files
Cargo.toml
Add minisign-verify dependency for Android                             
+1/-0     
Additional files
1 files
tauri.conf.json +1/-1     

HKLHaoBin and others added 6 commits June 13, 2026 19:48
Add signed release APK builds on v*-tauri tags, minisign manifests per ABI, custom Android in-app updater, and CI cargo check for aarch64-linux-android.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
generate_context! requires ../dist; the android-check job was missing npm ci + vite build, causing proc macro panic on CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
Pair with TAURI_SIGNING_PRIVATE_KEY set via gh secret set. Old pubkey superseded for new tag releases.

Co-authored-by: Cursor <cursoragent@cursor.com>
@HKLHaoBin

Copy link
Copy Markdown
Contributor Author

首先按照 OPENLESS_RELEASE_SECRETS_HANDOFF.md 在仓库中设置环境变量(或者你把这个文件提供给 AI,让 AI 通过 gh CLI 工具直接以命令方式为你的仓库配置 Secret。),之后再合并我的 PR。

@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

JNI field access

device_arch() uses env.call_static_method to read Build.SUPPORTED_ABIS, but this is a static field, not a method. It should use get_static_field with the field signature "[Ljava/lang/String;". The current code will throw a JNI error at runtime when checking for updates on Android, preventing any update from being discovered.

crate::android::jni::android::with_android_env(|env, _context| {
    let abis_obj = env
        .call_static_method(
            "android/os/Build",
            "SUPPORTED_ABIS",
            "()[Ljava/lang/String;",
            &[],
        )
        .and_then(|value| value.l())
        .map_err(|e| format!("read SUPPORTED_ABIS: {e}"))?;
Public key mismatch

PUBKEY_B64 is hardcoded to the old minisign public key, but tauri.conf.json has been updated to a new public key in this PR. When a new release is built, the desktop updater will use the new key, but the Android updater will use the old key, causing signature verification of new APKs to fail. All Android in-app updates will be rejected after the first release with the new key.

const PUBKEY_B64: &str = "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFERUFBODAzNTY0QzMyM0YKUldRL01reFdBNmpxSGE1K0JadlpONXNWTzhJcGZCRGxjUVdIWExNNFJpeUNsSGZwazdlQThhemkK";
Misleading install result

installApk returns false immediately after starting an intent to request INSTALL_PACKAGES permission (API >= O). The Rust caller treats Ok(false) as success, so the update flow reports 'downloaded' without actually installing the APK. The user sees a success state but no installation occurs; the system permission dialog may have been shown, but the user receives no feedback to retry. The method should either block until installation completes or return an error that propagates to the UI.

fun installApk(context: Context, apkPath: String): Boolean {
    val apkFile = File(apkPath)
    if (!apkFile.exists()) {
        return false
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.packageManager.canRequestPackageInstalls()) {
        val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
            data = Uri.parse("package:${context.packageName}")
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        context.startActivity(intent)
        return false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant