【Android】CrashMgr管理,搭配Bugly

CrashMgr管理,搭配Bugly

Why?

线上的环境, 在测试条件没有触发的bug会影响到用户体验, 特别是Crash, 这时就需要重启和收集的机制让我们来第一时间收集bug信息

这也就是我们为何写CrashMgr的原因

Crazy Coding

package com.mozhimen.basicsk.crashk

import android.annotation.SuppressLint
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Looper
import android.os.Process
import android.util.Log
import android.widget.Toast
import com.mozhimen.basicsk.crashk.commons.ICrashKListener
import com.mozhimen.basicsk.utilk.UtilKGlobal
import java.io.PrintWriter
import java.io.StringWriter
import java.io.Writer
import java.util.*
import kotlin.system.exitProcess

/**
 * @ClassName CrashKMgr
 * @Description Need <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
 * @Author Kolin Zhao / Mozhimen
 * @Date 2021/12/9 17:18
 * @Version 1.0
 */
@SuppressLint("StaticFieldLeak")
class CrashK private constructor() : Thread.UncaughtExceptionHandler {

    //region # variate
    private val TAG = "CrashK>>>>>"

    private lateinit var _context: Application
    private lateinit var _exceptionHandler: Thread.UncaughtExceptionHandler

    private var _clcCallback: CrashKLifeCycleCallback? = null

    private val _crashInfos = HashMap<String, String>() //用来存储设备信息和异常信息

    private var _logFileName: String? = null

    private var _I_crashKListener: ICrashKListener? = null

    companion object {
        @JvmStatic
        val instance: CrashK by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            CrashK()
        }
    }
    //endregion

    //初始化
    fun init(
        ICrashKListener: ICrashKListener
    ) {
        _context = UtilKGlobal.instance.getApp()!!
        _clcCallback = CrashKLifeCycleCallback()
        _I_crashKListener = ICrashKListener
        _exceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
        _context.registerActivityLifecycleCallbacks(_clcCallback)
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    fun getLog(): String? {
        return _logFileName
    }

    fun getTopActivity(): Class<*>? {
        val manager = _context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val className = manager.getRunningTasks(1)[0].topActivity!!.className
        Log.d(TAG, "getTopActivity: className $className")
        var cls: Class<*>? = null
        try {
            cls = Class.forName(className)
        } catch (e: ClassNotFoundException) {
            e.printStackTrace()
        }
        return cls
    }


    //当UncaughtException发生时会转入该函数来处理
    override fun uncaughtException(thread: Thread, ex: Throwable) {
        ex.printStackTrace()
        if (!handleException(ex)) {
            _exceptionHandler.uncaughtException(thread, ex)
        } else {
            try {
                Log.d(TAG, "uncaughtException: restart")
                val alarmManager = _context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
                val intent = Intent(_context, getTopActivity())
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
                val restartIntent = PendingIntent
                    .getActivity(_context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
                alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 500, restartIntent)
                _context.startActivity(intent)
            } catch (e: Exception) {
                Log.e(TAG, "uncaughtException: ${e.localizedMessage}")
            }

            // 退出程序
            _clcCallback?.removeAllActivities()
            Process.killProcess(Process.myPid())
            exitProcess(0)
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private fun handleException(ex: Throwable?): Boolean {
        if (ex == null) return false
        object : Thread() {
            override fun run() {
                Looper.prepare()
                Toast.makeText(_context, "程序出现异常,即将退出.", Toast.LENGTH_LONG).show()
                Looper.loop()
            }
        }.start()

        // 收集设备参数信息
        collectDeviceInfo(_context)

        // 获取到日志文件名
        _logFileName = saveCrashInfo2File(ex)

        // 上传或其他处理外放
        _I_crashKListener?.onGetMessage(_logFileName)

        return true
    }

    //收集设备参数信息
    private fun collectDeviceInfo(context: Context) {
        try {
            val packageManager = context.packageManager
            val packageInfo =
                packageManager.getPackageInfo(context.packageName, PackageManager.GET_ACTIVITIES)
            packageInfo?.let {
                val versionName =
                    if (packageInfo.versionName == null) "null" else packageInfo.versionName
                val versionCode = packageInfo.versionCode.toString()
                _crashInfos["versionName"] = versionName
                _crashInfos["versionCode"] = versionCode
            }
        } catch (e: PackageManager.NameNotFoundException) {
            Log.e(TAG, "collectDeviceInfo: an error happened when collect package info")
        }

        val fields = Build::class.java.declaredFields
        for (field in fields) {
            try {
                field.isAccessible = true
                _crashInfos[field.name] = field[null].toString()
                Log.d(TAG, "collectDeviceInfo: ${field.name} : ${field[null]})")
            } catch (e: Exception) {
                Log.e(TAG, "collectDeviceInfo: an error happened when collect crash info")
            }
        }
    }

    /**
     * 保存错误信息到文件中,需要有对SD的读写权限!
     *
     * @param ex
     * @return 返回文件名称, 便于将文件传送到服务器
     */
    private fun saveCrashInfo2File(ex: Throwable): String {
        val sb = StringBuilder()
        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.close()
        val result = writer.toString()
        sb.append("<font color=\"#FF0000\">")
        sb.append(result)
        sb.append("</font>")
        return sb.toString()
    }
}
class CrashKLifeCycleCallback : Application.ActivityLifecycleCallbacks {

    private val activities = ArrayList<Activity>()

    fun addActivity(activity: Activity) {
        activities.add(activity)
    }

    fun removeActivity(activity: Activity) {
        activities.remove(activity)
    }

    fun removeAllActivities() {
        for (activity in activities) {
            if (!activity.isFinishing) {
                activity.finish()
            }
        }
        activities.clear()
    }

    override fun onActivityCreated(activity: Activity, p1: Bundle?) {
        addActivity(activity)
    }

    override fun onActivityStarted(p0: Activity) {}

    override fun onActivityResumed(p0: Activity) {}

    override fun onActivityPaused(p0: Activity) {}

    override fun onActivityStopped(p0: Activity) {}

    override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {}

    override fun onActivityDestroyed(activity: Activity) {
        removeActivity(activity)
    }
}
  • 另外还要权限
<uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

How To use

class MainApplication : BaseKApplication() {
    override fun onCreate() {
        super.onCreate()

        ...
        
        CrashK.instance.init(_crashKCallback)
    }

    private val _crashKCallback = object : ICrashKListener {
        override fun onGetMessage(msg: String?) {
            msg?.let {

            } ?: "Ops! A crash happened, but i didn't get it messages".e()
        }
    }
}

其他的扩展用法

  • 结合你的上报接口或bugly
private val _crashKCallback = object : ICrashKListener {
	override fun onGetMessage(msg: String?) {
    	msg?.let {
			CrashReport.postCatchedException(Throwable(msg))
        } ?: "Ops! A crash happened, but i didn't get it messages".e()
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_42473228/article/details/124978943