[优化] 新增崩溃包报告页面(因为崩溃太多了孩子们
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user