对于已经发布出去的程序,一旦出现崩溃,就很难调查缘由。收集崩溃日志就尤其重要。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