From 76b6a7eae27256bc061ef54f50a1b0160ffc347a Mon Sep 17 00:00:00 2001 From: fengge <844143714@qq.com> Date: Thu, 23 Apr 2026 11:35:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=80=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../com/example/kingway/ptt/pttNative.java | 62 +++++++++++--- .../java/com/stand/standapp/MyApplication.kt | 20 ++++- .../com/stand/standapp/utils/LogManager.kt | 84 +++++++++++-------- 4 files changed, 121 insertions(+), 46 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index aad0f7c..bc08e9b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation("com.jakewharton.timber:timber:5.0.1") implementation("androidx.cardview:cardview:1.0.0") + implementation("com.iqiyi.xcrash:xcrash-android-lib:3.1.0") implementation("io.github.justson:agentweb-core:v5.1.1-androidx") implementation("com.github.Justson:Downloader:v5.0.4-androidx") implementation("com.google.android.material:material:1.12.0") diff --git a/app/src/main/java/com/example/kingway/ptt/pttNative.java b/app/src/main/java/com/example/kingway/ptt/pttNative.java index 2ed693e..7bebf49 100644 --- a/app/src/main/java/com/example/kingway/ptt/pttNative.java +++ b/app/src/main/java/com/example/kingway/ptt/pttNative.java @@ -117,53 +117,93 @@ public class pttNative { public void pttNativeSendAtCmd(String strAtCmd) { - Log.i(TAG,"strAtCmd " + strAtCmd); - OEM_AT_Send(strAtCmd); + try { + Log.i(TAG,"strAtCmd " + strAtCmd); + OEM_AT_Send(strAtCmd); + } catch (Throwable e) { + Log.e(TAG, "Native AT Send failed: " + e.getMessage()); + } } public void pttNativePocTaskStart() { - OEM_Poc_Task_Start(); + try { + OEM_Poc_Task_Start(); + } catch (Throwable e) { + Log.e(TAG, "Native PocTaskStart failed"); + } } public int pttNativeSetAtCallback() { - return OEM_Set_AT_CallBack("OEM_AT_cb"); + try { + return OEM_Set_AT_CallBack("OEM_AT_cb"); + } catch (Throwable e) { + return -1; + } } public int pttNativeSetPlayCallback() { - return OEM_Set_Play_CallBack("OEM_Play_cb"); + try { + return OEM_Set_Play_CallBack("OEM_Play_cb"); + } catch (Throwable e) { + return -1; + } } public int pttNativeSetPalyStartCallback() { - return OEM_Set_Play_Start_Callback("OEM_Play_Start_cb"); + try { + return OEM_Set_Play_Start_Callback("OEM_Play_Start_cb"); + } catch (Throwable e) { + return -1; + } } public int pttNativeSetPlayStopCallback() { - return OEM_Set_Play_Stop_Callback("OEM_Play_Stop_cb"); + try { + return OEM_Set_Play_Stop_Callback("OEM_Play_Stop_cb"); + } catch (Throwable e) { + return -1; + } } public int pttNativeSetRecordStartCallback() { - return OEM_Set_Record_Start_Callback("OEM_Record_Start_cb"); + try { + return OEM_Set_Record_Start_Callback("OEM_Record_Start_cb"); + } catch (Throwable e) { + return -1; + } } public int pttNativeSetRecordStopCallback() { - return OEM_Set_Record_Stop_Callback("OEM_Record_Stop_cb"); + try { + return OEM_Set_Record_Stop_Callback("OEM_Record_Stop_cb"); + } catch (Throwable e) { + return -1; + } } public int pttNativeSetTTSCallback() { - return OEM_Set_TTS_Callback("OEM_Play_TTS_cb"); + try { + return OEM_Set_TTS_Callback("OEM_Play_TTS_cb"); + } catch (Throwable e) { + return -1; + } } public int pttNativeSetPackageName(String name) { - return OEM_Set_Package_Name(name); + try { + return OEM_Set_Package_Name(name); + } catch (Throwable e) { + return -1; + } } public pttNative() { diff --git a/app/src/main/java/com/stand/standapp/MyApplication.kt b/app/src/main/java/com/stand/standapp/MyApplication.kt index e2a6e34..990fe41 100644 --- a/app/src/main/java/com/stand/standapp/MyApplication.kt +++ b/app/src/main/java/com/stand/standapp/MyApplication.kt @@ -13,9 +13,27 @@ class MyApplication : Application() { } override fun onCreate() { - // 第一步:先初始化日志,哪怕后面的代码崩了,这里的初始化也完成了 + // 第一步:初始化日志 LogManager.init(this) + // 【核心】初始化 xCrash 以捕获 Native (SO) 崩溃 + xcrash.XCrash.init(this, xcrash.XCrash.InitParameters().apply { + setAppVersion(packageManager.getPackageInfo(packageName, 0).versionName) + setLogDir(getExternalFilesDir("tombstones")?.absolutePath) + setNativeDumpAllThreads(true) + + // Java 崩溃回调 + setJavaCallback { logPath, emergency -> + LogManager.onCrashDetected(logPath, "Java-xCrash", null) + } + + // Native 崩溃回调 + setNativeCallback { logPath, emergency -> + LogManager.onCrashDetected(logPath, "Native-xCrash", null) + android.util.Log.e("MyApplication", "NATIVE CRASH DETECTED at: $logPath") + } + }) + super.onCreate() instance = this 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 190d017..0dece91 100644 --- a/app/src/main/java/com/stand/standapp/utils/LogManager.kt +++ b/app/src/main/java/com/stand/standapp/utils/LogManager.kt @@ -61,55 +61,58 @@ object LogManager { } } + /** + * 专门供外部(如 xCrash 或其它处理器)调用的保存方法 + */ + fun onCrashDetected(logPath: String?, threadName: String?, throwable: Throwable?) { + val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date()) + val crashInfo = StringBuilder().apply { + append("\n\n=== [DETECTION] CRASH LOG ===\n") + append("Time: $timestamp\n") + append("Thread: ${threadName ?: "Unknown"}\n") + logPath?.let { append("XCrash Report: $it\n") } + throwable?.let { + append("Exception: ${it.javaClass.simpleName}\n") + append("Message: ${it.message}\n") + append("Stacktrace:\n${it.stackTraceToString()}\n") + } + append("=== END OF DETECTION ===\n\n") + }.toString() + + logDir?.let { dir -> + try { + val logFile = File(dir, SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + ".log") + FileOutputStream(logFile, true).use { + it.write(crashInfo.toByteArray()) + it.flush() + it.fd.sync() + } + } catch (e: Exception) {} + } + } + private fun setupCrashHandler() { - // 1. 捕获所有普通线程、子线程的未捕获异常 + // 1. 捕获所有线程 val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> - saveCrashToFile(thread, throwable) + onCrashDetected(null, thread.name, throwable) defaultHandler?.uncaughtException(thread, throwable) ?: exitProcess(1) } - // 2. 捕获主线程 (UI 线程) 的所有异常 - // 这种方式能捕获点击事件、生命周期回调中发生的所有崩溃 + // 2. 捕获主线程 Looper android.os.Handler(android.os.Looper.getMainLooper()).post { while (true) { try { android.os.Looper.loop() } catch (e: Throwable) { - saveCrashToFile(android.os.Looper.getMainLooper().thread, e) - // 如果你希望应用在崩溃后不闪退而是尝试恢复,可以不抛出,但建议这里还是交还给系统处理比较稳妥 - // 这里我们为了记录完日志后让系统正常走关闭流程 + onCrashDetected(null, "MainLooper", e) + // 如果是 UnsatisfiedLinkError,这种错误无法恢复,必须抛出让系统处理 throw e } } } } - private fun saveCrashToFile(thread: Thread, throwable: Throwable) { - // 使用同步写入,确保磁盘 IO 优先 - val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date()) - val crashInfo = "\n\nCRASH_ERROR_REPORT\n" + - "Time: $timestamp\n" + - "Thread: ${thread.name}\n" + - "Cause: ${throwable.cause}\n" + - "Message: ${throwable.message}\n" + - "Full Stacktrace:\n${throwable.stackTraceToString()}\n" + - "-------------------\n\n" - - logDir?.let { dir -> - try { - val logFile = File(dir, SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + ".log") - val fos = FileOutputStream(logFile, true) - fos.write(crashInfo.toByteArray()) - fos.flush() - fos.fd.sync() - fos.close() - } catch (e: Exception) { - android.util.Log.e("LogManager", "Failed to save crash log", e) - } - } - } - private fun cleanOldLogs(dir: File) { executor.execute { try { @@ -126,15 +129,28 @@ object LogManager { fun zipLogs(context: Context): String? { val dir = logDir ?: return null val zipFile = File(context.getExternalFilesDir(null), "logs_${System.currentTimeMillis()}.zip") + val tombstoneDir = File(context.getExternalFilesDir(null), "tombstones") + return try { ZipOutputStream(FileOutputStream(zipFile)).use { zos -> + // 1. 打包普通日志 dir.listFiles()?.forEach { file -> - if (file.isFile && file.name.endsWith(".log")) { - zos.putNextEntry(ZipEntry(file.name)) + if (file.isFile && (file.name.endsWith(".log") || file.name.endsWith(".txt"))) { + zos.putNextEntry(ZipEntry("logs/${file.name}")) FileInputStream(file).use { it.copyTo(zos) } zos.closeEntry() } } + // 2. 打包 Native 崩溃文件 (Tombstones) + if (tombstoneDir.exists()) { + tombstoneDir.listFiles()?.forEach { file -> + if (file.isFile) { + zos.putNextEntry(ZipEntry("native/${file.name}")) + FileInputStream(file).use { it.copyTo(zos) } + zos.closeEntry() + } + } + } } zipFile.absolutePath } catch (e: Exception) { null }