开发者

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("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")

View File

@ -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() {

View File

@ -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

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() {
// 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 }