簡便地Android崩潰日誌收集

對於已經發布出去的程序,一旦出現崩潰,就很難調查緣由。收集崩潰日誌就尤其重要。java

像是騰訊Bugly就是提供相似的服務,可是我以爲這樣一個很精簡的功能沒有必要再引入別家的服務了。android

網上有不少相似功能的代碼,在程序崩潰的時候把日誌寫入到本地文件。bash

我這裏分享一個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)
複製代碼

舒適提示:這裏的寫入文件都是同步的,若是須要將錯誤日誌信息上傳到服務器,由於通常這種網絡通信都是異步的,因此須要將app

if (!handleException(e)) {
            // 未通過人爲處理,則調用系統默認處理異常,彈出系統強制關閉的對話框
            if (mDefaultHandler != null) {
                mDefaultHandler.uncaughtException(t, e)
            }
        }
複製代碼

中的異步

if (mDefaultHandler != null) {
                mDefaultHandler.uncaughtException(t, e)
            }
複製代碼

部分移動到上傳結束動做的回調中,防止上傳動做尚未結束,程序就被意外終止。ide

相關文章
相關標籤/搜索