对于已经发布出去的程序,一旦出现崩溃,就很难调查原因。收集崩溃日志就尤为重要。
像是腾讯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() 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) }复制代码
部分移动到上传结束动作的回调中,防止上传动作还没有结束,程序就被意外终止。