Просмотр исходного кода

[优化] 新增崩溃包报告页面(因为崩溃太多了孩子们

HapeLee 6 месяцев назад
Родитель
Сommit
bc670629b4

+ 5 - 0
app/src/main/AndroidManifest.xml

@@ -222,6 +222,11 @@
             android:name=".ui.about.AboutActivity"
             android:enableOnBackInvokedCallback="true"
             android:launchMode="singleTask" />
+        <activity
+            android:name=".ui.about.CrashReportActivity"
+            android:enableOnBackInvokedCallback="true"
+            android:excludeFromRecents="true"
+            android:launchMode="singleTask" />
         <!-- 书源管理 -->
         <activity
             android:name=".ui.book.source.manage.BookSourceActivity"

+ 48 - 8
app/src/main/java/io/legado/app/help/CrashHandler.kt

@@ -1,11 +1,13 @@
 package io.legado.app.help
 
 import android.annotation.SuppressLint
+import android.content.Intent
 import android.content.Context
 import android.net.Uri
 import android.os.Build
 import android.os.Debug
 import android.os.Looper
+import android.os.Process
 import android.webkit.WebSettings
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppLog
@@ -28,6 +30,7 @@ import java.io.StringWriter
 import java.text.SimpleDateFormat
 import java.util.Date
 import java.util.concurrent.TimeUnit
+import kotlin.system.exitProcess
 
 /**
  * 异常管理类
@@ -53,8 +56,12 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
             Looper.loop()
         } else {
             ReadAloud.stop(context)
-            handleException(ex)
-            mDefaultHandler?.uncaughtException(thread, ex)
+            if (handleException(ex)) {
+                Process.killProcess(Process.myPid())
+                exitProcess(10)
+            } else {
+                mDefaultHandler?.uncaughtException(thread, ex)
+            }
         }
     }
 
@@ -73,19 +80,39 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
     /**
      * 处理该异常
      */
-    private fun handleException(ex: Throwable?) {
-        if (ex == null) return
+    private fun handleException(ex: Throwable?): Boolean {
+        if (ex == null) return false
         LocalConfig.appCrash = true
         //保存日志文件
-        saveCrashInfo2File(ex)
+        val crashFileName = saveCrashInfo2File(ex)
         if ((ex is OutOfMemoryError || ex.cause is OutOfMemoryError) && AppConfig.recordHeapDump) {
             doHeapDump()
         }
-        context.longToastOnUiLegacy(ex.stackTraceStr)
-        Thread.sleep(3000)
+        return if (startCrashReport(crashFileName)) {
+            LocalConfig.appCrash = false
+            true
+        } else {
+            context.longToastOnUiLegacy(ex.stackTraceStr)
+            Thread.sleep(3000)
+            false
+        }
+    }
+
+    private fun startCrashReport(fileName: String): Boolean {
+        return runCatching {
+            val intent = Intent()
+                .setClassName(context.packageName, CRASH_REPORT_ACTIVITY)
+                .putExtra(EXTRA_CRASH_FILE_NAME, fileName)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+            context.startActivity(intent)
+        }.isSuccess
     }
 
     companion object {
+        const val EXTRA_CRASH_FILE_NAME = "crashFileName"
+
+        private const val CRASH_REPORT_ACTIVITY = "io.legado.app.ui.about.CrashReportActivity"
+
         /**
          * 存储异常和参数信息
          */
@@ -123,7 +150,7 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
         /**
          * 保存错误信息到文件中
          */
-        fun saveCrashInfo2File(ex: Throwable) {
+        fun saveCrashInfo2File(ex: Throwable): String {
             val sb = StringBuilder()
             for ((key, value) in paramsMap) {
                 sb.append(key).append("=").append(value).append("\n")
@@ -165,6 +192,19 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
                         .writeText(crashLog)
                 }
             }
+            return fileName
+        }
+
+        fun readCrashLog(fileName: String?): String? {
+            return runCatching {
+                val crashDir = appCtx.externalCacheDir?.resolve("crash") ?: return null
+                val file = fileName
+                    ?.let { crashDir.resolve(it) }
+                    ?.takeIf { it.isFile }
+                    ?: crashDir.listFiles { file -> file.isFile }
+                        ?.maxByOrNull { it.name }
+                file?.readText()
+            }.getOrNull()
         }
 
         /**

+ 35 - 0
app/src/main/java/io/legado/app/ui/about/CrashReportActivity.kt

@@ -0,0 +1,35 @@
+package io.legado.app.ui.about
+
+import android.content.Intent
+import androidx.compose.runtime.Composable
+import io.legado.app.base.BaseComposeActivity
+import io.legado.app.help.CrashHandler
+import io.legado.app.ui.main.MainActivity
+import io.legado.app.utils.sendToClip
+
+class CrashReportActivity : BaseComposeActivity() {
+
+    private val crashText: String by lazy {
+        CrashHandler.readCrashLog(
+            intent.getStringExtra(CrashHandler.EXTRA_CRASH_FILE_NAME)
+        ).orEmpty()
+    }
+
+    @Composable
+    override fun Content() {
+        CrashReportScreen(
+            errorText = crashText,
+            onCopy = { sendToClip(crashText) },
+            onRestart = { restartApp() },
+            onClose = { finish() }
+        )
+    }
+
+    private fun restartApp() {
+        startActivity(
+            Intent(this, MainActivity::class.java)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+        )
+        finish()
+    }
+}

+ 105 - 0
app/src/main/java/io/legado/app/ui/about/CrashReportScreen.kt

@@ -0,0 +1,105 @@
+package io.legado.app.ui.about
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.text.selection.SelectionContainer
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.dp
+import io.legado.app.R
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.AppScaffold
+import io.legado.app.ui.widget.components.icon.AppIcons
+import io.legado.app.ui.widget.components.text.AppText
+import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
+import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
+@Composable
+fun CrashReportScreen(
+    errorText: String,
+    onCopy: () -> Unit,
+    onRestart: () -> Unit,
+    onClose: () -> Unit
+) {
+    val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
+    val displayText = errorText.ifBlank {
+        stringResource(R.string.crash_report_empty)
+    }
+
+    AppScaffold(
+        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+        topBar = {
+            GlassMediumFlexibleTopAppBar(
+                title = stringResource(R.string.crash_report),
+                navigationIcon = {
+                    TopBarNavigationButton(
+                        onClick = onClose,
+                        imageVector = AppIcons.Close
+                    )
+                },
+                scrollBehavior = scrollBehavior
+            )
+        }
+    ) { padding ->
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .padding(padding)
+                .padding(horizontal = 16.dp, vertical = 12.dp),
+            verticalArrangement = Arrangement.spacedBy(12.dp)
+        ) {
+            AppText(
+                text = stringResource(R.string.crash_report_message),
+                style = LegadoTheme.typography.bodyMedium
+            )
+
+            SelectionContainer(
+                modifier = Modifier
+                    .weight(1f)
+                    .fillMaxWidth()
+                    .verticalScroll(rememberScrollState())
+            ) {
+                Text(
+                    text = displayText,
+                    style = LegadoTheme.typography.bodySmall,
+                    fontFamily = FontFamily.Monospace
+                )
+            }
+
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.spacedBy(12.dp)
+            ) {
+                OutlinedButton(
+                    onClick = onRestart,
+                    modifier = Modifier.weight(1f)
+                ) {
+                    Text(text = stringResource(R.string.restart_app))
+                }
+                Button(
+                    onClick = onCopy,
+                    enabled = errorText.isNotBlank(),
+                    modifier = Modifier.weight(1f)
+                ) {
+                    Text(text = stringResource(R.string.copy_text))
+                }
+            }
+        }
+    }
+}

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

@@ -1209,6 +1209,10 @@
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="create_heap_dump">创建堆转储</string>
+    <string name="crash_report">崩溃报告</string>
+    <string name="crash_report_message">应用发生崩溃。报告问题时,请复制以下错误信息。</string>
+    <string name="crash_report_empty">未找到崩溃信息。</string>
+    <string name="restart_app">重启应用</string>
     <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
     <string name="record_heap_dump_t">记录堆转储</string>
     <string name="font_weight_text">字重</string>

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

@@ -1178,6 +1178,10 @@
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="create_heap_dump">创建堆转储</string>
+    <string name="crash_report">崩潰報告</string>
+    <string name="crash_report_message">應用程式發生崩潰。回報問題時,請複製以下錯誤資訊。</string>
+    <string name="crash_report_empty">找不到崩潰資訊。</string>
+    <string name="restart_app">重新啟動應用程式</string>
     <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
     <string name="record_heap_dump_t">记录堆转储</string>
     <string name="font_weight_text">字重</string>

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

@@ -1181,6 +1181,10 @@
     <string name="key_page_on_long_press">按键长按翻页</string>
     <string name="save_log">保存日志</string>
     <string name="create_heap_dump">创建堆转储</string>
+    <string name="crash_report">崩潰報告</string>
+    <string name="crash_report_message">應用程式發生崩潰。回報問題時,請複製以下錯誤資訊。</string>
+    <string name="crash_report_empty">找不到崩潰資訊。</string>
+    <string name="restart_app">重新啟動應用程式</string>
     <string name="record_heap_dump_s">当应用发生OOM崩溃时保存堆转储</string>
     <string name="record_heap_dump_t">记录堆转储</string>
     <string name="font_weight_text">字重</string>

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

@@ -1212,6 +1212,10 @@
     <string name="key_page_on_long_press">Press and Hold the Key to Turn Page</string>
     <string name="save_log">Save logs</string>
     <string name="create_heap_dump">Create Heap Dump</string>
+    <string name="crash_report">Crash report</string>
+    <string name="crash_report_message">The app crashed. Copy the error information below when reporting the issue.</string>
+    <string name="crash_report_empty">No crash information found.</string>
+    <string name="restart_app">Restart app</string>
     <string name="record_heap_dump_s">Saving a heap dump when the application crashes by OOM</string>
     <string name="record_heap_dump_t">Capture heap dump</string>
     <string name="font_weight_text">N/B/L</string>