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

This commit is contained in:
HapeLee
2026-04-29 20:01:19 +08:00
parent a191d80e02
commit bc670629b4
8 changed files with 209 additions and 8 deletions
@@ -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()
}
/**
@@ -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))
}
}
}
}
}