From 0e5883d0de0daaf22ac7ecca7f7b2504c8496c56 Mon Sep 17 00:00:00 2001 From: fengge <844143714@qq.com> Date: Sun, 19 Apr 2026 14:02:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B1=8F=E8=94=BDptt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/stand/standapp/MyApplication.kt | 25 ++++--- .../com/stand/standapp/utils/LogManager.kt | 67 ++++++++++++++----- 2 files changed, 61 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/stand/standapp/MyApplication.kt b/app/src/main/java/com/stand/standapp/MyApplication.kt index d841617..e2a6e34 100644 --- a/app/src/main/java/com/stand/standapp/MyApplication.kt +++ b/app/src/main/java/com/stand/standapp/MyApplication.kt @@ -13,21 +13,20 @@ class MyApplication : Application() { } override fun onCreate() { + // 第一步:先初始化日志,哪怕后面的代码崩了,这里的初始化也完成了 + LogManager.init(this) + super.onCreate() instance = this - // Initialize Timber and File Logging - LogManager.init(this) - - try { - // Initialize PTT Module - PTTMyApplication.init(this) - Timber.tag("MyApplication").i("PTT Module init sequence finished") - } catch (e: Throwable) { - // Catch UnsatisfiedLinkError or other PTT init failures - Timber.tag("MyApplication").e(e, "PTT Module failed to start (Expected on non-ARM simulators)") - } - - Timber.tag("MyApplication").i("Application onCreate - Logging system ready") + Timber.tag("MyApplication").i("=== Application Starting ===") + +// try { +// // 第二步:异步或保护性初始化 PTT +// PTTMyApplication.init(this) +// Timber.tag("MyApplication").i("PTT Module init sequence finished") +// } catch (e: Throwable) { +// Timber.tag("MyApplication").e(e, "PTT Module CRASH during init") +// } } } diff --git a/app/src/main/java/com/stand/standapp/utils/LogManager.kt b/app/src/main/java/com/stand/standapp/utils/LogManager.kt index 70643ec..6b7fd0e 100644 --- a/app/src/main/java/com/stand/standapp/utils/LogManager.kt +++ b/app/src/main/java/com/stand/standapp/utils/LogManager.kt @@ -6,28 +6,65 @@ import okhttp3.* import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody.Companion.asRequestBody import timber.log.Timber -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.io.FileWriter -import java.io.IOException +import java.io.* import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.Executors import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream +import kotlin.system.exitProcess object LogManager { private val executor = Executors.newSingleThreadExecutor() private const val MAX_DAYS = 15 private val client = OkHttpClient() + private var logDir: File? = null fun init(context: Context) { Timber.plant(Timber.DebugTree()) - val logDir = File(context.getExternalFilesDir(null), "logs") - if (!logDir.exists()) logDir.mkdirs() - cleanOldLogs(logDir) - Timber.plant(FileLoggingTree(logDir)) + + try { + logDir = File(context.getExternalFilesDir(null), "logs") + if (logDir?.exists() == false) logDir?.mkdirs() + + // 立即清理旧日志 + logDir?.let { cleanOldLogs(it) } + + // 挂载文件日志树 + logDir?.let { Timber.plant(FileLoggingTree(it)) } + + // 【核心增强】接管全局异常,确保崩溃信息能写进文件 + setupCrashHandler() + + Timber.tag("LogManager").i("Logging system initialized and CrashHandler attached") + } catch (e: Exception) { + android.util.Log.e("LogManager", "Init failed", e) + } + } + + private fun setupCrashHandler() { + val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + // 强制同步写入崩溃日志 + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date()) + val crashInfo = "\n\n=== FATAL CRASH ===\n" + + "Time: $timestamp\n" + + "Thread: ${thread.name}\n" + + "Stacktrace:\n${throwable.stackTraceToString()}\n" + + "====================\n\n" + + logDir?.let { dir -> + val logFile = File(dir, SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + ".log") + try { + FileOutputStream(logFile, true).use { it.write(crashInfo.toByteArray()) } + } catch (e: Exception) { + android.util.Log.e("LogManager", "Failed to write crash log", e) + } + } + + // 交还给系统处理(弹出“应用已停止”对话框) + defaultHandler?.uncaughtException(thread, throwable) ?: exitProcess(1) + } } private fun cleanOldLogs(logDir: File) { @@ -46,12 +83,12 @@ object LogManager { } fun zipLogs(context: Context): String? { - val logDir = File(context.getExternalFilesDir(null), "logs") - if (!logDir.exists() || logDir.listFiles().isNullOrEmpty()) return null + val dir = logDir ?: return null + if (!dir.exists() || dir.listFiles().isNullOrEmpty()) return null val zipFile = File(context.getExternalFilesDir(null), "logs_${System.currentTimeMillis()}.zip") return try { ZipOutputStream(FileOutputStream(zipFile)).use { zos -> - logDir.listFiles()?.forEach { file -> + dir.listFiles()?.forEach { file -> if (file.isFile && file.name.endsWith(".log")) { zos.putNextEntry(ZipEntry(file.name)) FileInputStream(file).use { it.copyTo(zos) } @@ -66,9 +103,6 @@ object LogManager { } } - /** - * 上传日志到服务器 - */ fun uploadLogs(context: Context, callback: (Boolean, String) -> Unit) { executor.execute { val zipPath = zipLogs(context) @@ -80,8 +114,6 @@ object LogManager { val file = File(zipPath) val serverUrl = AppConfig.getServerUrl(context) val uploadUrl = "$serverUrl/api/attachment/upload" - - // 使用固定的 Token (根据用户提供) val bearerToken = "eyJraWQiOiJzLXYxIiwidHlwIjoiSldUIiwiYWxnIjoiSFMyNTYifQ.eyJzdWIiOiJhZG1pbiIsInR5cCI6ImFjY2VzcyIsImV4cCI6MTc3NjQ5MDczNSwianRpIjoiMjYzNjljYzQtYzJiMy00YTg0LWFiYTMtZDU4NmNiZDRmMmM0IiwiZmdwIjoiY2FhNzYzOTI4YWRhYWZiYjgzMjE3NjExYmVjYTc3ZjMiLCJraWQiOiJzLXYxIn0.AyuzNAe-R_GOBC65STH5qpHkmmUngX8rMBPI4CdIIhk" val requestBody = MultipartBody.Builder() @@ -99,7 +131,6 @@ object LogManager { client.newCall(request).execute().use { response -> if (response.isSuccessful) { callback(true, "上传成功") - // 上传成功后删除临时 zip 文件 file.delete() } else { callback(false, "服务器返回错误: ${response.code}")