From 9861c468f8ad6a1a8d12af662c6d5fc42b763b8c Mon Sep 17 00:00:00 2001 From: Super12138 <70494801+Super12138@users.noreply.github.com> Date: Tue, 14 Jan 2025 22:14:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BA=94=E7=94=A8=E5=B4=A9=E6=BA=83?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 3 + .../main/kotlin/cn/super12138/todo/TodoApp.kt | 5 + .../todo/ui/activities/CrashActivity.kt | 66 ++++++++++++++ .../todo/ui/pages/crash/CrashHandler.kt | 28 ++++++ .../todo/ui/pages/crash/CrashPage.kt | 91 +++++++++++++++++++ app/src/main/res/values-zh-rCN/strings.xml | 3 + app/src/main/res/values/strings.xml | 3 + 7 files changed, 199 insertions(+) create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashHandler.kt create mode 100644 app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 92ae6dd..b0ee2ae 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -24,6 +24,9 @@ + \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/TodoApp.kt b/app/src/main/kotlin/cn/super12138/todo/TodoApp.kt index 959823a..c13057f 100644 --- a/app/src/main/kotlin/cn/super12138/todo/TodoApp.kt +++ b/app/src/main/kotlin/cn/super12138/todo/TodoApp.kt @@ -2,6 +2,7 @@ package cn.super12138.todo import android.app.Application import cn.super12138.todo.logic.database.TodoDatabase +import cn.super12138.todo.ui.pages.crash.CrashHandler class TodoApp : Application() { private val database by lazy { TodoDatabase.getDatabase(this) } @@ -12,6 +13,10 @@ class TodoApp : Application() { override fun onCreate() { super.onCreate() + db = database + + val crashHandler = CrashHandler(applicationContext) + Thread.setDefaultUncaughtExceptionHandler(crashHandler) } } \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt new file mode 100644 index 0000000..dc439fc --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/activities/CrashActivity.kt @@ -0,0 +1,66 @@ +package cn.super12138.todo.ui.activities + +import android.os.Build +import android.os.Bundle +import android.view.HapticFeedbackConstants +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import cn.super12138.todo.R +import cn.super12138.todo.ui.pages.crash.CrashPage +import cn.super12138.todo.ui.theme.ToDoTheme +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale + +class CrashActivity : ComponentActivity() { + companion object { + const val BRAND_PREFIX = "Brand: " + const val MODEL_PREFIX = "Model: " + const val DEVICE_SDK_PREFIX = "Device SDK: " + const val CRASH_TIME_PREFIX = "Crash time: " + const val BEGINNING_CRASH = "======beginning of crash======" + } + + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + + val crashLogs = intent.getStringExtra("crash_logs") + + val deviceBrand = Build.BRAND + val deviceModel = Build.MODEL + val sdkLevel = Build.VERSION.SDK_INT + val currentDateTime = Calendar.getInstance().time + val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + val formattedDateTime = formatter.format(currentDateTime) + + val deviceInfo = StringBuilder().apply { + append(BRAND_PREFIX).append("").append(deviceBrand).append('\n') + append(MODEL_PREFIX).append(deviceModel).append('\n') + append(DEVICE_SDK_PREFIX).append(sdkLevel).append('\n').append('\n') + append(CRASH_TIME_PREFIX).append(formattedDateTime).append('\n').append('\n') + append(BEGINNING_CRASH).append('\n') + } + + val crashLogFormatted = StringBuilder().apply { + append(deviceInfo) + append(crashLogs) + }.toString() + + setContent { + ToDoTheme { + val view = LocalView.current + CrashPage( + crashLog = if (crashLogs == null) stringResource(R.string.tip_no_crash_logs) else crashLogFormatted, + exitApp = { + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP) + finishAffinity() + } + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashHandler.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashHandler.kt new file mode 100644 index 0000000..028d28f --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashHandler.kt @@ -0,0 +1,28 @@ +package cn.super12138.todo.ui.pages.crash +import android.content.Context +import android.content.Intent +import android.os.Process +import cn.super12138.todo.ui.activities.CrashActivity +import kotlin.system.exitProcess + +class CrashHandler(private val context: Context) : Thread.UncaughtExceptionHandler { + private val defaultUEH = Thread.getDefaultUncaughtExceptionHandler() + + override fun uncaughtException(thread: Thread, ex: Throwable) { + val stackTrace = ex.stackTraceToString() + + // 启动新的 Activity 来显示崩溃日志 + val intent = Intent(context, CrashActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + putExtra("crash_logs", stackTrace) + } + context.startActivity(intent) + + // 传递异常给默认的异常处理器 + defaultUEH?.uncaughtException(thread, ex) + + // 杀掉崩溃的应用程序进程 + Process.killProcess(Process.myPid()) + exitProcess(10) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt new file mode 100644 index 0000000..d0397bf --- /dev/null +++ b/app/src/main/kotlin/cn/super12138/todo/ui/pages/crash/CrashPage.kt @@ -0,0 +1,91 @@ +package cn.super12138.todo.ui.pages.crash + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeContent +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ExitToApp +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +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.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import cn.super12138.todo.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CrashPage( + crashLog: String, + exitApp: () -> Unit, + modifier: Modifier = Modifier +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + Scaffold( + modifier = modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + LargeTopAppBar( + title = { + Text( + text = stringResource(R.string.page_crash), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + scrollBehavior = scrollBehavior + ) + }, + floatingActionButton = { + ExtendedFloatingActionButton( + onClick = exitApp, + icon = { + Icon( + imageVector = Icons.AutoMirrored.Outlined.ExitToApp, + contentDescription = stringResource(R.string.action_exit_app) + ) + }, + text = { + Text( + text = stringResource(R.string.action_exit_app) + ) + }, + ) + }, + contentWindowInsets = WindowInsets.safeContent + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .padding(horizontal = 16.dp) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Spacer(Modifier.height(5.dp)) + SelectionContainer { + Text( + text = crashLog, + fontFamily = FontFamily.Monospace, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c28d6a8..c1b4bd2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -22,4 +22,7 @@ 其它 修改待办 剩余 %s 项任务 + 应用程序出现错误 + 没有日志传入 + 退出应用 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9dbf90f..ec41fd3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,4 +21,7 @@ Others Edit Task %s tasks remaining + Oops! App went wrong + No crash logs + Exit app \ No newline at end of file