Android: CrashHandler获取APP的crash信息

当APP发生Crash后虽然无法让其再继续运行,但能直到程序crash的原因,就可以修复错误(比如热修复)。当APP上线后,APP发生了crash,这个crash信息是很难被获取到的。而通过CrahHandler来监视应用的crash信息,给程序设置一个CrashHandler,这样当程序crash的时候就会调用CrahHandler的uncaughtException(),在这个方法中可以获取crash信息并上传到服务器或保留到手机的SD卡中,这种方式后台就可监控程序的运行状况并进行后续的修复。

发生crash的原因可能是系统底层的BUG,可能是机型适配,可能是网络状况等。当crash发生时,系统会kill掉正在执行的程序,造成常见的“闪退”。

Android提供了处理这类问题的方法:(Thread类中[xx:\AndroidSDK\sources\android-28\java\lang\Thread.java])

Android: CrashHandler获取APP的crash信息_第1张图片

Android: CrashHandler获取APP的crash信息_第2张图片

根据注释得知可以设置系统的默认异常处理器。这个方法就可解决线上的app的crash问题.

当Android发生carsh的时候,系统就会回调uncaughtException(),在此方法中就可以获取到异常信息,然后剩下的至于怎么处理它就是开发人员的事情了,比如:可把异常信息存储到SD卡中,当有网络的时候发送到服务器上,然后在新版本中去修复它,甚至可以在crash的时候给用户一个对话框告知程序crash了。

Android: CrashHandler获取APP的crash信息_第3张图片


实现步骤:

1.实现一个UncaughtExceptionHandler对象,在该对象的uncaughtException()中获取异常信息并将其存储到SD卡中或者上传到服务器,然后调用Thread的setDefaultUncaughtExceptionHandler()将它设置为线程默认的异常处理器。因为默认的异常处理器是Thread类的静态成员,它的作用对象是当前进程的所有线程。

package com.yinlei.crashhandler

import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.util.Log
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
import java.text.SimpleDateFormat
import java.util.*
import kotlin.contracts.contract

/**
 * 没用kotlon中的单例
 */
class CrashHandler private constructor() : Thread.UncaughtExceptionHandler{
    private lateinit var mDefaultCrashHandler: Thread.UncaughtExceptionHandler
    private lateinit var mContext: Context

    companion object{
         val TAG ="yinlei"
        private val DEBUG = true
        private val PATH = "${Environment.getExternalStorageDirectory().path}/CrashTest/log/"
        private const val FILE_NAME = "crash"
        private const val FILE_NAME_SUFFIX = ".trace"

        private var sInstance = CrashHandler()

        fun getInstance(): CrashHandler = sInstance

    }

    /**
     * 初始化工作准备
     */
    fun initWork(context: Context){
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler(this)
        mContext = context
        Log.d(TAG,"当前的FIle路径为:$PATH")
    }

    /**
     * 当APP中有未被捕获的异常,系统将会自动调用此方法
     * @param t 为出现未捕获异常的线程
     * @param e 为未捕获的异常,通过这个e就可以得到异常信息
     */
    override fun uncaughtException(t: Thread?, e: Throwable?) {
        try{
            //导出异常信息到SD卡中
            dumpExceptionToSDCard(e)
            Log.d(TAG,"我进入了uncaughtException方法" )
            //上传异常信息到服务器便于开发者分析日志解决bug等。。。【开发者自己处理】
        }catch (e: Exception){
            e.printStackTrace()
        }
    }

    /***
     * 导出异常信息到SDCard中
     * @param e 为未捕获的异常,通过这个e就可以得到异常信息
     */
    private fun dumpExceptionToSDCard(e: Throwable?) {
        Log.d(TAG,"我进入了dumpExceptionToSDCard方法" )

        //判断SDCard是否挂载
        if(Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED){//没挂载SD卡
            if(DEBUG){
                Log.d(TAG,"SD卡未挂载,跳过导出异常信息到SD卡中的操作。")
                return
            }
        }

        //挂载SD了的..
        val dir = File(PATH)
        if(!dir.exists()){
            dir.mkdirs()
        }

        val current = System.currentTimeMillis()
        val time = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date(current))
        val file = File(PATH+ FILE_NAME+time+ FILE_NAME_SUFFIX)
        Log.d(TAG,"文件位于:${file.absolutePath}" )
        try{
            PrintWriter(BufferedWriter(FileWriter(file))).use {
                it.println(time)
                dumpPhoneInfo(it)
                it.println()
                e!!.printStackTrace(it)
            }
        }catch (ex: Exception){
            Log.d(TAG,"导出异常信息失败")
            ex.printStackTrace()
        }

    }

    /**
     * 导出手机信息
     */
    private fun dumpPhoneInfo(pw: PrintWriter) {
        var pm  = mContext.packageManager

        var pi  = pm.getPackageInfo(mContext.packageName,PackageManager.GET_ACTIVITIES)
        pw.print("APP版本:")
        pw.print(pi.versionName)
        pw.println("_")
        pw.println(pi.versionCode)

        pw.print("Android版本号:")
        pw.print(Build.VERSION.RELEASE)
        pw.println("_")
        pw.print(Build.VERSION.SDK_INT)

        pw.print("手机制造商(Vendor): ")
        pw.println(Build.MANUFACTURER)

        pw.print("手机型号(Model): ")
        pw.println(Build.MODEL)

        pw.print("CPU架构:")
        pw.println(Build.CPU_ABI)
    }


}

当应用崩溃的时候,上述代码的作用是将异常信息和设备信息写到SD卡中,接着异常交给系统处理,系统会帮助我们终止程序,如果系统没有默认的异常处理机制,那么就自行终止。

2.使用编写的自定义CrashHandler:Application初始化的时候为线程设置CrashHandler

Android: CrashHandler获取APP的crash信息_第4张图片

package com.yinlei.crashhandler

import android.app.Application

/**
 * 此处kotlin写创建kt文件的时候要类名首字母大写,才有class + 类名 的提示
 */
class MyApplication : Application(){

    companion object{
        private lateinit var sInstance: MyApplication
        
        fun getInstance(): MyApplication = sInstance
        
    }
    
    override fun onCreate() {
        super.onCreate()
        sInstance = this
        
        //在这里为应用设置异常处理,然后程序才能获取未处理的异常
        var crashHandler = CrashHandler.getInstance()
        crashHandler.initWork(this)
    }
    
    
} 

3.自己模拟一个异常出来看看程序是否能线上捕获到[此处模拟通过在SD卡是否能看到异常文件即可判断是否有效代码]

注意Android 6.0动态权限适配:

Android: CrashHandler获取APP的crash信息_第5张图片

package com.yinlei.crashhandler

import android.Manifest
import android.content.pm.PackageManager
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.util.Log
import android.view.View
import com.yinlei.crashhandler.CrashHandler.Companion.FILE_NAME
import com.yinlei.crashhandler.CrashHandler.Companion.FILE_NAME_SUFFIX
import com.yinlei.crashhandler.CrashHandler.Companion.PATH
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity(){


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        checkAndroidPermission()

        testTryCatch()

        ProduceException.setOnClickListener {
            //模拟异常抛出情况,抛出一个运行时异常
            throw RuntimeException("我是一个运行时异常!")
        }


    }

    /**
     * 危险权限适配
     */
    private fun checkAndroidPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CALENDAR)
            != PackageManager.PERMISSION_GRANTED) {
            // Permission is not granted
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                // Show an explanation to the user *asynchronously* -- don't block
                // this thread waiting for the user's response! After the user
                // sees the explanation, try again to request the permission.
                ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    0)
            } else {
                // No explanation needed, we can request the permission.
                ActivityCompat.requestPermissions(this,
                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    0)

                // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
                // app-defined int constant. The callback method gets the
                // result of the request.
            }
        } else {
            // Permission has already been granted
        }
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
        when (requestCode) {
            0 -> {
                // If request is cancelled, the result arrays are empty.
                if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                    // permission was granted, yay! Do the
                    // contacts-related task you need to do.
                    Log.d(CrashHandler.TAG,"授权成功")

                } else {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                    Log.d(CrashHandler.TAG,"权限被否决了")
                }
                return
            }

            // Add other 'when' lines to check for other
            // permissions this app might request.
            else -> {
                // Ignore all other requests.
                Log.d(CrashHandler.TAG,"检测到其他权限")
            }
        }
    }


    private fun testTryCatch(){
        try{
            thread {

                try{

                    var result: CharSequence = (3/0).toString()

                    ShowResult.append(result)

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

                /**
                 * 当然kotlin提供了空安全机制,采用如下代码就可以避免空指针异常,并将判空交给开发者自己去处理
                 */
//                var file1: File? = null
//                file1!!.absolutePath

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

 

4.验证:[这里我打包给了真机运行,模拟了线上环境]

运行APP点击按钮抛出运行时异常,然后在手机文件管理器中查找或者搜索CrashTest,可以发现如下:

Android: CrashHandler获取APP的crash信息_第6张图片Android: CrashHandler获取APP的crash信息_第7张图片

 

你可能感兴趣的:(Android)