From 2c10731b8d891cba89e6f64af9be571e34cb37f7 Mon Sep 17 00:00:00 2001 From: zxj <275873859@qq.com> Date: Fri, 8 Aug 2025 10:50:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E8=AF=86=E5=88=AB?= =?UTF-8?q?=E8=BF=BD=E8=B8=AA=E4=BF=A1=E6=81=AF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/sw/platecabinet/MyApp.kt | 4 + .../activity/LoginByFaceActivity.kt | 28 ++- .../com/sw/platecabinet/utils/CrashHandler.kt | 237 ++++++++++++++++++ .../main/res/layout/activity_login_face.xml | 12 +- 4 files changed, 267 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/sw/platecabinet/utils/CrashHandler.kt diff --git a/app/src/main/java/com/sw/platecabinet/MyApp.kt b/app/src/main/java/com/sw/platecabinet/MyApp.kt index b792609..5419329 100644 --- a/app/src/main/java/com/sw/platecabinet/MyApp.kt +++ b/app/src/main/java/com/sw/platecabinet/MyApp.kt @@ -2,6 +2,7 @@ package com.sw.platecabinet import com.sw.plate.App import com.sw.plate.utils.AppUtil +import com.sw.platecabinet.utils.CrashHandler import timber.log.Timber class MyApp : App() { @@ -14,6 +15,9 @@ class MyApp : App() { Timber.plant(Timber.DebugTree()) Timber.d("初始化") initGlobalData() + + // 初始化崩溃处理器 + CrashHandler.init(this) } /** diff --git a/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt b/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt index 4db2c53..d122c46 100644 --- a/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt +++ b/app/src/main/java/com/sw/platecabinet/activity/LoginByFaceActivity.kt @@ -248,25 +248,37 @@ class LoginByFaceActivity : BaseActivity(), displayOrientation: Int, isMirror: Boolean ) { + Timber.d("initRgbCamera Rgb onCameraOpened: cameraId = $cameraId, displayOrientation = $displayOrientation") runOnUiThread({ val previewSizeRgb = camera.getParameters().getPreviewSize() val layoutParams = adjustPreviewViewSize( binding.dualCameraTexturePreviewRgb, binding.dualCameraTexturePreviewRgb, binding.dualCameraFaceRectView, - previewSizeRgb, displayOrientation, 1f + previewSizeRgb, displayOrientation, 0.8f ) Timber.d("initRgbCamera previewSizeRgb = ${previewSizeRgb.width}-${previewSizeRgb.height}") Timber.d("initRgbCamera layoutParams = ${layoutParams.width}-${layoutParams.height}") + Timber.d( + "initRgbCamera isMirror = ${isMirror}, isDrawRgbRectHorizontalMirror = ${ + ConfigUtil.isDrawRgbRectHorizontalMirror( + context + ) + }, isDrawRgbRectVerticalMirror = ${ + ConfigUtil.isDrawRgbRectVerticalMirror( + context + ) + }" + ) rgbFaceRectTransformer = FaceRectTransformer( previewSizeRgb.width, previewSizeRgb.height, layoutParams.width, layoutParams.height, - displayOrientation, + 90, cameraId, isMirror, - ConfigUtil.isDrawRgbRectHorizontalMirror(context), - ConfigUtil.isDrawRgbRectVerticalMirror(context) + true, + true ) recognizeViewModel.onRgbCameraOpened(camera) @@ -289,20 +301,20 @@ class LoginByFaceActivity : BaseActivity(), } override fun onCameraClosed() { - Timber.i("onCameraClosed: ") + Timber.i("initRgbCamera onCameraClosed: ") } override fun onCameraError(e: java.lang.Exception) { - Timber.i("onCameraError: %s", e.message) + Timber.i("initRgbCamera onCameraError: %s", e.message) e.printStackTrace() } override fun onCameraConfigurationChanged(cameraID: Int, displayOrientation: Int) { - Timber.i("onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") + Timber.i("initRgbCamera onCameraConfigurationChanged: threadName = ${Thread.currentThread().name}") if (rgbFaceRectTransformer != null) { rgbFaceRectTransformer!!.cameraDisplayOrientation = displayOrientation } - Timber.i("onCameraConfigurationChanged: $cameraID $displayOrientation") + Timber.i("initRgbCamera onCameraConfigurationChanged: $cameraID $displayOrientation") } } diff --git a/app/src/main/java/com/sw/platecabinet/utils/CrashHandler.kt b/app/src/main/java/com/sw/platecabinet/utils/CrashHandler.kt new file mode 100644 index 0000000..436bd07 --- /dev/null +++ b/app/src/main/java/com/sw/platecabinet/utils/CrashHandler.kt @@ -0,0 +1,237 @@ +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) + + // 收集应用日志 + append("\n\n").append(collectLogs()) + + // 收集线程和异常信息 + 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[0]}\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() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login_face.xml b/app/src/main/res/layout/activity_login_face.xml index 6e3de7b..d82642e 100644 --- a/app/src/main/res/layout/activity_login_face.xml +++ b/app/src/main/res/layout/activity_login_face.xml @@ -21,12 +21,12 @@ + android:layout_height="match_parent" + android:visibility="visible" />