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 4d17296..671dcb0 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/App.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/App.kt @@ -5,8 +5,11 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.SharedPreferences +import android.os.Handler +import android.os.Looper import com.shuwei.intelligent.shelves.utils.BootReceiver import com.shuwei.intelligent.shelves.utils.CrashHandler +import com.shuwei.intelligent.shelves.utils.FileLogger /** * @author: star @@ -19,10 +22,22 @@ class App : Application() { super.onCreate() app = this CrashHandler.init(this) + // 启动时立即清理一次过期日志 + cleanupLogs() + // 设备长期不断电、进程不会重新启动,运行中每24小时定期清理一次 + logCleanupHandler.postDelayed(logCleanupRunnable, LOG_CLEANUP_INTERVAL) val filter = IntentFilter(Intent.ACTION_BOOT_COMPLETED) registerReceiver(BootReceiver(), filter) } + /** + * 清理过期的日志文件:文件日志与崩溃日志均保留最近7天 + */ + private fun cleanupLogs() { + FileLogger.cleanupExpiredLogs(this) + CrashHandler.getInstance()?.cleanupOldCrashReports() + } + companion object { private lateinit var app: App var canteenId = "0" @@ -36,6 +51,19 @@ class App : Application() { @Volatile private var sharedPref: SharedPreferences? = null + /** 日志定期清理间隔:24小时,与日志文件按天滚动对齐 */ + private const val LOG_CLEANUP_INTERVAL = 24L * 60 * 60 * 1000 + + private val logCleanupHandler = Handler(Looper.getMainLooper()) + + /** 日志定期清理任务:执行后重新调度自身,形成循环 */ + private val logCleanupRunnable = object : Runnable { + override fun run() { + getInstance().cleanupLogs() + logCleanupHandler.postDelayed(this, LOG_CLEANUP_INTERVAL) + } + } + fun getInstance(): App { return app } 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 index db07709..11953e7 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/utils/CrashHandler.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/CrashHandler.kt @@ -39,11 +39,19 @@ class CrashHandler private constructor(private val context: Context) : synchronized(CrashHandler::class.java) { if (instance == null) { instance = CrashHandler(context.applicationContext) + // 初始化时清理过期的崩溃日志(默认保留7天) + instance?.cleanupOldCrashReports() } } } } + /** + * 获取单例实例 + * @return 已通过 init() 初始化时返回实例,否则返回 null + */ + fun getInstance(): CrashHandler? = instance + //fun getCrashReportFiles(context: Context): Array { // val crashDir = getCrashDir() // return if (crashDir.exists() && crashDir.isDirectory) { @@ -222,16 +230,21 @@ class CrashHandler private constructor(private val context: Context) : * 清理旧的崩溃日志 */ fun cleanupOldCrashReports(maxAgeDays: Int = 7) { - val crashDir = getCrashDir() - if (!crashDir.exists() || !crashDir.isDirectory) return + // 该方法会在 Application 启动及定时任务中调用,异常必须内部消化,避免影响主流程 + try { + val crashDir = getCrashDir() + if (!crashDir.exists() || !crashDir.isDirectory) return - val now = System.currentTimeMillis() - val maxAgeMillis = maxAgeDays * 24 * 60 * 60 * 1000L + val now = System.currentTimeMillis() + val maxAgeMillis = maxAgeDays * 24 * 60 * 60 * 1000L - crashDir.listFiles()?.forEach { file -> - if (file.lastModified() < now - maxAgeMillis) { - file.delete() + crashDir.listFiles()?.forEach { file -> + if (file.lastModified() < now - maxAgeMillis) { + file.delete() + } } + } catch (e: Exception) { + Log.e(TAG, "清理崩溃日志失败", e) } } diff --git a/app/src/main/java/com/shuwei/intelligent/shelves/utils/FileLogger.kt b/app/src/main/java/com/shuwei/intelligent/shelves/utils/FileLogger.kt index 0047d5c..9afe78f 100644 --- a/app/src/main/java/com/shuwei/intelligent/shelves/utils/FileLogger.kt +++ b/app/src/main/java/com/shuwei/intelligent/shelves/utils/FileLogger.kt @@ -17,8 +17,37 @@ class FileLogger(private val context: Context) { private const val LOG_DIR = "logs" private const val FILE_PREFIX = "log_" private const val FILE_EXTENSION = ".txt" + + /** 日志保留天数,超过该天数的日志文件在应用启动时清理 */ + private const val RETENTION_DAYS = 7L + + /** 单个日志文件大小上限(100MB),超过后当天不再写入,防止异常刷屏撑爆存储 */ + private const val MAX_LOG_FILE_SIZE = 100L * 1024 * 1024 + private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) private val timeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + + /** + * 清理过期的日志文件,保留最近 retentionDays 天 + * 按文件最后修改时间判断,最近仍在写入的文件不会被误删 + * 建议在 Application 启动时调用一次 + * @param context 上下文,用于定位 filesDir + * @param retentionDays 日志保留天数 + */ + fun cleanupExpiredLogs(context: Context, retentionDays: Long = RETENTION_DAYS) { + try { + val cutoff = System.currentTimeMillis() - retentionDays * 24 * 60 * 60 * 1000L + context.filesDir.listFiles { _, name -> + name.startsWith(FILE_PREFIX) && name.endsWith(FILE_EXTENSION) + }?.forEach { file -> + if (file.lastModified() < cutoff) { + file.delete() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } } /** @@ -45,6 +74,7 @@ class FileLogger(private val context: Context) { /** * 写入文件 + * 单文件超过大小上限(100MB)时当天不再写入,防止异常刷屏撑爆存储 */ private fun writeToFile(logEntry: String) { // val path = File(context.filesDir, LOG_DIR) @@ -52,10 +82,10 @@ class FileLogger(private val context: Context) { // path.mkdirs() // } val fileName = getCurrentDateFileName() -// val file = File(path, fileName) -// if (file.exists().not()) { -// file.createNewFile() -// } + val file = File(context.filesDir, fileName) + if (file.exists() && file.length() > MAX_LOG_FILE_SIZE) { + return + } val output = context.openFileOutput(fileName, Context.MODE_APPEND) val writer = BufferedWriter(OutputStreamWriter(output))