diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/App.kt b/app/src/main/java/com/shuwei/intelligent/shelves/App.kt index 5c95634..4d17296 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/App.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/App.kt @@ -6,6 +6,7 @@ import android.content.Intent import android.content.IntentFilter import android.content.SharedPreferences import com.shuwei.intelligent.shelves.utils.BootReceiver +import com.shuwei.intelligent.shelves.utils.CrashHandler /** * @author: star @@ -17,6 +18,7 @@ class App : Application() { override fun onCreate() { super.onCreate() app = this + CrashHandler.init(this) val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED) registerReceiver(BootReceiver(), filter) } diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt b/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt index a453d57..fff4e2c 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/base/BaseActivity.kt @@ -114,11 +114,18 @@ open class BaseActivity : AppCompatActivity() { override fun onUserInteraction() { super.onUserInteraction() + //开灯 + if(System.currentTimeMillis() - lastTouchTime >= TIME_OUT) { + this.switchLight(true) + } // 重置定时器 mHandler.removeCallbacks(mTimeoutRunnable) mHandler.postDelayed(mTimeoutRunnable, TIME_OUT) + lastTouchTime = System.currentTimeMillis() } + private var lastTouchTime = 0L + override fun onResume() { super.onResume() // 启动定时器 diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/net/HttpManager.kt b/app/src/main/java/com/shuwei/intelligent/shelves/net/HttpManager.kt index d883cf6..525d8d9 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/net/HttpManager.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/net/HttpManager.kt @@ -53,12 +53,11 @@ class HttpManager private constructor() { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() // 打印请求日志 - Log.d("OkHttp", "--> ${request.method},${request.url}") + Log.d("HttpManager", "--> ${request.method},${request.url},${request.body}") val response = chain.proceed(request) // 打印响应日志 - Log.d("OkHttp", "<-- ${response.code},${response.request.url}") - + Log.d("HttpManager", "<-- ${response.body}") return response } } diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/net/NetViewModel.kt b/app/src/main/java/com/shuwei/intelligent/shelves/net/NetViewModel.kt index 00743c0..98e7c5f 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/net/NetViewModel.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/net/NetViewModel.kt @@ -2,6 +2,7 @@ package com.shuwei.intelligent.shelves.net import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.shuwei.intelligent.shelves.App import com.shuwei.intelligent.shelves.model.ShelfBody import com.shuwei.intelligent.shelves.net.HttpManager.Companion.instance import kotlinx.coroutines.flow.MutableStateFlow @@ -129,6 +130,9 @@ class NetViewModel : ViewModel() { viewModelScope.launch { _getGoodsListUiState.value = UiState.Loading runCatching { + if (App.configUrl.isNullOrBlank()) { + App.configUrl = UrlConfig.BASE_URL + } val response = apiService.getGoodsList( canteenId = canteenId, goodsName = goodsName, diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/CrashHandler.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/CrashHandler.kt new file mode 100644 index 0000000..06174fd --- /dev/null +++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/CrashHandler.kt @@ -0,0 +1,251 @@ +package com.shuwei.intelligent.shelves.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 android.util.Log +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 = getCrashDir() + // 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) + + try { + Thread.sleep(3000) + } catch (e: InterruptedException) { + e.printStackTrace() + } + // 如果系统提供了默认的异常处理器,则交给系统去结束程序 + // 否则自己结束程序 + defaultHandler?.uncaughtException(thread, ex) ?: run { + Process.killProcess(Process.myPid()) + exitProcess(1) + } + } + + /** + * 自动重启app + */ +// private fun restartApp() { +// // 延迟1秒后重启应用 +// Handler(Looper.getMainLooper()).postDelayed({ +// val intent = Intent(context, MainActivity::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) { + Log.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 = maxOf(0, logLines.size - LOG_LINES) + + logLines.subList(start, logLines.size).forEach { + append(it).append("\n") + } + } catch (e: IOException) { + Log.e(TAG, "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 = getCrashDir() + + val crashFile = File(crashDir, fileName) + FileOutputStream(crashFile).use { it.write(crashInfo.toByteArray()) } + + Log.d(TAG,"Crash info saved to: ${crashFile.absolutePath}") + } catch (e: Exception) { + Log.e(TAG, "Error saving crash info to file") + } + } + + /** + * 清理旧的崩溃日志 + */ + fun cleanupOldCrashReports(maxAgeDays: Int = 7) { + val crashDir = getCrashDir() + 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() + } + } + } + + private fun getCrashDir():File { + //val crashDir = File(context.getExternalFilesDir(null), CRASH_REPORTS_DIR) + var crashDir = File(context.filesDir, CRASH_REPORTS_DIR) + if (!crashDir.exists()) { + crashDir.mkdirs() + } + + if (!(crashDir.exists())) { + crashDir = File(context.cacheDir, CRASH_REPORTS_DIR) + } + return crashDir + } + +} \ No newline at end of file