對於已經發布出去的程序,一旦出現崩潰,就很難調查緣由。收集崩潰日誌就尤其重要。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