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>
查看日志