fix: manifest放回仓库源码 + APK按版本号命名
- manifest.json/beta/nightly 从Release Assets移到源码仓库 - APK命名: gca-v0.1.0 → v0.1.0-dav-android-debug.apk - Release #7: 仅含APK(prerelease), manifest从raw URL拉取 - 三渠道manifest: manifest.json / manifest-beta.json / manifest-nightly.json
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
apply plugin: "com.android.application"
|
apply plugin: "com.android.application"
|
||||||
apply plugin: "org.jetbrains.kotlin.android"
|
apply plugin: "org.jetbrains.kotlin.android"
|
||||||
|
|
||||||
archivesBaseName = "gca-v0.1.0"
|
archivesBaseName = "v0.1.0-dav-android"
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "dev.yuzu.gca"
|
namespace "dev.yuzu.gca"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package dev.yuzu.gca
|
package dev.yuzu.gca
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
@@ -15,6 +16,7 @@ import java.security.MessageDigest
|
|||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var logView: TextView
|
private lateinit var logView: TextView
|
||||||
|
private var selectedChannel = "stable"
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -22,25 +24,61 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val scroll = ScrollView(this)
|
val scroll = ScrollView(this)
|
||||||
val layout = LinearLayout(this).apply {
|
val layout = LinearLayout(this).apply {
|
||||||
orientation = LinearLayout.VERTICAL
|
orientation = LinearLayout.VERTICAL
|
||||||
setPadding(32, 32, 32, 32)
|
setPadding(24, 24, 24, 24)
|
||||||
}
|
}
|
||||||
|
|
||||||
val title = TextView(this).apply {
|
val title = TextView(this).apply {
|
||||||
text = "Yuzu GCA — OTA 测试"
|
text = "Yuzu GCA v0.1.0"
|
||||||
textSize = 22f
|
textSize = 20f
|
||||||
setPadding(0, 0, 0, 16)
|
setPadding(0, 0, 0, 8)
|
||||||
}
|
}
|
||||||
layout.addView(title)
|
layout.addView(title)
|
||||||
|
|
||||||
logView = TextView(this).apply {
|
val channelLabel = TextView(this).apply {
|
||||||
|
text = "选择更新渠道:"
|
||||||
textSize = 13f
|
textSize = 13f
|
||||||
|
setPadding(0, 8, 0, 4)
|
||||||
|
}
|
||||||
|
layout.addView(channelLabel)
|
||||||
|
|
||||||
|
// 渠道按钮行
|
||||||
|
val channelRow = LinearLayout(this).apply {
|
||||||
|
orientation = LinearLayout.HORIZONTAL
|
||||||
|
setPadding(0, 0, 0, 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeChannelBtn(label: String, channel: String): Button {
|
||||||
|
return Button(this).apply {
|
||||||
|
text = label
|
||||||
|
textSize = 12f
|
||||||
|
setOnClickListener {
|
||||||
|
selectedChannel = channel
|
||||||
|
highlightChannel(channelRow, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val btnStable = makeChannelBtn("Stable", "stable")
|
||||||
|
val btnBeta = makeChannelBtn("Beta", "beta")
|
||||||
|
val btnNightly = makeChannelBtn("Nightly", "nightly")
|
||||||
|
|
||||||
|
channelRow.addView(btnStable)
|
||||||
|
channelRow.addView(btnBeta)
|
||||||
|
channelRow.addView(btnNightly)
|
||||||
|
layout.addView(channelRow)
|
||||||
|
highlightChannel(channelRow, btnStable)
|
||||||
|
|
||||||
|
logView = TextView(this).apply {
|
||||||
|
textSize = 12f
|
||||||
|
setTextColor(Color.LTGRAY)
|
||||||
}
|
}
|
||||||
layout.addView(logView)
|
layout.addView(logView)
|
||||||
|
|
||||||
val btn = Button(this).apply {
|
val btn = Button(this).apply {
|
||||||
text = "检查更新并安装"
|
text = "检查更新"
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
CoroutineScope(Dispatchers.IO).launch { doOtaTest() }
|
val ch = selectedChannel
|
||||||
|
CoroutineScope(Dispatchers.IO).launch { doOtaTest(ch) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layout.addView(btn)
|
layout.addView(btn)
|
||||||
@@ -48,74 +86,91 @@ class MainActivity : AppCompatActivity() {
|
|||||||
scroll.addView(layout)
|
scroll.addView(layout)
|
||||||
setContentView(scroll)
|
setContentView(scroll)
|
||||||
|
|
||||||
appendLog("设备已就绪\n点击按钮开始 OTA 端到端测试")
|
appendLog("渠道: stable | 点击[检查更新]开始")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun highlightChannel(row: LinearLayout, active: Button) {
|
||||||
|
for (i in 0 until row.childCount) {
|
||||||
|
val b = row.getChildAt(i) as Button
|
||||||
|
b.isSelected = (b == active)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun appendLog(msg: String) {
|
private fun appendLog(msg: String) {
|
||||||
runOnUiThread { logView.append("$msg\n") }
|
runOnUiThread { logView.append("$msg\n") }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doOtaTest() = withContext(Dispatchers.IO) {
|
private fun manifestUrl(channel: String): String {
|
||||||
appendLog("=== OTA 端到端测试开始 ===")
|
val base = "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/raw/branch/main/packages/client-android"
|
||||||
|
return when (channel) {
|
||||||
|
"beta" -> "$base/manifest-beta.json"
|
||||||
|
"nightly" -> "$base/manifest-nightly.json"
|
||||||
|
else -> "$base/manifest.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun doOtaTest(channel: String) = withContext(Dispatchers.IO) {
|
||||||
|
val url = manifestUrl(channel)
|
||||||
|
appendLog("=== OTA 渠道: $channel ===")
|
||||||
|
appendLog("manifest: $url")
|
||||||
|
|
||||||
|
// 1. 拉取 manifest
|
||||||
appendLog("[1/4] 拉取 manifest...")
|
appendLog("[1/4] 拉取 manifest...")
|
||||||
|
val manifestJson: String
|
||||||
try {
|
try {
|
||||||
val manifestJson = httpGet("https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/manifest.json")
|
manifestJson = httpGet(url)
|
||||||
appendLog(" manifest: ${manifestJson.take(200)}...")
|
appendLog(" ✅ 获取成功 (${manifestJson.length}B)")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
appendLog(" ⚠️ 网络不可用: ${e.message}")
|
appendLog(" ❌ 获取失败: ${e.message}")
|
||||||
appendLog(" 代码逻辑已验证(版本解析/SHA256/PackageInstaller)")
|
appendLog("=== 测试中止 ===")
|
||||||
appendLog("=== OTA 测试完成(离线模式) ===")
|
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
appendLog("[2/4] 解析 manifest JSON...")
|
// 2. 解析
|
||||||
|
appendLog("[2/4] 解析 manifest...")
|
||||||
var manifestSha256 = ""
|
var manifestSha256 = ""
|
||||||
var manifestApkUrl = ""
|
var manifestApkUrl = ""
|
||||||
|
var manifestTag = ""
|
||||||
try {
|
try {
|
||||||
val manifestJson = httpGet("https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/manifest.json")
|
fun extractJson(key: String): String {
|
||||||
// 简易 JSON 解析(不用 Gson,直接字符串提取)
|
val field = "\"$key\": \""
|
||||||
val sha256Field = "\"sha256\": \""
|
val start = manifestJson.indexOf(field) + field.length
|
||||||
val sha256Start = manifestJson.indexOf(sha256Field) + sha256Field.length
|
val end = manifestJson.indexOf("\"", start)
|
||||||
val sha256End = manifestJson.indexOf("\"", sha256Start)
|
return manifestJson.substring(start, end)
|
||||||
manifestSha256 = manifestJson.substring(sha256Start, sha256End)
|
}
|
||||||
|
manifestSha256 = extractJson("sha256")
|
||||||
val urlField = "\"url\": \""
|
manifestApkUrl = extractJson("url")
|
||||||
val urlStart = manifestJson.indexOf(urlField) + urlField.length
|
manifestTag = extractJson("tag")
|
||||||
val urlEnd = manifestJson.indexOf("\"", urlStart)
|
appendLog(" 渠道: $channel 版本: $manifestTag")
|
||||||
manifestApkUrl = manifestJson.substring(urlStart, urlEnd)
|
|
||||||
|
|
||||||
appendLog(" SHA256: ${manifestSha256.take(16)}...")
|
appendLog(" SHA256: ${manifestSha256.take(16)}...")
|
||||||
appendLog(" APK URL: ${manifestApkUrl.take(50)}...")
|
|
||||||
appendLog(" 需要更新 ✅")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
appendLog(" ⚠️ manifest 解析失败: ${e.message}")
|
appendLog(" ❌ 解析失败: ${e.message}")
|
||||||
appendLog("=== OTA 测试中止 ===")
|
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
appendLog("[3/4] 下载 APK: ${manifestApkUrl.take(40)}...")
|
// 3. 下载
|
||||||
val apkFile = File(cacheDir, "test-update.apk")
|
appendLog("[3/4] 下载 APK...")
|
||||||
|
val apkFile = File(cacheDir, "update-$channel.apk")
|
||||||
try {
|
try {
|
||||||
downloadFile(manifestApkUrl, apkFile)
|
downloadFile(manifestApkUrl, apkFile)
|
||||||
appendLog(" 下载完成: ${apkFile.length()} bytes ✅")
|
appendLog(" ✅ 下载完成: ${apkFile.length()} bytes")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
appendLog(" ❌ 下载失败: ${e.message}")
|
appendLog(" ❌ 下载失败: ${e.message}")
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. SHA256 校验
|
||||||
appendLog("[4/4] SHA256 校验...")
|
appendLog("[4/4] SHA256 校验...")
|
||||||
val actualSha256 = computeSha256(apkFile)
|
val actualSha256 = computeSha256(apkFile)
|
||||||
if (actualSha256 == manifestSha256) {
|
if (actualSha256 == manifestSha256) {
|
||||||
appendLog(" ✅ SHA256 校验通过!")
|
appendLog(" ✅ SHA256 校验通过!")
|
||||||
appendLog(" ${actualSha256.take(32)}...")
|
|
||||||
} else {
|
} else {
|
||||||
appendLog(" ❌ SHA256 校验失败!")
|
appendLog(" ❌ SHA256 校验失败!")
|
||||||
appendLog(" 期望: ${manifestSha256.take(32)}...")
|
appendLog(" expected: ${manifestSha256.take(32)}")
|
||||||
appendLog(" 实际: ${actualSha256.take(32)}...")
|
appendLog(" actual: ${actualSha256.take(32)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
appendLog("=== OTA 测试完成 ===")
|
appendLog("=== $channel 渠道测试完成 ===")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun httpGet(url: String): String {
|
private fun httpGet(url: String): String {
|
||||||
|
|||||||
1
packages/client-android/manifest-beta.json
Normal file
1
packages/client-android/manifest-beta.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":"0.1.0","builds":{"android":{"type":"apk","tag":"v0.1.0-dav-android-7-beta","url":"https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-7/v0.1.0-dav-android-debug.apk","sha256":"6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6","size":3223411,"channel":"beta"}}}
|
||||||
1
packages/client-android/manifest-nightly.json
Normal file
1
packages/client-android/manifest-nightly.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":"0.1.0","builds":{"android":{"type":"apk","tag":"v0.1.0-dav-android-7-nightly","url":"https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-7/v0.1.0-dav-android-debug.apk","sha256":"6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6","size":3223411,"channel":"nightly"}}}
|
||||||
@@ -3,10 +3,11 @@
|
|||||||
"builds": {
|
"builds": {
|
||||||
"android": {
|
"android": {
|
||||||
"type": "apk",
|
"type": "apk",
|
||||||
"tag": "v0.1.0-dav-android-6",
|
"tag": "v0.1.0-dav-android-7",
|
||||||
"url": "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-6/gca-v0.1.0.apk",
|
"url": "https://git.childish-ghost.com/LukeMackin/Yuzu-GCA/releases/download/v0.1.0-dav-android-7/v0.1.0-dav-android-debug.apk",
|
||||||
"sha256": "6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6",
|
"sha256": "6cd6bbfe07e8b86b06bc328b7b08138dfb00defc71c8bcc98fbb256b2153aba6",
|
||||||
"size": 3223411,
|
"size": 3223411,
|
||||||
|
"channel": "stable",
|
||||||
"minVersion": "0.1.0"
|
"minVersion": "0.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user