Android 项目 日志

Android 项目 日志

源码

FileLogger

package cn.com.xuxiaowei.utils

import android.content.Context
import android.util.Log
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

object FileLogger {
    private const val TAG = "FileLogger"

    // 使用 ThreadLocal 确保 SimpleDateFormat 的线程安全
    private val dateFormat =
        ThreadLocal.withInitial { SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()) }
    private val fileNameFormat =
        ThreadLocal.withInitial { SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) }

    // 使用专用的单线程调度器执行文件写入,以防止竞争条件,并避免阻塞主线程。
    private val logScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

    fun log(context: Context, filename: String, tag: String, message: String) {
        // 立即输出到 Logcat
        Log.d(tag, message)

        // 异步写入到文件
        logScope.launch { writeLogToFile(context, filename, tag, message) }
    }

    private fun writeLogToFile(context: Context, filename: String, tag: String, message: String) {
        try {
            // 获取外部存储的私有目录:
            // /storage/emulated/0/Android/data/cn.com.xuxiaowei/files/logs/
            val logDir = context.getExternalFilesDir("logs")
            if (logDir != null) {
                if (!logDir.exists()) {
                    logDir.mkdirs()
                }

                val currentDate = Date()
                val dateStr = fileNameFormat.get()?.format(currentDate) ?: ""
                val fileName = "${filename}_log_$dateStr.txt"
                val logFile = File(logDir, fileName)

                val timestamp = dateFormat.get()?.format(currentDate) ?: ""
                val logEntry = "$timestamp $tag: $message\n"

                // 以追加方式写入文件
                FileWriter(logFile, true).use { writer -> writer.append(logEntry) }
            }
        } catch (e: IOException) {
            Log.e(TAG, "Error writing log to file", e)
        }
    }
}

App

package cn.com.xuxiaowei

import android.app.Application
import android.os.Build
import cn.com.xuxiaowei.utils.FileLogger

class App : Application() {
    override fun onCreate() {
        super.onCreate()

        val appVersion = try {
            val pm = packageManager
            val pi = pm.getPackageInfo(packageName, 0)
            val versionName = pi.versionName ?: "unknown"
            val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                pi.longVersionCode.toString()
            } else {
                @Suppress("DEPRECATION")
                pi.versionCode.toString()
            }
            "$versionName($versionCode)"
        } catch (e: Exception) {
            "unknown"
        }

        val deviceInfo = "model=${Build.MODEL}, brand=${Build.BRAND}, sdk=${Build.VERSION.SDK_INT}"
        FileLogger.log(this, "startup", "App", "App started version=$appVersion, $deviceInfo")
    }
}

MainActivity

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        FileLogger.log(this, "startup", "MainActivity", "onCreate")
        enableEdgeToEdge()
        setContent {
            XuxiaoweiTheme {
                XuxiaoweiApp()
            }
        }
    }
}

AndroidManifest.xml

    <application
        android:name=".App">
    </application>

查看日志


Logcat 实时查看日志

  • 可以使用过滤条件,减少日志数量