Android全局异常捕获上传服务器处理

本文介绍了一个用于Android应用的全局异常捕获机制,通过自定义CrashHandler类实现未捕获异常的处理,包括收集设备信息、异常堆栈信息,并将其上传至服务器。此外,还介绍了如何在应用启动时初始化此异常处理机制。
package com.hzpd.jwztc.service

import android.content.Context
import android.os.Environment
import android.util.Log
import chengying.com.core.util.TimeUtils
import chengying.com.core.util.TimeUtils.format1
import chengying.com.core.util.getAppVersion
import com.google.gson.Gson
import com.hzpd.jwztc.service.api.ErrorMsgService
import com.hzpd.jwztc.service.beans.request.AddWarnInfoReq
import com.hzpd.jwztc.service.beans.request.ErrorInfoReq
import com.hzpd.jwztc.service.beans.request.WarnInfo
import com.hzpd.jwztc.service.control.NetUtils
import java.io.*
import java.io.File.separator
import java.text.SimpleDateFormat
import java.util.concurrent.CountDownLatch


/**
 * Created by yangfan
 * nrainyseason@163.com
 * 异常全局捕获
 */
class CrashHandler private constructor() : Exception(), Thread.UncaughtExceptionHandler {
    private var context: Context? = null
    private val tag = javaClass.simpleName
    private var mDefaultCrashHandler: Thread.UncaughtExceptionHandler? = null
    private var errorSrc: ErrorMsgService? = null
    private var countDownLunch: CountDownLatch? = null

    companion object {
        private var instance: CrashHandler? = null
            get() {
                if (field == null) {
                    field = CrashHandler()
                }
                return field
            }

        @Synchronized
        fun get(): CrashHandler {
            return instance!!
        }
    }

    fun init(context: Context) {
        this.context = context
        errorSrc = ErrorMsgService(context!!)
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler(this)
    }


    override fun uncaughtException(thread: Thread, ex: Throwable) {
        Log.e(tag, "uncaughtException")
        //如果异常没有被处理
        if (!handleException(ex) && mDefaultCrashHandler != null) {
            //进行异常捕获
            mDefaultCrashHandler?.uncaughtException(thread, ex);
        }
    }

    //上传服务器
    fun uploadToServer(ex: Throwable) {
        var crashReport = getCrashReport(ex)
        Log.e(tag, "crashReport" + crashReport)
        if (crashReport.isNullOrEmpty()) return
        var warnInfo = WarnInfo().apply {
            this.detail = AddWarnInfoReq().apply {
                this.clientOs = "Android: ${android.os.Build.VERSION.RELEASE}"
                this.clientAppVer = getAppVersion(context!!)
                this.clientType="${android.os.Build.BRAND} \r ${android.os.Build.MODEL}"
                this.warnTrack = crashReport
                this.netWork="${getNetState(NetUtils.getNetworkState(context!!))}"
            }
            this.warnTitle=getWarnTitle(ex)
            this.createTime = TimeUtils.getDateToString3(System.currentTimeMillis(), format1)
            this.warnType="6"
            this.warnFrom= "Android"
            this.warnLevel="5"
        }
        errorSrc?.saveErrorInfo(ErrorInfoReq().apply { this.warnInfo = Gson().toJson(warnInfo) }) {
            it?.let {
                if (it.code == 200) {
                    Log.e(tag, "upload success")
                }
                countDownLunch!!.countDown()
            }
        }
    }

    /**
     * 获取异常title
     */
    fun getWarnTitle(ex: Throwable):String{
        var array=ex.javaClass.name.split(".")
        if(array.size>0)return array[array.size-1]
        return "UnKnowException"
    }

    fun getNetState(state:Int):String{
        when(state){
            NetUtils.NETWORK_2G->return "2G"
            NetUtils.NETWORK_3G->return "3G"
            NetUtils.NETWORK_4G->return "4G"
            NetUtils.NETWORK_WIFI->return "wifi"
            else->return "无网络"
        }
    }

    private fun handleException(ex: Throwable): Boolean {
        if (ex == null) {
            return false
        }
        countDownLunch= CountDownLatch(1)
        var crashReport = getCrashReport(ex)
        Log.e(tag, "handleException: ${crashReport}")
//        saveInfoToSD(crashReport)
        uploadToServer(ex)
        countDownLunch!!.await()
        return true
    }

    /**
     * @Description 获取APP崩溃异常报告
     *
     * @param Context
     *            context(上下文), Throwable ex(异常信息)
     * @return String
     */
    private fun getCrashReport(ex: Throwable): String {
        var stringBuilder = StringBuilder()
        stringBuilder.append("Android: ${android.os.Build.VERSION.RELEASE}(BRAND: ${android.os.Build.BRAND} MODEL: ${android.os.Build.MODEL})\n")//版本
                .append("Exception: ${ex.message}\n")//异常信息
        var elements = ex.stackTrace
        elements.forEach {
            stringBuilder.append("${it}\n")
        }
        return stringBuilder.toString()
    }

    private fun saveInfoToSD(str: String): String? {
        var fileName: String? = null
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            val dir = File("${context?.getFilesDir()}${separator} crash ${separator}")
            if (dir.exists()) {
                deleteDir(dir)
            }
            if (!dir.exists()) {
                dir.mkdir()
            }
            try {
                fileName = dir.toString() + File.separator + getAssignTime("yyyy_MM_dd_HH_mm") + ".txt"
                val fos = FileOutputStream(fileName)
                fos.write(str.toByteArray())
                fos.flush()
                fos.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
        return fileName
    }

    private fun deleteDir(dir: File): Boolean {
        if (dir.isDirectory) {
            val children = dir.listFiles()
            // 递归删除目录中的子目录下
            for (child in children!!) {
                child.delete()
            }
        }
        // 目录此时为空,可以删除
        return true
    }

    private fun cacheCrashFile(fileName: String) {
        val sp = context?.getSharedPreferences("crash", Context.MODE_PRIVATE)
        sp?.edit()?.putString("CRASH_FILE_NAME", fileName)?.commit()
    }

    fun getCrashFile(): String {
        return context?.getSharedPreferences("crash", Context.MODE_PRIVATE)?.getString("CRASH_FILE_NAME", "")
                ?: ""
    }

    private fun getAssignTime(dateFormatStr: String): String {
        val dataFormat = SimpleDateFormat(dateFormatStr)
        val currentTime = System.currentTimeMillis()
        return dataFormat.format(currentTime)
    }

    fun getCrashReport(): String {
        var fileName = getCrashFile()
        if (fileName.isNullOrEmpty()) return ""
        var crashFile = File(fileName)
        var fis: FileInputStream? = null
        try {
            fis = FileInputStream(crashFile)
            val buffer = ByteArray(1024)
            val sb = StringBuilder()
            while (fis!!.read(buffer) !== -1) {
                sb.append(String(buffer))
            }
            return sb.toString()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            if (fis != null) {
                fis!!.close()
            }
        }
        return ""
    }

}

在application中调用

//全局异常捕获
        CrashHandler.get().init(applicationContext)

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值