简便地Android崩溃日志收集

对于已经发布出去的程序,一旦出现崩溃,就很难调查原因。收集崩溃日志就尤为重要。

像是腾讯Bugly就是提供类似的服务,但是我觉得这样一个很精简的功能没有必要再引入别家的服务了。

网上有很多类似功能的代码,在程序崩溃的时候把日志写入到本地文件。

我这里分享一个kotlin版本:

/**
 * Created by GreendaMi on 2018/5/3.
 */
class CrashHandler :Thread.UncaughtExceptionHandler {

    lateinit var mDefaultHandler: Thread.UncaughtExceptionHandler
    lateinit var mContext: Context
    // 保存手机信息和异常信息
    private val mMessage = HashMap<String,String>()

    companion object {
        var sInstance: CrashHandler? = null
        fun getInstance(): CrashHandler? {
            if (sInstance == null) {
                synchronized(CrashHandler::class.java) {
                    if (sInstance == null) {
                        synchronized(CrashHandler::class.java) {
                            sInstance = CrashHandler()
                        }
                    }
                }
            }
            return sInstance
        }
    }

    override fun uncaughtException(t: Thread?, e: Throwable?) {
        if (!handleException(e)) {
            // 未经过人为处理,则调用系统默认处理异常,弹出系统强制关闭的对话框
            if (mDefaultHandler != null) {
                mDefaultHandler.uncaughtException(t, e)
            }
        } else {
            // 已经人为处理,系统自己退出
            try {
                Thread.sleep(1000)
            } catch (e1: InterruptedException) {
                e1.printStackTrace()
            }
            //重启
            var intent = mContext?.packageManager?.getLaunchIntentForPackage(mContext?.packageName)
            intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
            mContext?.startActivity(intent)
            android.os.Process.killProcess(android.os.Process.myPid())

        }
    }

    /**
     * 初始化默认异常捕获
     *
     * @param context context
     */
    fun init(context: Context) {
        mContext = context
        // 获取默认异常处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        // 将此类设为默认异常处理器
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    /**
     * 是否人为捕获异常
     *
     * @param e Throwable
     * @return true:已处理 false:未处理
     */
    private fun handleException(e: Throwable?): Boolean {
        if (e == null) {// 异常是否为空
            return false
        }
        object : Thread() {
            // 在主线程中弹出提示
            override fun run() {
                Looper.prepare()
                Toast.makeText(mContext, "程序发生未知异常,将重启。", Toast.LENGTH_SHORT).show()
                Looper.loop()
            }
        }.start()
        collectErrorMessages()
        saveErrorMessages(e)
        return false
    }

    private fun collectErrorMessages() {
        val pm = mContext?.packageManager
        try {
            val pi = pm?.getPackageInfo(mContext?.packageName, PackageManager.GET_ACTIVITIES)
            if (pi != null) {
                val versionName = if (TextUtils.isEmpty(pi.versionName)) "null" else pi.versionName
                val versionCode = "" + pi.versionCode
                mMessage["versionName"] = versionName
                mMessage["versionCode"] = versionCode
            }
            // 通过反射拿到错误信息
            val fields = Build::class.java!!.fields
            if (fields != null && fields.isNotEmpty()) {
                for (field in fields!!) {
                    field.isAccessible = true
                    try {
                        mMessage[field.name] = field.get(null).toString()
                    } catch (e: IllegalAccessException) {
                        e.printStackTrace()
                    }

                }
            }
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }

    }

    private fun saveErrorMessages(e: Throwable) {
        val sb = StringBuilder()
        for (entry in mMessage) {
            val key = entry.key
            val value = entry.value
            sb.append(key).append("=").append(value).append("\n")
        }
        val writer = StringWriter()
        val pw = PrintWriter(writer)
        e.printStackTrace(pw)
        var cause: Throwable? = e.cause
        // 循环取出Cause
        if (cause != null) {
            cause.printStackTrace(pw)
        }
        pw.close()
        val result = writer.toString()
        sb.append(result)
        val time = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(Date())
        val fileName = "crash-" + time + "-" + System.currentTimeMillis() + ".log"
        // 有无SD卡
        if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
            val path = AppConfig.crashPath
            val dir = File(path)
            if (!dir.exists()) dir.mkdirs()
            var fos: FileOutputStream? = null
            try {
                fos = FileOutputStream(path + fileName)
                fos!!.write(sb.toString().toByteArray())
            } catch (e1: Exception) {
                e1.printStackTrace()
            } finally {
                if (fos != null) {
                    try {
                        fos!!.close()
                    } catch (e1: IOException) {
                        e1.printStackTrace()
                    }

                }
            }
        }
    }
}

在Application里面初始化一下:

 //异常收集
CrashHandler.getInstance()?.init(this@App)

温馨提示:这里的写入文件都是同步的,如果需要将错误日志信息上传到服务器,因为一般这种网络通讯都是异步的,所以需要将

if (!handleException(e)) {
            // 未经过人为处理,则调用系统默认处理异常,弹出系统强制关闭的对话框
            if (mDefaultHandler != null) {
                mDefaultHandler.uncaughtException(t, e)
            }
        }

中的

if (mDefaultHandler != null) {
                mDefaultHandler.uncaughtException(t, e)
            }

部分移动到上传结束动作的回调中,防止上传动作还没有结束,程序就被意外终止。

猜你喜欢

转载自juejin.im/post/5b3ad56ff265da62d21e1597
今日推荐