package com.sw.platecabinet.utils import android.app.ActivityManager import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build import android.os.Handler import android.os.Looper import android.os.Process import com.sw.platecabinet.activity.InitActivity import timber.log.Timber import java.io.File import java.io.FileOutputStream import java.io.IOException import java.io.PrintWriter import java.io.StringWriter import java.text.SimpleDateFormat import java.util.Date import java.util.Locale import kotlin.system.exitProcess /** * 崩溃处理 */ class CrashHandler private constructor(private val context: Context) : Thread.UncaughtExceptionHandler { companion object { private const val TAG = "CrashHandler" private const val CRASH_REPORTS_DIR = "crash_reports" private const val LOG_LINES = 500 // 收集最近500行日志 @Volatile private var instance: CrashHandler? = null fun init(context: Context) { if (instance == null) { synchronized(CrashHandler::class.java) { if (instance == null) { instance = CrashHandler(context.applicationContext) } } } } fun getCrashReportFiles(context: Context): Array { val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR) return if (crashDir.exists() && crashDir.isDirectory) { crashDir.listFiles { _, name -> name.endsWith(".log") } ?: emptyArray() } else { emptyArray() } } fun clearCrashReports(context: Context) { getCrashReportFiles(context).forEach { it.delete() } } } private val defaultHandler = Thread.getDefaultUncaughtExceptionHandler() init { Thread.setDefaultUncaughtExceptionHandler(this) } override fun uncaughtException(thread: Thread, ex: Throwable) { handleException(thread, ex) // 如果系统提供了默认的异常处理器,则交给系统去结束程序 // 否则自己结束程序 defaultHandler?.uncaughtException(thread, ex) ?: run { Process.killProcess(Process.myPid()) exitProcess(1) } } /** * 自动重启app */ private fun restartApp() { // 延迟1秒后重启应用 Handler(Looper.getMainLooper()).postDelayed({ val intent = Intent(context, InitActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } val pendingIntent = PendingIntent.getActivity( context, 0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE ) val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent) Process.killProcess(Process.myPid()) exitProcess(1) }, 1000) } private fun handleException(thread: Thread, ex: Throwable) { // 收集设备信息和异常信息 val crashInfo = collectCrashInfo(thread, ex) // 保存日志文件 saveCrashInfoToFile(crashInfo) // 这里可以添加其他处理逻辑,比如上传到服务器等 } private fun collectCrashInfo(thread: Thread, ex: Throwable): String { return buildString { // 收集设备信息 collectDeviceInfo(this) // 收集应用日志 try { append("\n\n").append(collectLogs()) } catch (e: OutOfMemoryError) { append("collectLogs发生OutOfMemoryError:${e.message}") } // 收集线程和异常信息 append("\n\n========== Thread & Exception Info ==========\n") append("Thread: ${thread.name}\n") append("Stack Trace:\n") val sw = StringWriter() val pw = PrintWriter(sw) ex.printStackTrace(pw) var cause: Throwable? = ex.cause while (cause != null) { cause.printStackTrace(pw) cause = cause.cause } pw.close() append(sw.toString()) } } private fun collectDeviceInfo(sb: StringBuilder) { sb.append("========== Device Info ==========\n") try { // 应用信息 val pm = context.packageManager val pi = pm.getPackageInfo(context.packageName, 0) sb.append("App Version: ${pi.versionName}_${pi.versionCode}\n") // Android 版本信息 sb.append("OS Version: ${Build.VERSION.RELEASE}_${Build.VERSION.SDK_INT}\n") // 设备信息 sb.append("Vendor: ${Build.MANUFACTURER}\n") sb.append("Model: ${Build.MODEL}\n") sb.append("CPU ABI: ${Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"}\n") // 其他信息 sb.append("Locale: ${Locale.getDefault()}\n") sb.append( "Current Time: ${ SimpleDateFormat( "yyyy-MM-dd HH:mm:ss", Locale.getDefault() ).format(Date()) }\n" ) // 内存信息 val memoryInfo = ActivityManager.MemoryInfo() val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager activityManager.getMemoryInfo(memoryInfo) sb.append("Available Memory: ${memoryInfo.availMem / (1024 * 1024)}MB\n") sb.append("Total Memory: ${memoryInfo.totalMem / (1024 * 1024)}MB\n") sb.append("Low Memory: ${memoryInfo.lowMemory}\n") } catch (e: Exception) { Timber.e(e, "Error while collecting device info") sb.append("Error while collecting device info: ${e.message}\n") } } private fun collectLogs(): String { return buildString { append("========== Application Logs ==========\n") try { val process = Runtime.getRuntime().exec("logcat -d -v threadtime") val reader = process.inputStream.bufferedReader() val logLines = reader.readLines() val start = kotlin.comparisons.maxOf(0, logLines.size - LOG_LINES) logLines.subList(start, logLines.size).forEach { append(it).append("\n") } } catch (e: IOException) { Timber.e(e, "Error collecting logs") append("Error collecting logs: ${e.message}\n") } } } private fun saveCrashInfoToFile(crashInfo: String) { try { val time = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault()).format(Date()) val fileName = "crash_$time.log" val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR) if (!crashDir.exists() && !crashDir.mkdirs()) { Timber.tag(TAG).e("Failed to create crash report directory") return } val crashFile = File(crashDir, fileName) FileOutputStream(crashFile).use { it.write(crashInfo.toByteArray()) } Timber.tag(TAG).d("Crash info saved to: ${crashFile.absolutePath}") } catch (e: Exception) { Timber.tag(TAG).e(e, "Error saving crash info to file") } } /** * 清理旧的崩溃日志 */ fun cleanupOldCrashReports(maxAgeDays: Int = 7) { val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR) if (!crashDir.exists() || !crashDir.isDirectory) return val now = System.currentTimeMillis() val maxAgeMillis = maxAgeDays * 24 * 60 * 60 * 1000L crashDir.listFiles()?.forEach { file -> if (file.lastModified() < now - maxAgeMillis) { file.delete() } } } }