Browse Source

新增启动时自动检查更新功能

a328929 3 months ago
parent
commit
9760be3dfd

+ 1 - 0
app/src/main/java/io/legado/app/constant/PreferKey.kt

@@ -178,6 +178,7 @@ object PreferKey {
     const val recordHeapDump = "recordHeapDump"
     const val optimizeRender = "optimizeRender"
     const val updateToVariant = "updateToVariant"
+    const val autoCheckUpdateOnStart = "autoCheckUpdateOnStart"
     const val streamReadAloudAudio = "streamReadAloudAudio"
     const val pauseReadAloudWhilePhoneCalls = "pauseReadAloudWhilePhoneCalls"
     const val readAloudByMediaButton = "readAloudByMediaButton"

+ 31 - 13
app/src/main/java/io/legado/app/help/update/AppReleaseInfo.kt

@@ -36,23 +36,41 @@ data class GithubRelease(
     @SerializedName("created_at")
     val createdAt: String?
 ) {
-    fun gitReleaseToAppReleaseInfo(): List<AppReleaseInfo> {
+    fun gitReleaseToAppReleaseInfo(
+        supportedAbis: List<String> = Build.SUPPORTED_ABIS.asList()
+    ): List<AppReleaseInfo> {
         assets ?: throw NoStackTraceException("获取新版本出错")
 
         val version = tagName
-        val abi = Build.SUPPORTED_ABIS.firstOrNull() ?: ""
-        val abiSuffix = when {
-            abi.contains("arm64") -> "arm64-v8a"
-            abi.contains("armeabi") -> "armeabi-v7a"
-            else -> ""
+        return selectCompatibleAssets(assets, supportedAbis)
+            .map { it.assetToAppReleaseInfo(isPreRelease, body, version) }
+    }
+}
+
+private val releaseAbiSuffixes = listOf(
+    "arm64-v8a",
+    "armeabi-v7a",
+    "x86_64",
+    "x86"
+)
+
+internal fun selectCompatibleAssets(
+    assets: List<Asset>,
+    supportedAbis: List<String>
+): List<Asset> {
+    val validAssets = assets.filter { it.isValid }
+
+    supportedAbis.forEach { supportedAbi ->
+        val matchingAssets = validAssets.filter { asset ->
+            asset.name.contains(supportedAbi, ignoreCase = true)
         }
+        if (matchingAssets.isNotEmpty()) return matchingAssets
+    }
 
-        return assets
-            .filter { it.isValid }
-            .filter { asset ->
-                abiSuffix.isEmpty() || asset.name.contains(abiSuffix, ignoreCase = true)
-            }
-            .map { it.assetToAppReleaseInfo(isPreRelease, body, version) }
+    return validAssets.filter { asset ->
+        releaseAbiSuffixes.none { abiSuffix ->
+            asset.name.contains(abiSuffix, ignoreCase = true)
+        }
     }
 }
 
@@ -89,4 +107,4 @@ data class Asset(
             versionName = version
         )
     }
-}
+}

+ 5 - 0
app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfig.kt

@@ -17,6 +17,11 @@ object OtherConfig {
         "official_version"
     )
 
+    var autoCheckUpdateOnStart by prefDelegate(
+        PreferKey.autoCheckUpdateOnStart,
+        false
+    )
+
     var webServiceAutoStart by prefDelegate(
         PreferKey.webServiceAutoStart,
         false

+ 7 - 0
app/src/main/java/io/legado/app/ui/config/otherConfig/OtherConfigScreen.kt

@@ -117,6 +117,13 @@ fun OtherConfigScreen(
                     onValueChange = { OtherConfig.updateToVariant = it }
                 )
 
+                SwitchSettingItem(
+                    title = stringResource(R.string.auto_check_update_on_start_title),
+                    description = stringResource(R.string.auto_check_update_on_start_summary),
+                    checked = OtherConfig.autoCheckUpdateOnStart,
+                    onCheckedChange = { OtherConfig.autoCheckUpdateOnStart = it }
+                )
+
                 SwitchSettingItem(
                     title = stringResource(R.string.web_service_auto_start),
                     checked = OtherConfig.webServiceAutoStart,

+ 14 - 0
app/src/main/java/io/legado/app/ui/main/MainActivity.kt

@@ -77,6 +77,7 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
         private const val KEY_RESTORE_READ_ALOUD = "restoreReadAloud"
         private const val KEY_RESTORE_READ_IN_BOOKSHELF = "restoreReadInBookshelf"
         private const val KEY_RESTORE_READ_CHAPTER_CHANGED = "restoreReadChapterChanged"
+        private val startupUpdateCheckGate = ProcessStartupUpdateCheckGate()
 
         @Volatile
         var hasActiveReadBookRoute: Boolean = false
@@ -165,6 +166,9 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
         restoredReadBookRoute = savedInstanceState?.restoreReadBookRoute()
         super.onCreate(savedInstanceState)
 
+        val shouldAutoCheckUpdate = startupUpdateCheckGate.consume(
+            OtherConfig.autoCheckUpdateOnStart
+        )
         if (checkStartupRoute()) return
 
         // 智能自启:如果上次是手动开启状态(web_service_auto 为 true),则自启
@@ -185,6 +189,9 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
                 viewModel.upAllBookToc()
             }
             viewModel.postLoad()
+            if (shouldAutoCheckUpdate) {
+                checkUpdateOnStart()
+            }
         }
     }
 
@@ -333,6 +340,13 @@ open class MainActivity : BaseComposeActivity(), VariableDialog.Callback {
         }
     }
 
+    private fun checkUpdateOnStart() {
+        AppUpdateGitHub.check(lifecycleScope)
+            .onSuccess { updateInfo ->
+                showDialogFragment(UpdateDialog(updateInfo))
+            }
+    }
+
     /**
      * 版本更新日志
      */

+ 10 - 0
app/src/main/java/io/legado/app/ui/main/ProcessStartupUpdateCheckGate.kt

@@ -0,0 +1,10 @@
+package io.legado.app.ui.main
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+internal class ProcessStartupUpdateCheckGate {
+
+    private val consumed = AtomicBoolean(false)
+
+    fun consume(enabled: Boolean): Boolean = !consumed.getAndSet(true) && enabled
+}

+ 2 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -1393,6 +1393,8 @@
     <string name="start_chapter">起始章</string>
     <string name="update_to_variant_title">检查更新查找版本</string>
     <string name="update_to_variant_summary">检查更新时查找其他签名版本</string>
+    <string name="auto_check_update_on_start_title">启动时自动检测更新</string>
+    <string name="auto_check_update_on_start_summary">仅在应用进程完全重新启动时检测,从后台返回不会触发</string>
     <string name="default_version">当前</string>
     <string name="official_version">正式版</string>
     <string name="beta_release_version">测试版</string>

+ 2 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -1221,6 +1221,8 @@
     <string name="start_chapter">開始篇章</string>
     <string name="update_to_variant_title">检查更新查找版本</string>
     <string name="update_to_variant_summary">检查更新时查找其他签名版本</string>
+    <string name="auto_check_update_on_start_title">啟動時自動檢查更新</string>
+    <string name="auto_check_update_on_start_summary">僅在應用程式程序完全重新啟動時檢查,從背景返回不會觸發</string>
     <string name="default_version">当前</string>
     <string name="official_version">正式版</string>
     <string name="beta_release_version">测试版</string>

+ 2 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -1225,6 +1225,8 @@
     <string name="start_chapter">開始篇章</string>
     <string name="update_to_variant_title">检查更新查找版本</string>
     <string name="update_to_variant_summary">检查更新时查找其他签名版本</string>
+    <string name="auto_check_update_on_start_title">啟動時自動檢查更新</string>
+    <string name="auto_check_update_on_start_summary">僅在應用程式程序完全重新啟動時檢查,從背景返回不會觸發</string>
     <string name="default_version">当前</string>
     <string name="official_version">正式版</string>
     <string name="beta_release_version">测试版</string>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -1429,6 +1429,8 @@
     <string name="start_chapter">Start Chapter</string>
     <string name="update_to_variant_title">Check Updates for Variants</string>
     <string name="update_to_variant_summary">Look for other signature variants when checking for updates</string>
+    <string name="auto_check_update_on_start_title">Check for updates on startup</string>
+    <string name="auto_check_update_on_start_summary">Only checks after the app process fully restarts; returning from the background will not trigger a check</string>
     <string name="default_version">Current</string>
     <string name="official_version">Official</string>
     <string name="beta_release_version">Beta</string>

+ 68 - 0
app/src/test/java/io/legado/app/help/update/AppReleaseInfoTest.kt

@@ -0,0 +1,68 @@
+package io.legado.app.help.update
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class AppReleaseInfoTest {
+
+    @Test
+    fun `falls back to next supported abi when preferred apk is missing`() {
+        val selected = selectCompatibleAssets(
+            assets = listOf(
+                asset("legado-1.0.0-armeabi-v7a.apk"),
+                asset("legado-1.0.0-x86.apk")
+            ),
+            supportedAbis = listOf("arm64-v8a", "armeabi-v7a")
+        )
+
+        assertEquals(listOf("legado-1.0.0-armeabi-v7a.apk"), selected.map { it.name })
+    }
+
+    @Test
+    fun `uses universal apk when no supported abi apk exists`() {
+        val selected = selectCompatibleAssets(
+            assets = listOf(
+                asset("legado-1.0.0.apk"),
+                asset("legado-1.0.0-x86.apk")
+            ),
+            supportedAbis = listOf("arm64-v8a", "armeabi-v7a")
+        )
+
+        assertEquals(listOf("legado-1.0.0.apk"), selected.map { it.name })
+    }
+
+    @Test
+    fun `does not select an incompatible abi when universal apk is missing`() {
+        val selected = selectCompatibleAssets(
+            assets = listOf(asset("legado-1.0.0-x86.apk")),
+            supportedAbis = listOf("arm64-v8a", "armeabi-v7a")
+        )
+
+        assertEquals(emptyList<Asset>(), selected)
+    }
+
+    @Test
+    fun `prefers the highest priority supported abi`() {
+        val selected = selectCompatibleAssets(
+            assets = listOf(
+                asset("legado-1.0.0.apk"),
+                asset("legado-1.0.0-armeabi-v7a.apk"),
+                asset("legado-1.0.0-arm64-v8a.apk")
+            ),
+            supportedAbis = listOf("arm64-v8a", "armeabi-v7a")
+        )
+
+        assertEquals(listOf("legado-1.0.0-arm64-v8a.apk"), selected.map { it.name })
+    }
+
+    private fun asset(name: String) = Asset(
+        apkUrl = "https://example.com/$name",
+        contentType = "application/vnd.android.package-archive",
+        createdAt = "2026-07-13T00:00:00Z",
+        downloadCount = 0,
+        id = name.hashCode(),
+        name = name,
+        state = "uploaded",
+        url = "https://api.example.com/$name"
+    )
+}

+ 24 - 0
app/src/test/java/io/legado/app/ui/main/ProcessStartupUpdateCheckGateTest.kt

@@ -0,0 +1,24 @@
+package io.legado.app.ui.main
+
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class ProcessStartupUpdateCheckGateTest {
+
+    @Test
+    fun `enabled check runs only once per process`() {
+        val gate = ProcessStartupUpdateCheckGate()
+
+        assertTrue(gate.consume(enabled = true))
+        assertFalse(gate.consume(enabled = true))
+    }
+
+    @Test
+    fun `disabled first startup still consumes the process check`() {
+        val gate = ProcessStartupUpdateCheckGate()
+
+        assertFalse(gate.consume(enabled = false))
+        assertFalse(gate.consume(enabled = true))
+    }
+}