美文网首页Android技术知识Android开发Android开发
Android - Kotlin 实现全局异常捕获

Android - Kotlin 实现全局异常捕获

作者: 古舟咕咕 | 来源:发表于2020-04-10 21:35 被阅读0次

    我们的 APP 项目多多少少有可能出现异常,从而发生崩溃闪退现象。要优化这个问题,全局异常捕获是个不错的选择。

    最近学习了 Kotlin,把之前用 Java 写的全局异常捕获用 Kotlin 重写了一遍,留着以后备用。

    简要介绍

    在 APP 遇到异常时,弹出提示,并且跳转到 Log 显示界面。

    实现过程

    1.CrashHandler.kt

    package com.myz.crashtest
    
    import android.content.Context
    import android.content.Intent
    import android.content.pm.PackageManager
    import android.os.Build
    import android.os.Looper
    import android.widget.Toast
    import androidx.core.content.pm.PackageInfoCompat
    import java.text.SimpleDateFormat
    import java.util.*
    import kotlin.system.exitProcess
    
    class CrashHandler private constructor(context: Context) : Thread.UncaughtExceptionHandler {
    
        // 系统默认的 UncaughtException 处理类
        private val mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
    
        // 上下文对象
        private val mContext = context
    
        // 用于存放参数信息
        private val info = LinkedHashMap<String, String>()
    
        // 用于格式化日期
        private val mDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
    
        // 单例模式
        companion object {
            private var instance: CrashHandler? = null
            fun getInstance(context: Context): CrashHandler? {
                if (instance == null) {
                    synchronized(CrashHandler::class) {
                        instance = CrashHandler(context)
                    }
                }
                return instance
            }
        }
    
        /**
         * 构造初始化
         */
        init {
            // 设置当前类为应用默认处理器
            Thread.setDefaultUncaughtExceptionHandler(this)
        }
    
        /**
         * 当 UncaughtException 发生时转入该函数
         * @param t
         * @param e
         */
        override fun uncaughtException(t: Thread, e: Throwable) {
            if (!handleException(e)) {
                mDefaultHandler?.uncaughtException(t, e)
            } else {
                try {
                    Thread.sleep(2000)
                } catch (e: Exception) {
                }
                exitProcess(0)
            }
        }
    
        /**
         * 自定义错误处理
         * @param e
         * @return 已处理异常信息
         */
        private fun handleException(e: Throwable): Boolean {
            Thread {
                Looper.prepare()
                Toast.makeText(mContext, "A problem caused", Toast.LENGTH_SHORT).show()
                Looper.loop()
            }.start()
    
            // 跳转到日志显示界面
            val intent = Intent(mContext, LogViewerActivity::class.java)
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            intent.putExtra("log_summary", getLogSummary(e))
            mContext.startActivity(intent)
    
            /**
             * 这里可以执行一些业务操作
             * 比如保存崩溃日志到文件等等
             */
            return true
        }
    
        /**
         * 收集参数信息
         * @param context
         */
        private fun putInfoToMap(context: Context) {
            info["设备型号"] = Build.MODEL
            info["设备品牌"] = Build.BOARD
            info["硬件名称"] = Build.HARDWARE
            info["硬件制造商"] = Build.MANUFACTURER
            info["系统版本"] = Build.VERSION.RELEASE
            info["系统版本号"] = "${Build.VERSION.SDK_INT}"
    
            val pm = context.packageManager
            val pi = pm.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES)
            if (pi != null) {
                info["应用版本"] = pi.versionName
                info["应用版本号"] = "${PackageInfoCompat.getLongVersionCode(pi)}"
            }
        }
    
        /**
         * 获取日志头信息
         * @return StringBuffer
         */
        private fun getLogHeader(): StringBuffer {
            val sb = StringBuffer()
            sb.append(">>>>时间: ${mDateFormat.format(Date())}\n")
            putInfoToMap(mContext)
            info.entries.forEach {
                sb.append("${it.key}: ${it.value}\n")
            }
            return sb
        }
    
        /**
         * 获取日志概要
         * @param e
         * @return 日志概要
         */
        private fun getLogSummary(e: Throwable): String {
            val sb = getLogHeader().append("\n")
            sb.append("异常类: ${e.javaClass}\n")
            sb.append("异常信息: ${e.message}\n\n")
            for (i in e.stackTrace.indices) {
                sb.append("****堆栈追踪 ${i + 1}\n")
                sb.append("类名: ${e.stackTrace[i].className}\n")
                sb.append("方法: ${e.stackTrace[i].methodName}\n")
                sb.append("文件: ${e.stackTrace[i].fileName}\n")
                sb.append("行数: ${e.stackTrace[i].lineNumber}\n\n")
            }
            return sb.toString().trim()
        }
    }
    

    2.MyApplication.kt

    package com.myz.crashtest
    
    import android.app.Application
    
    class MyApplication : Application() {
        override fun onCreate() {
            super.onCreate()
            CrashHandler.getInstance(this)
        }
    }
    

    3.LogViewerActivity.kt

    package com.myz.crashtest
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import kotlinx.android.synthetic.main.activity_log_viewer.*
    
    class LogViewerActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_log_viewer)
    
            val log_summary = intent.getStringExtra("log_summary")
            if (!log_summary.isNullOrEmpty()) {
                text_log.text = log_summary
            }
        }
    }
    

    4.activity_log_viewer.xml

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".LogViewerActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/text_log"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:padding="@dimen/widget_common_padding"
            android:text="@string/default_log_text" />
    
        </LinearLayout>
    
    </ScrollView>
    

    最后别忘了在AndroidManifest.xml文件的application节点中添加name属性!

    android:name=".MyApplication"
    

    相关文章

      网友评论

        本文标题:Android - Kotlin 实现全局异常捕获

        本文链接:https://www.haomeiwen.com/subject/yahjmhtx.html