android 崩溃日志收集以及上传服务器

前言

一般情况下app开发完成最后一道工序就是日志收集、奔溃信息查找,我们需要根据打印的错误日志来定位,分析,解决错误。现在一般我们会使用市场上一些第三方平台替我们做了这些事情,比如腾讯的Bugly,和友盟的统计等。但是我们对于一些特殊的app,比如支付、加密、军工……对数据保密性要求严格的应用就的自己搞了,接下来我们一起看看怎么搞定它

定位

这些Java的异常类,对于编译器来说,可以分为两大类:

unCheckedException(非检查异常):Error和RuntimeException以及他们各自的子类,都是非检查异常。换句话说,当我们编译程序的时候,编译器并不会提示我们这些异常。要么我们在编程的时候,对于可能抛出异常的代码加上try…catch,要么就等着运行的时候崩溃就好了。

checkedException(检查异常):除了UncheckedException之外,其他的都是checkedExcption。对于这种异常,我们的代码通常都无法进行编译,因为as都会提示我们出错了。这个时候要强制加上try…catch,或者将异常throw。


该图来自网络,如有侵权联系我删除

其实最主要的就是下面这个类:Thread.UncaughtExceptionHandler
这个接口很简单代码如下

@FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * 

Any exception thrown by this method will be ignored by the * Java Virtual Machine. * 当传过来的【Thread】因为穿过来的未捕获的异常而停止时候调用这个方法。 * 所有被这个方法抛出的异常,都将会被java虚拟机忽略。 * * @param t the thread * @param e the exception */ void uncaughtException(@RecentlyNonNull Thread var1, @RecentlyNonNull Throwable var2); }

简单解释一下Google 上的描述

/**
* Interface for handlers invoked when a Thread abruptly
* terminates due to an uncaught exception.
* 处理接口,当一个线程由于未捕获的异常突然停止的时候调用。
*
*

When a thread is about to terminate due to an uncaught exception
* the Java Virtual Machine will query the thread for its
* UncaughtExceptionHandler using
* {@link #getUncaughtExceptionHandler} and will invoke the handler's
* uncaughtException method, passing the thread and the
* exception as arguments.
* 当一个线程由于一个未捕获的异常即将崩溃的时候,Java虚拟机将会通过【getUncaughtExceptionHandler()】方法,来
* 查询这个线程的【UncaughtExceptionHandler】,并且会调用他的【uncaughtException()】方法,并且把当前线程
* 和异常作为参数传进去。
*
* If a thread has not had its UncaughtExceptionHandler
* explicitly set, then its ThreadGroup object acts as its
* UncaughtExceptionHandler. If the ThreadGroup object
* has no
* special requirements for dealing with the exception, it can forward
* the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
* default uncaught exception handler}.
*如果一个线程没有设置他的【UncaughtExceptionHandler】,那么他的ThreadGroup对象就会作为他的
*【UncaughtExceptionHandler】。如果【ThreadGroup】没有特殊的处理异常的需求,那么就会转调
*【getDefaultUncaughtExceptionHandler】这个默认的处理异常的handler。
*(线程组的东西我们先不管,我们只需要知道,如果Thread没有设置【UncaughtExceptionHandler】的话,那么
*最终会调用【getDefaultUncaughtExceptionHandler】获取默认的【UncaughtExceptionHandler】来处理异常)
*
* @see #setDefaultUncaughtExceptionHandler
* @see #setUncaughtExceptionHandler
* @see ThreadGroup#uncaughtException
* @since 1.5
*/

所有我们如果仅仅不想让app崩溃可以直接在application中调用:

Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> 
            //你倒是奔溃呀
 }

但这种操作容易被领导优化调,接下来我就附上至少被领导优化掉之前稍微犹豫一下的代码

import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.util.Log
import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
import java.io.StringWriter
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*

class CrashManager private constructor() : Thread.UncaughtExceptionHandler {
    private val TAG = "CrashManager"
    private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null
    private lateinit var mContext: Context

    companion object {
        val instance = CrashManager.holder
    }

    private object CrashManager {
        val holder = CrashManager()
    }

    fun init(context: Context) {
        Thread.currentThread().uncaughtExceptionHandler = this
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        mContext = context
    }

    override fun uncaughtException(p0: Thread, p1: Throwable) {
        val crashFileName = saveCatchInfo2File(p1)
        Log.e(TAG, "fileName --> $crashFileName")
        cacheCrashFile(crashFileName)
        mDefaultHandler?.uncaughtException(p0, p1)
    }

    /**
     * 信息保存
     */
    private fun saveCatchInfo2File(ex: Throwable): String? {
        var fileName: String? = ""
        val sb = StringBuffer()
        for ((key, value) in obtainSimpleInfo(mContext).entries) {
            sb.append(key).append(" = ").append(value).append("\n")
        }
        sb.append(obtainExceptionInfo(ex))
        if (Environment.getExternalStorageState() ==
            Environment.MEDIA_MOUNTED
        ) {
            val dir = File(
                mContext.filesDir.toString() + File.separator + "crash"
                        + File.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(sb.toString().toByteArray())
                fos.flush()
                fos.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        return fileName
    }

    /**
     * 获取一些简单的信息,软件版本,手机版本,型号等信息存放在HashMap中
     * 这里缺什么可以自己补全
     */
    private fun obtainSimpleInfo(context: Context): HashMap {
        val map = HashMap()
        val mPackageManager = context.packageManager
        var mPackageInfo: PackageInfo? = null
        try {
            mPackageInfo = mPackageManager.getPackageInfo(
                context.packageName, PackageManager.GET_ACTIVITIES
            )
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
        map["versionName"] = mPackageInfo?.versionName ?: ""
        map["versionCode"] = "" + mPackageInfo?.versionCode
        map["MODEL"] = "" + Build.MODEL
        map["SDK_INT"] = "" + Build.VERSION.SDK_INT
        map["PRODUCT"] = "" + Build.PRODUCT
        map["MOBILE_INFO"] = getMobileInfo()
        return map
    }

    /**
     * 缓存崩溃日志文件
     */
    private fun cacheCrashFile(fileName: String?) {
        val sp = mContext.getSharedPreferences("crash", Context.MODE_PRIVATE)
        sp.edit().putString("CRASH_FILE_NAME", fileName).apply()
    }

    /**
     * 获取系统未捕捉的错误信息
     */
    private fun obtainExceptionInfo(throwable: Throwable): String? {
        val stringWriter = StringWriter()
        val printWriter = PrintWriter(stringWriter)
        throwable.printStackTrace(printWriter)
        printWriter.close()
        return stringWriter.toString()
    }

    /**
     * 获取设备信息
     *
     */
    private fun getMobileInfo(): String {
        val sb = StringBuffer()
        try {
            val fields = Build::class.java.declaredFields
            for (field in fields) {
                field.isAccessible = true
                val name = field.name
                val value = field[null].toString()
                sb.append("$name=$value")
                sb.append("\n")
            }
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
        return sb.toString()
    }

    /**
     * 删除文件
     */
    private fun deleteDir(dir: File): Boolean {
        if (dir.isDirectory) {
            val iterator = dir.list().iterator()
            while (iterator.hasNext()) {
                val success = deleteDir(File(dir, iterator.next()))
                if (!success) {
                    return false
                }
            }
        }
        // 目录此时为空,可以删除
        return true
    }

    /**
     * 返回当前日期根据格式
     */
    private fun getAssignTime(str: String): String? {
        val dataFormat: DateFormat = SimpleDateFormat(str)
        val currentTime = System.currentTimeMillis()
        return dataFormat.format(currentTime)
    }

    /**
     * 获取崩溃文件名称
     *
     * @return
     */
    fun getCrashFile(): File? {
        val crashFileName = mContext.getSharedPreferences(
            "crash",
            Context.MODE_PRIVATE
        ).getString("CRASH_FILE_NAME", "")
        return File(crashFileName)
    }
}

调用方式applicaiton 中:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        CrashManager.instance.init(this)
    }
}

测试:Activity onCreate中调用:

        // 获取上次的崩溃信息(拿到file你就可以肆意妄为了)
        val crashFile = CrashManager.instance.getCrashFile()
        // 上传到服务器

你可能感兴趣的:(android 崩溃日志收集以及上传服务器)