Android日志框架-xlog的使用


本篇用于记录日志框架的使用,原地址:https://juejin.cn/post/7470521949626613771

一、为什么我们需要专门的日志框架?

1.1 原生Log的缺点

  • 😱 性能杀手:同步写入日志导致主线程卡顿(线上环境每秒数百条日志时尤为明显)
  • 📉 日志丢失:系统日志缓冲区溢出时直接丢弃旧日志(关键崩溃现场说没就没)
  • 🔒 安全隐患:测试代码忘记删除,敏感数据(用户ID、地址)裸奔在Logcat
  • 🌪 可读性灾难:不同模块的日志混杂,定位问题像大海捞针
  • 🚫 无法动态控制:线上环境无法按需调整日志级别,被迫重新打包发布
  • 📁 文件管理缺失:无法自动分片、压缩、加密存储日志文件
  • 📶 网络同步困难:关键日志无法实时上报到服务端

1.2 理想日志框架的六大特征

  1. 异步写入:绝不阻塞主线程
  2. 分级控制:动态调整日志级别
  3. 安全保障:自动脱敏+加密存储
  4. 智能管理:日志分片/压缩/自动清理
  5. 崩溃现场保留:确保关键日志不丢失
  6. 可扩展性:支持自定义输出和网络上报

二、主流日志框架横向评测

2.1 候选名单

框架核心优势潜在短板
Logger漂亮的格式化输出功能较基础
Timber灵活的树形结构扩展需要自行实现文件存储
XLog(微信Mars框架的一部分)微信出品,全链路解决方案接入成本略高
com.elvishew:xlog轻量级、灵活配置、兼容性好社区活跃度较低

三、为什么选择xlog?

  1. 性能:com.elvishew:xlog是一个轻量级的日志库,对应用性能的影响较小。它提供了灵活的日志配置和多种输出目标,可以满足大多数应用的日志记录需求。

  2. 易用性:该方案易于集成和使用,提供了丰富的API和配置选项,方便开发者根据需求进行定制。

  3. 功能需求:支持多种日志级别、格式化输出、日志文件管理等功能,可以满足一般应用的日志记录和管理需求。

  4. 日志管理便捷性:日志文件可以灵活配置保存路径和自动备份策略,方便开发者进行日志管理和分析。

    另外,如何选择合适的框架,需要根据项目情况去判定,并非一成不变的。即使手写管理也是可以的。

四、使用案例

  • github网址:xLog/README_ZH.md at master · elvishew/xLog · GitHub
  • 为了保持项目的组件化,这里创建新的模块,专门进行log的管理。
  • 日志模块,加密存储日志文件到本地。
  • 解密工具:https://github.com/leavesCZY/compose-multiplatform-xlog-decode
  • 日志文件存储位置 /sdcard/Android/data/包名/files/logs/

4.1 引入依赖

xlog = "1.11.1"
xlogLibcat = "1.0.0"
xlog-libcat = { module = "com.elvishew:xlog-libcat", version.ref = "xlogLibcat" }
xlog = { module = "com.elvishew:xlog", version.ref = "xlog" }
dependencies {
    implementation(libs.xlog)
    implementation(libs.xlog.libcat
}

4.2 配置xlog

  • 用于生成日志文件名
/** 
 * FramesFileNameGenerator 类实现了 FileNameGenerator 接口,用于生成日志文件的文件名。
 * 它根据应用程序的版本名称、日志级别和时间戳来创建唯一的文件名。
 *
 * @param appVersionName 应用程序的版本名称,用于包含在生成的文件名中。
 */
class FramesFileNameGenerator(private val appVersionName: String) : FileNameGenerator {

    /**
     * 指示文件名是否可以更改。
     *
     * @return 始终返回 true,表示文件名是可更改的。
     */
    override fun isFileNameChangeable(): Boolean {
        return true
    }

    /**
     * 根据日志级别和时间戳生成日志文件的文件名。
     * 文件名格式为:.kLog-日期-v版本号.log,例如:.kLog-2023-10-04-v1.0.log
     *
     * @param logLevel 日志级别,未在此方法中使用,但可能在将来用于更细化的日志文件名生成。
     * @param timestamp 时间戳,用于生成文件名中的日期部分。
     * @return 生成的日志文件名。
     */
    override fun generateFileName(logLevel: Int, timestamp: Long): String {
        // 使用默认区域设置创建 SimpleDateFormat 实例,用于格式化日期。
        val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
        // 拼接文件名并返回。
        return ".kLog" + "-" + sdf.format(Date(timestamp)).plus("-v")
            .plus(appVersionName) + ".log"
    }
}
  • 用于日志信息格式化
/**
 * FramesFlattener 类实现了 Flattener2 接口,用于将日志信息格式化为扁平化的字符串格式。
 * 该类负责将日志事件信息转换为可读的字符串格式。
 */
class FramesFlattener : Flattener2 {

    /**
     * 将日志信息格式化为扁平化的字符串格式。
     *
     * @param timeMillis 日志事件的时间戳,自 Unix 纪元以来的毫秒数。
     * @param logLevel 日志事件的日志级别。
     * @param tag 日志事件的标签,可能为 null。
     * @param message 日志事件的消息内容,可能为 null。
     * @return 返回格式化后的日志信息字符串,包含日期、日志级别、标签和消息,各部分以 '|' 分隔。
     */
    override fun flatten(
        timeMillis: Long,
        logLevel: Int,
        tag: String?,
        message: String?
    ): CharSequence {
        // 拼接当前日期、日志级别名称、标签和消息,使用 '|' 作为分隔符
        return (getCurrDDate()
                + '|' + LogLevel.getLevelName(logLevel)
                + '|' + tag
                + '|' + message)
    }

    /**
     * 获取当前日期和时间的字符串表示。
     *
     * @return 返回当前日期和时间的字符串,格式为 "yyyy-MM-dd HH:mm:ss.SSS"。
     */
    private fun getCurrDDate(): String {
        // 使用 SimpleDateFormat 格式化当前日期和时间
        return SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date())
    }
}
  • 用于捕捉崩溃异常等
/**
 * 自定义崩溃处理类,用于捕获未处理的异常和崩溃信息
 */
class FramesCrashHandler private constructor() : UncaughtExceptionHandler {

    private val tag = "FramesCrashHandler"
    private val infoMap = mutableMapOf<String, String?>()
    private var mDefaultHandler: UncaughtExceptionHandler? = null
    private var mContext: Context? = null
    private val sDateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault())

    companion object {
        // 单例实例
        val instance: FramesCrashHandler by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            FramesCrashHandler()
        }
    }

    /**
     * 初始化崩溃处理器
     * @param context 应用上下文
     */
    fun init(context: Context?) {
        mContext = context
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    /**
     * 处理未捕获的异常
     * @param thread 发生异常的线程
     * @param ex 异常信息
     */
    override fun uncaughtException(thread: Thread, ex: Throwable) {
        if (!handleException(ex) && mDefaultHandler != null) {
            mDefaultHandler!!.uncaughtException(thread, ex)
        } else {
            KLog.e("uncaughtException", ex)
            SystemClock.sleep(3000L)
            Process.killProcess(Process.myPid())
            exitProcess(1)
        }

    }

    /**
     * 自定义异常处理逻辑
     * @param ex 异常信息
     * @return 是否处理了该异常
     */
    private fun handleException(ex: Throwable?): Boolean {
        return if (ex == null) {
            false
        } else {
            try {
                object : Thread() {
                    override fun run() {
                        Looper.prepare()
//                        Toast.makeText(mContext, "很抱歉,程序出现异常", Toast.LENGTH_LONG).show()
                        Looper.loop()
                    }
                }.start()
                KLog.e(tag, "crash!!!", ex)
                collectDeviceInfo(mContext)
                saveCrashInfoFile(ex)
                SystemClock.sleep(3000L)
            } catch (var3: Exception) {
                var3.printStackTrace()
            }
            true
        }
    }

    /**
     * 收集设备信息
     * @param ctx 应用上下文
     */
    private fun collectDeviceInfo(ctx: Context?) {
        try {
            val pm = ctx!!.packageManager
            val pi = pm.getPackageInfo(ctx.packageName, PackageManager.GET_ACTIVITIES)
            if (pi != null) {
                val versionName = pi.versionName + ""
                val versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    pi.longVersionCode.toString() + ""
                } else {
                    pi.versionCode.toString()
                }
                infoMap["versionName"] = versionName
                infoMap["versionCode"] = versionCode
            }
        } catch (var9: PackageManager.NameNotFoundException) {
            KLog.e(tag, "an error occured when collect package info", var9)
        }
        val fields = Build::class.java.declaredFields
        val var12 = fields.size
        for (var13 in 0 until var12) {
            val field = fields[var13]
            try {
                field.isAccessible = true
                infoMap[field.name] = field?.get(null).toString()
            } catch (var8: Exception) {
                KLog.e(tag, "an error occur when collect crash info", var8)
            }
        }
    }


    /**
     * 保存崩溃信息到文件
     * @param ex 异常信息
     * @return 文件名
     */
    @Throws(Exception::class)
    private fun saveCrashInfoFile(ex: Throwable): String? {
        val sb = StringBuffer()
        return try {
            val date = sDateFormat.format(Date())
            sb.append("\r\n" + date + "\n")
            val var5: Iterator<*> = infoMap.entries.iterator()
            var result: String
            while (var5.hasNext()) {
                val (key1, value) = var5.next() as Map.Entry<*, *>
                val key = key1 as String
                result = value as String
                sb.append("$key=$result\n")
            }
            val writer: Writer = StringWriter()
            val printWriter = PrintWriter(writer)
            ex.printStackTrace(printWriter)
            var cause = ex.cause
            while (cause != null) {
                cause.printStackTrace(printWriter)
                cause = cause.cause
            }
            printWriter.flush()
            printWriter.close()
            result = writer.toString()
            sb.append(result)
            writeFile(sb.toString())
        } catch (var10: Exception) {
            KLog.e(tag, "an error occured while writing file...", var10)
            sb.append("an error occured while writing file...\r\n")
            writeFile(sb.toString())
            null
        }
    }

    /**
     * 将崩溃信息写入文件
     * @param sb 崩溃信息
     * @return 文件名
     */
    @Throws(Exception::class)
    private fun writeFile(sb: String): String {
        val time = sDateFormat.format(Date())
        val fileName = "crash-$time.log"
        val path = getPrivateErrDir()
        val fos = FileOutputStream(path + fileName, true)
        fos.write(sb.toByteArray())
        fos.flush()
        fos.close()
        return fileName
    }

    /**
     * 获取崩溃日志保存目录
     * @return 目录路径
     */
    private fun getPrivateErrDir(): String? {
        val str = KLog.LOG_DIR
        val filePath: String =
            if ("mounted" != Environment.getExternalStorageState() && Environment.isExternalStorageRemovable()) {
                mContext!!.filesDir.path + str + "/"
            } else {
                mContext!!.getExternalFilesDir(null as String?)!!.path + str + "/"
            }
        val file = File(filePath)
        return if (!file.exists() && !file.mkdirs()) null else filePath
    }

}

4.3 xlog简单封装

  • xlog工具类简单封装管理
/**
 * 日志工具类
 * @see https://github.com/elvishew/xLog/blob/master/README_ZH.md
 */
object KLog {

    private const val isEncryptLog = false

    private const val DEFAULT_TAG = "K_LOG"
    const val LOG_DIR = "/kLogs/"
    private const val ZIP_FILE = "androidLogs.zip"

    private lateinit var fullLogDir: String
    private lateinit var fullLogZip: String

    fun init(context: Context) {
        val fileDir = context.getExternalFilesDir(null)?.path + ""
        fullLogDir = fileDir + LOG_DIR
        fullLogZip = fileDir + "/${System.currentTimeMillis()}-" + ZIP_FILE
        val config = LogConfiguration.Builder()
            .logLevel(LogLevel.ALL)
            .tag(DEFAULT_TAG).build()
        val filePrinter = FilePrinter.Builder(fullLogDir)
            .fileNameGenerator(FramesFileNameGenerator(AppUtils.getAppVersionName()))
            //缓存10天日志
            .cleanStrategy(FileLastModifiedCleanStrategy(10L * 24L * 60L * 60L * 1000L))
            .flattener(FramesFlattener())
            .build()
        if (true) {
            XLog.init(config, filePrinter, AndroidPrinter(true, 1500))
        } else {
            //正式环境不打印console.log
            XLog.init(config, filePrinter)
        }

    }

    /**
     * Log an object with level [LogLevel.VERBOSE].
     *
     * @param object the object to log
     * @see LogConfiguration.Builder.addObjectFormatter
     * @since 1.1.0
     */
    fun v(`object`: Any?) {
        if (isEncryptLog) {
            XLog.v(DEFAULT_TAG, `object`)
        } else {
            XLog.v(`object`)
        }
    }

    /**
     * Log an array with level [LogLevel.VERBOSE].
     *
     * @param array the array to log
     */
    fun v(array: Array<Any?>?) {
        if (isEncryptLog) {
            XLog.v(DEFAULT_TAG, array)
        } else {
            XLog.v(array)
        }
    }

    /**
     * Log a message with level [LogLevel.VERBOSE].
     *
     * @param format the format of the message to log
     * @param args   the arguments of the message to log
     */
    fun v(format: String?, vararg args: Any?) {
        if (isEncryptLog) {
            XLog.v(DEFAULT_TAG, args)
        } else {
            XLog.v(format, args)
        }

    }

    /**
     * Log a message with level [LogLevel.VERBOSE].
     *
     * @param msg the message to log
     */
    fun v(msg: String?) {
        if (isEncryptLog) {
            XLog.v(DEFAULT_TAG, msg)
        } else {
            XLog.v(msg)
        }
    }

    /**
     * Log a message and a throwable with level [LogLevel.VERBOSE].
     *
     * @param msg the message to log
     * @param tr  the throwable to be log
     */
    fun v(msg: String?, tr: Throwable?) {
        if (isEncryptLog) {
            XLog.v(DEFAULT_TAG, tr)
        } else {
            XLog.v(msg, tr)
        }
    }

    /**
     * Log an object with level [LogLevel.DEBUG].
     *
     * @param object the object to log
     * @see LogConfiguration.Builder.addObjectFormatter
     * @since 1.1.0
     */
    fun d(`object`: Any?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, `object`)
        } else {
            XLog.d(`object`)
        }
    }

    /**
     * Log an array with level [LogLevel.DEBUG].
     *
     * @param array the array to log
     */
    fun d(array: Array<Any?>?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, array)
        } else {
            XLog.d(array)
        }
    }

    /**
     * Log a message with level [LogLevel.DEBUG].
     *
     * @param msg the message to log
     */
    @JvmStatic
    fun d(msg: String?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, msg)
        } else {
            XLog.d(msg)
        }
    }

    /**
     * Log a message and a throwable with level [LogLevel.DEBUG].
     *
     * @param msg the message to log
     * @param tr  the throwable to be log
     */
    fun d(msg: String?, tr: Throwable?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, tr)
        } else {
            XLog.d(msg, tr)
        }
    }

    /**
     * Log a message with level [LogLevel.DEBUG].
     *
     * @param tag custom tag
     * @param msg the message to log
     */
    fun d(tag: String, msg: String?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, msg)
        } else {
            XLog.tag(tag).d(msg)
        }
    }

    /**
     * Log an object with level [LogLevel.INFO].
     *
     * @param object the object to log
     * @see LogConfiguration.Builder.addObjectFormatter
     * @since 1.1.0
     */
    fun i(`object`: Any?) {
        if (isEncryptLog) {
            XLog.i(DEFAULT_TAG, `object`)
        } else {
            XLog.i(`object`)
        }
    }

    /**
     * Log an array with level [LogLevel.INFO].
     *
     * @param array the array to log
     */
    fun i(array: Array<Any?>?) {
        if (isEncryptLog) {
            XLog.i(DEFAULT_TAG, array)
        } else {
            XLog.i(array)
        }
    }

    /**
     * Log a message with level [LogLevel.INFO].
     *
     * @param format the format of the message to log
     * @param args   the arguments of the message to log
     */
    @JvmStatic
    fun i(format: String?, vararg args: Any?) {
        if (isEncryptLog) {
            XLog.i(DEFAULT_TAG, args)
        } else {
            XLog.i(format, args)
        }
    }

    /**
     * Log a message with level [LogLevel.INFO].
     *
     * @param msg the message to log
     */
    fun i(msg: String?) {
        if (isEncryptLog) {
            XLog.i(DEFAULT_TAG, msg)
        } else {
            XLog.i(msg)
        }
    }

    /**
     * Log a message with level [LogLevel.INFO].
     *
     * @param tag custom tag
     * @param msg the message to log
     */
    fun i(tag: String, msg: String?) {
        if (isEncryptLog) {
            XLog.i(DEFAULT_TAG, msg)
        } else {
            XLog.tag(tag).i(msg)
        }
    }

    /**
     * Log a message and a throwable with level [LogLevel.INFO].
     *
     * @param msg the message to log
     * @param tr  the throwable to be log
     */
    fun i(msg: String?, tr: Throwable?) {
        if (isEncryptLog) {
            XLog.i(DEFAULT_TAG, tr)
        } else {
            XLog.i(msg, tr)
        }
    }

    /**
     * Log an object with level [LogLevel.WARN].
     *
     * @param object the object to log
     * @see LogConfiguration.Builder.addObjectFormatter
     * @since 1.1.0
     */
    @JvmStatic
    fun w(`object`: Any?) {
        if (isEncryptLog) {
            XLog.w(DEFAULT_TAG, `object`)
        } else {
            XLog.w(`object`)
        }
    }

    /**
     * Log an array with level [LogLevel.WARN].
     *
     * @param array the array to log
     */
    @JvmStatic
    fun w(array: Array<Any?>?) {
        if (isEncryptLog) {
            XLog.w(DEFAULT_TAG, array)
        } else {
            XLog.w(array)
        }
    }

    /**
     * Log a message with level [LogLevel.WARN].
     *
     * @param format the format of the message to log
     * @param args   the arguments of the message to log
     */
    @JvmStatic
    fun w(format: String?, vararg args: Any?) {
        if (isEncryptLog) {
            XLog.w(DEFAULT_TAG, args)
        } else {
            XLog.w(format, args)
        }
    }

    /**
     * Log a message with level [LogLevel.WARN].
     *
     * @param msg the message to log
     */
    @JvmStatic
    fun w(msg: String?) {
        if (isEncryptLog) {
            XLog.w(DEFAULT_TAG, msg)
        } else {
            XLog.w(msg)
        }
    }

    /**
     * Log a message and a throwable with level [LogLevel.WARN].
     *
     * @param msg the message to log
     * @param tr  the throwable to be log
     */
    @JvmStatic
    fun w(msg: String?, tr: Throwable?) {
        if (isEncryptLog) {
            XLog.w(DEFAULT_TAG, tr)
        } else {
            XLog.w(msg, tr)
        }
    }

    /**
     * Log an object with level [LogLevel.ERROR].
     *
     * @param object the object to log
     * @see LogConfiguration.Builder.addObjectFormatter
     * @since 1.1.0
     */
    @JvmStatic
    fun e(`object`: Any?) {
        if (isEncryptLog) {
            XLog.e(DEFAULT_TAG, `object`)
        } else {
            XLog.e(`object`)
        }
    }

    /**
     * Log an array with level [LogLevel.ERROR].
     *
     * @param array the array to log
     */
    @JvmStatic
    fun e(array: Array<Any?>?) {
        if (isEncryptLog) {
            XLog.e(DEFAULT_TAG, array)
        } else {
            XLog.e(array)
        }
    }

    /**
     * Log a message with level [LogLevel.ERROR].
     *
     * @param format the format of the message to log
     * @param args   the arguments of the message to log
     */
    @JvmStatic
    fun e(format: String?, vararg args: Any?) {
        if (isEncryptLog) {
            XLog.e(DEFAULT_TAG, args)
        } else {
            XLog.e(format, args)
        }
    }

    /**
     * Log a message with level [LogLevel.ERROR].
     *
     * @param msg the message to log
     */
    @JvmStatic
    fun e(msg: String?) {
        if (isEncryptLog) {
            XLog.e(DEFAULT_TAG, msg)
        } else {
            XLog.e(msg)
        }
    }

    /**
     * Log a message and a throwable with level [LogLevel.ERROR].
     *
     * @param msg the message to log
     * @param tr  the throwable to be log
     */
    @JvmStatic
    fun e(msg: String?, tr: Throwable?) {
        if (isEncryptLog) {
            XLog.e(DEFAULT_TAG, tr)
        } else {
            XLog.e(msg, tr)
        }
    }

    /**
     * Log a message with level [LogLevel.ERROR].
     *
     * @param tag custom tag
     * @param msg the message to log
     */
    fun e(tag: String, msg: String?) {
        if (isEncryptLog) {
            XLog.e(DEFAULT_TAG, msg)
        } else {
            XLog.tag(tag).e(msg)
        }
    }

    /**
     * Log an object with specific log level.
     *
     * @param logLevel the specific log level
     * @param object   the object to log
     * @see LogConfiguration.Builder.addObjectFormatter
     * @since 1.4.0
     */
    @JvmStatic
    fun log(logLevel: Int, `object`: Any?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, `object`)
        } else {
            XLog.log(logLevel, `object`)
        }
    }

    /**
     * Log an array with specific log level.
     *
     * @param logLevel the specific log level
     * @param array    the array to log
     * @since 1.4.0
     */
    fun log(logLevel: Int, array: Array<Any?>?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, array)
        } else {
            XLog.log(logLevel, array)
        }
    }

    /**
     * Log a message with specific log level.
     *
     * @param logLevel the specific log level
     * @param format   the format of the message to log
     * @param args     the arguments of the message to log
     * @since 1.4.0
     */
    fun log(logLevel: Int, format: String?, vararg args: Any?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, args)
        } else {
            XLog.log(logLevel, format, args)
        }
    }

    /**
     * Log a message with specific log level.
     *
     * @param logLevel the specific log level
     * @param msg      the message to log
     * @since 1.4.0
     */
    fun log(logLevel: Int, msg: String?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, msg)
        } else {
            XLog.log(logLevel, msg)
        }
    }

    /**
     * Log a message and a throwable with specific log level.
     *
     * @param logLevel the specific log level
     * @param msg      the message to log
     * @param tr       the throwable to be log
     * @since 1.4.0
     */
    fun log(logLevel: Int, msg: String?, tr: Throwable?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, tr)
        } else {
            XLog.log(logLevel, msg, tr)
        }
    }

    /**
     * Log a JSON string, with level [LogLevel.DEBUG] by default.
     *
     * @param json the JSON string to log
     */
    fun json(json: String?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, json)
        } else {
            XLog.json(json)
        }
    }

    /**
     * Log a XML string, with level [LogLevel.DEBUG] by default.
     *
     * @param xml the XML string to log
     */
    fun xml(xml: String?) {
        if (isEncryptLog) {
            XLog.d(DEFAULT_TAG, xml)
        } else {
            XLog.xml(xml)
        }
    }

    /**
     * compress
     */
    fun compress(folderPath: String, zipFilePath: String) {
        try {
            LogUtils.compress(folderPath, zipFilePath)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun compressLog() {
        try {
            LogUtils.compress(fullLogDir, fullLogZip)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun getLogZipFile(): MutableList<File> {
        val file = File(fullLogZip)
        if (file.exists()) {
            return mutableListOf<File>().also {
                it.add(file)
            }
        }
        return mutableListOf()
    }

    fun hasCrashLog(): Boolean {
        val dir = File(fullLogDir)
        return if (dir.exists() && dir.isDirectory) {
            dir.listFiles()?.any {
                it.name.startsWith("crash-")
            } == true
        } else {
            false
        }
    }

    fun deleteLogFile() {
        try {
            File(fullLogZip).delete()

            val dir = File(fullLogDir)
            dir.listFiles()?.filter {
                it.name.startsWith("crash-")
            }?.forEach {
                it.delete()
            }

        } catch (e: Exception) {
            e(e.toString())
        }
    }

}

4.4 本地初始化

  • 需要将app模块下的AndroidManifest.xml中属性修改为 android:name=“.InitApplication”
class InitApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        appInit()
    }
    
    private fun appInit() {
        // 本地日志初始化
        KLog.init(this)
        FramesCrashHandler.instance.init(this)
}

到此,xlog的日志就可以使用了,只需要在其他模块引入一下log模块就可以调用。如:KLog.d("hello,xlog")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值