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

This commit is contained in:
HapeLee committed 2026-04-29 20:01:19 +08:00
1 parent a191d80e02
commit bc670629b4
8 files changed
+209 -8

No files matched your search

+5
View File
@@ -222,6 +222,11 @@
android:name=".ui.about.AboutActivity" android:name=".ui.about.AboutActivity"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
android:launchMode="singleTask" /> android:launchMode="singleTask" />
<activity
android:name=".ui.about.CrashReportActivity"
android:enableOnBackInvokedCallback="true"
android:excludeFromRecents="true"
android:launchMode="singleTask" />
<!-- 书源管理 --> <!-- 书源管理 -->
<activity <activity
android:name=".ui.book.source.manage.BookSourceActivity" android:name=".ui.book.source.manage.BookSourceActivity"
@@ -1,11 +1,13 @@
package io.legado.app.help package io.legado.app.help
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Debug import android.os.Debug
import android.os.Looper import android.os.Looper
import android.os.Process
import android.webkit.WebSettings import android.webkit.WebSettings
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
@@ -28,6 +30,7 @@ import java.io.StringWriter
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.system.exitProcess
/** /**
* 异常管理类 * 异常管理类
@@ -53,8 +56,12 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
Looper.loop() Looper.loop()
} else { } else {
ReadAloud.stop(context) ReadAloud.stop(context)
handleException(ex) if (handleException(ex)) {
mDefaultHandler?.uncaughtException(thread, 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?) { private fun handleException(ex: Throwable?): Boolean {
if (ex == null) return if (ex == null) return false
LocalConfig.appCrash = true LocalConfig.appCrash = true
//保存日志文件 //保存日志文件
saveCrashInfo2File(ex) val crashFileName = saveCrashInfo2File(ex)
if ((ex is OutOfMemoryError || ex.cause is OutOfMemoryError) && AppConfig.recordHeapDump) { if ((ex is OutOfMemoryError || ex.cause is OutOfMemoryError) && AppConfig.recordHeapDump) {
doHeapDump() doHeapDump()
} }
context.longToastOnUiLegacy(ex.stackTraceStr) return if (startCrashReport(crashFileName)) {
Thread.sleep(3000) 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 { 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() val sb = StringBuilder()
for ((key, value) in paramsMap) { for ((key, value) in paramsMap) {
sb.append(key).append("=").append(value).append("\n") sb.append(key).append("=").append(value).append("\n")
@@ -165,6 +192,19 @@ class CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {
.writeText(crashLog) .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()
} }
/** /**
@@ -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()
}
}
@@ -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))
}
}
}
}
}
@@ -1209,6 +1209,10 @@
<string name="key_page_on_long_press">按键长按翻页</string> <string name="key_page_on_long_press">按键长按翻页</string>
<string name="save_log">保存日志</string> <string name="save_log">保存日志</string>
<string name="create_heap_dump">创建堆转储</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_s">当应用发生OOM崩溃时保存堆转储</string>
<string name="record_heap_dump_t">记录堆转储</string> <string name="record_heap_dump_t">记录堆转储</string>
<string name="font_weight_text">字重</string> <string name="font_weight_text">字重</string>
@@ -1178,6 +1178,10 @@
<string name="key_page_on_long_press">按键长按翻页</string> <string name="key_page_on_long_press">按键长按翻页</string>
<string name="save_log">保存日志</string> <string name="save_log">保存日志</string>
<string name="create_heap_dump">创建堆转储</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_s">当应用发生OOM崩溃时保存堆转储</string>
<string name="record_heap_dump_t">记录堆转储</string> <string name="record_heap_dump_t">记录堆转储</string>
<string name="font_weight_text">字重</string> <string name="font_weight_text">字重</string>
@@ -1181,6 +1181,10 @@
<string name="key_page_on_long_press">按键长按翻页</string> <string name="key_page_on_long_press">按键长按翻页</string>
<string name="save_log">保存日志</string> <string name="save_log">保存日志</string>
<string name="create_heap_dump">创建堆转储</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_s">当应用发生OOM崩溃时保存堆转储</string>
<string name="record_heap_dump_t">记录堆转储</string> <string name="record_heap_dump_t">记录堆转储</string>
<string name="font_weight_text">字重</string> <string name="font_weight_text">字重</string>
+4
View File
@@ -1212,6 +1212,10 @@
<string name="key_page_on_long_press">Press and Hold the Key to Turn Page</string> <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="save_log">Save logs</string>
<string name="create_heap_dump">Create Heap Dump</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_s">Saving a heap dump when the application crashes by OOM</string>
<string name="record_heap_dump_t">Capture heap dump</string> <string name="record_heap_dump_t">Capture heap dump</string>
<string name="font_weight_text">N/B/L</string> <string name="font_weight_text">N/B/L</string>