开发者
This commit is contained in:
parent
3302225e74
commit
76b6a7eae2
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
Loading…
Reference in New Issue