开发者

This commit is contained in:
fengge 2026-04-23 11:35:36 +08:00
parent 3302225e74
commit 76b6a7eae2
4 changed files with 121 additions and 46 deletions

View File

@ -48,6 +48,7 @@ dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
implementation("com.jakewharton.timber:timber:5.0.1") implementation("com.jakewharton.timber:timber:5.0.1")
implementation("androidx.cardview:cardview:1.0.0") 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("io.github.justson:agentweb-core:v5.1.1-androidx")
implementation("com.github.Justson:Downloader:v5.0.4-androidx") implementation("com.github.Justson:Downloader:v5.0.4-androidx")
implementation("com.google.android.material:material:1.12.0") implementation("com.google.android.material:material:1.12.0")

View File

@ -117,53 +117,93 @@ public class pttNative {
public void pttNativeSendAtCmd(String strAtCmd) public void pttNativeSendAtCmd(String strAtCmd)
{ {
Log.i(TAG,"strAtCmd " + strAtCmd); try {
OEM_AT_Send(strAtCmd); Log.i(TAG,"strAtCmd " + strAtCmd);
OEM_AT_Send(strAtCmd);
} catch (Throwable e) {
Log.e(TAG, "Native AT Send failed: " + e.getMessage());
}
} }
public void pttNativePocTaskStart() public void pttNativePocTaskStart()
{ {
OEM_Poc_Task_Start(); try {
OEM_Poc_Task_Start();
} catch (Throwable e) {
Log.e(TAG, "Native PocTaskStart failed");
}
} }
public int pttNativeSetAtCallback() 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() 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() 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() 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() 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() 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() 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) 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() { public pttNative() {

View File

@ -13,9 +13,27 @@ class MyApplication : Application() {
} }
override fun onCreate() { override fun onCreate() {
// 第一步:初始化日志,哪怕后面的代码崩了,这里的初始化也完成了 // 第一步:初始化日志
LogManager.init(this) 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() super.onCreate()
instance = this instance = this

View File

@ -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() { private fun setupCrashHandler() {
// 1. 捕获所有普通线程、子线程的未捕获异常 // 1. 捕获所有线程
val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
saveCrashToFile(thread, throwable) onCrashDetected(null, thread.name, throwable)
defaultHandler?.uncaughtException(thread, throwable) ?: exitProcess(1) defaultHandler?.uncaughtException(thread, throwable) ?: exitProcess(1)
} }
// 2. 捕获主线程 (UI 线程) 的所有异常 // 2. 捕获主线程 Looper
// 这种方式能捕获点击事件、生命周期回调中发生的所有崩溃
android.os.Handler(android.os.Looper.getMainLooper()).post { android.os.Handler(android.os.Looper.getMainLooper()).post {
while (true) { while (true) {
try { try {
android.os.Looper.loop() android.os.Looper.loop()
} catch (e: Throwable) { } catch (e: Throwable) {
saveCrashToFile(android.os.Looper.getMainLooper().thread, e) onCrashDetected(null, "MainLooper", e)
// 如果你希望应用在崩溃后不闪退而是尝试恢复,可以不抛出,但建议这里还是交还给系统处理比较稳妥 // 如果是 UnsatisfiedLinkError这种错误无法恢复必须抛出让系统处理
// 这里我们为了记录完日志后让系统正常走关闭流程
throw e 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) { private fun cleanOldLogs(dir: File) {
executor.execute { executor.execute {
try { try {
@ -126,15 +129,28 @@ object LogManager {
fun zipLogs(context: Context): String? { fun zipLogs(context: Context): String? {
val dir = logDir ?: return null val dir = logDir ?: return null
val zipFile = File(context.getExternalFilesDir(null), "logs_${System.currentTimeMillis()}.zip") val zipFile = File(context.getExternalFilesDir(null), "logs_${System.currentTimeMillis()}.zip")
val tombstoneDir = File(context.getExternalFilesDir(null), "tombstones")
return try { return try {
ZipOutputStream(FileOutputStream(zipFile)).use { zos -> ZipOutputStream(FileOutputStream(zipFile)).use { zos ->
// 1. 打包普通日志
dir.listFiles()?.forEach { file -> dir.listFiles()?.forEach { file ->
if (file.isFile && file.name.endsWith(".log")) { if (file.isFile && (file.name.endsWith(".log") || file.name.endsWith(".txt"))) {
zos.putNextEntry(ZipEntry(file.name)) zos.putNextEntry(ZipEntry("logs/${file.name}"))
FileInputStream(file).use { it.copyTo(zos) } FileInputStream(file).use { it.copyTo(zos) }
zos.closeEntry() 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 zipFile.absolutePath
} catch (e: Exception) { null } } catch (e: Exception) { null }