Android中Crash的记录和防护

Android中Crash的记录和防护


1、崩溃的记录:

创建自定义UncaughtExceptionHandler处理异常
01.定义并设置DefaultUncaughtExceptionHandler
02.存在Throwable时记录异常信息saveCrashInfoToFile
03.异常情况killProcess进程,App强制退出

/**
 * 异常处理捕获类
 */
@SuppressLint("StaticFieldLeak")
object CrashHandler : Thread.UncaughtExceptionHandler {

    private val mLogFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault())
    var path = "/sdcard/corelog/Crash/"

    private var mContext: Context? = null
    private var mDefaultHandler: Thread.UncaughtExceptionHandler? = null

    private val infos = HashMap<String, String>()

    init {
        //获取线程中的DefaultUncaughtExceptionHandler实例
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        //设置当前的DefaultUncaughtExceptionHandler
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

    /**
     * 初始化
     * @param context
     */
    fun init(context: Context) {
        mContext = context
        //设置崩溃日志文件的存储路径
        CrashHandler.path = context.externalCacheDir?.absolutePath.toString()
        /**
         * 确保创建文件夹
         */
        val crashFolder = File(path)
        if (!crashFolder.exists()) {
            crashFolder.mkdirs()
        }
    }

    /**
     *  当发生uncaughtException异常时,会进入该方法进行处理
     *  @param thread
     *  @param throwable
     */
    override fun uncaughtException(thread: Thread, throwable: Throwable) {
        if (!handleException(throwable) && mDefaultHandler != null) {
            mDefaultHandler!!.uncaughtException(thread, throwable)
        } else {
            try {
                //异常线程延迟0.5s,用于handleException()方法中的异常信息记录
                Thread.sleep(500)
            } catch (e: InterruptedException) {
                //记录异常信息
                e.printStackTrace()
            }
        }
        //发生异常,杀死当前进程
        android.os.Process.killProcess(android.os.Process.myPid())
        //退出当前System
        System.exit(1)
    }

    /**
     *
     * @param throwable
     * @return 没有处理返回false ,处理了返回true
     */
    private fun handleException(throwable: Throwable?): Boolean {
        //不存在异常
        if (throwable == null) {
            return false
        }
        //存在异常
        collectDeviceInfo() // 收集设备参数信息
        saveCrashInfoToFile(throwable) // 保存日志文件
        return true
    }

    /**
     * 获取崩溃的设备信息
     */
    private fun collectDeviceInfo() {
        try {
            val pm = mContext!!.packageManager
            val pi = pm.getPackageInfo(
                mContext!!.packageName,
                PackageManager.GET_ACTIVITIES
            )
            if (pi != null) {
                val versionName = pi.versionName
                val versionCode = pi.versionCode.toString() + ""
                infos["versionName"] = versionName
                infos["versionCode"] = versionCode
            }
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }

        val fields = Build::class.java.declaredFields
        for (field in fields) {
            try {
                field.isAccessible = true
                infos[field.name] = field.get(null).toString()
                Logcat.d("crash", field.name + " : " + field.get(null))
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }

    }

    /**
     * 保存崩溃信息到指定路径
     *
     * @param throwable
     */
    private fun saveCrashInfoToFile(throwable: Throwable) {
        // 获取设备信息
        val sb = StringBuffer()
        for ((key, value) in infos) {
            sb.append("$key=$value\n")
        }

        // 获取崩溃信息
        val writer = StringWriter()
        val printWriter = PrintWriter(writer)
        throwable.printStackTrace(printWriter)
        var cause: Throwable? = throwable.cause
        // 循环着把所有异常信息写入writer中
        while (cause != null) {
            cause.printStackTrace(printWriter)
            cause = cause.cause
        }
        printWriter.close()
        val result = writer.toString()
        sb.append(result)

        // 获取日志
        var buffRead: BufferedReader? = null
        var str: String? = null
        try {
            /* 命令的准备 */
            val getLog = ArrayList<String>()
            getLog.add("logcat")
            getLog.add("-t")
            getLog.add("1200")
            getLog.add("-v")
            getLog.add("time")
            val clearLog = ArrayList<String>()
            clearLog.add("logcat")
            clearLog.add("-c")

            val process = Runtime.getRuntime().exec(
                getLog.toTypedArray()
            )// 抓取当前的缓存日志
            buffRead = BufferedReader(
                InputStreamReader(process.inputStream)
            )
            /**
             * 获取输入流
             */
            Runtime.getRuntime().exec(clearLog.toTypedArray())
            /**
             *  清除是为了下次抓取不会从头抓取
             */
        } catch (e1: IOException) {
            e1.printStackTrace()
        }

        // 保存崩溃信息
        val time = mLogFormat.format(Date())
        val fileName = getNewName(time)
        if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
            val file = File(path)
            if (!file.exists()) {
                file.mkdirs()
            }
            val fos: FileOutputStream
            try {
                /**
                 * 输入文件
                 */
                fos = FileOutputStream("$path/$fileName")
                fos.write(sb.toString().toByteArray())
                val newline = System.getProperty("line.separator")// 换行的字符串

                var loop = true
                while (loop) {
                    var str = buffRead!!.readLine()
                    if (str != null) {
                        fos.write((time + str!!).toByteArray())// 加上年
                        fos.write(newline!!.toByteArray())// 换行
                    } else {
                        loop = false
                    }
                }
                fos.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
    }

    /**
     * 新文件
     * @param tim
     * @return
     *
     *
     * 默认保留20个崩溃文件超过20个
     * 从后往前删
     */
    private fun getNewName(time: String): String {
        val dirFile = File(path)
        //删除
        if (dirFile != null) {
            val mFiles = dirFile.listFiles()
            if (mFiles != null && mFiles.size > 20) {

                var deleteFile: File? = null

                val localTime = System.currentTimeMillis()
                //先从当前时间往后找,最近的一个
                for (mFile in mFiles) {
                    if (mFile.lastModified() > localTime) {
                        if (deleteFile == null) {
                            deleteFile = mFile
                        } else if (mFile.lastModified() < deleteFile.lastModified()) {
                            deleteFile = mFile
                        }
                    }
                }
                /**
                 * 没有,则从前往后找最早的一个
                 */
                if (deleteFile == null) {
                    deleteFile = mFiles[0]
                    for (mFile in mFiles) {
                        if (mFile.lastModified() < deleteFile!!.lastModified()) {
                            deleteFile = mFile
                        }
                    }
                }

                deleteFile?.delete()
            }

        }

        val timeStamp = System.currentTimeMillis()
        val name = "LOG-$time-$timeStamp"
        return "$name.log"
    }
}

2、崩溃的捕获:

创建NeverCrash来try/catch所有线程的异常
01.try/catch所有的异常过程,让异常无法抛出
02.打印异常信息

/**
 * 异常捕获
 */
public class NeverCrash {

    private CrashHandler mCrashHandler;

    private static NeverCrash mInstance;

    private NeverCrash() {

    }

    private static NeverCrash getInstance() {
        if (mInstance == null) {
            synchronized (NeverCrash.class) {
                if (mInstance == null) {
                    mInstance = new NeverCrash();
                }
            }
        }

        return mInstance;
    }

    public static void init(CrashHandler crashHandler) {
        getInstance().setCrashHandler(crashHandler);
    }

    private void setCrashHandler(final CrashHandler crashHandler) {

        mCrashHandler = crashHandler;
        //设置捕获所有的异常,下面定义的DefaultUncaughtExceptionHandle就无法获取并处理异常信息了
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                    try {
                        //try-catch掉所有的异常,异常不会抛出,App不会崩溃
                        Looper.loop();
                    } catch (Throwable e) {
                        if (mCrashHandler != null) {
                            //捕获异常处理。自定义的MyApplication中实现了异常处理的具体方法
                            mCrashHandler.uncaughtException(Looper.getMainLooper().getThread(), e);
                        }
                    }
                }
            }
        });

//        //设置静态的默认的UncaughtExceptionHandler,管辖所有进程的异常信息
//        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
//            @Override
//            public void uncaughtException(Thread t, Throwable e) {
//                //打印异常信息,不处理异常,所以出现崩溃仍然会退出App
//                if (mCrashHandler != null) {
//                    //捕获异常处理
//                    mCrashHandler.uncaughtException(t, e);
//                }
//
//            }
//        });
    }

    public interface CrashHandler {
        void uncaughtException(Thread t, Throwable e);
    }

    //异常拦截器
    public static boolean crashInterceptor(Throwable throwable, Thread thread) {

        if (thread.getId() == 1
                || throwable == null
                || throwable.getMessage() == null
                || throwable.getStackTrace() == null) {
            //异常发生之后,所在线程会挂掉.所以主线程异常,拦截了也没用.主线程也会死掉.
            //除非,后续判断,app在前台,触发APP重启.
            return false;
        }

        String classpath = null;
        if (throwable.getStackTrace() != null && throwable.getStackTrace().length > 0) {
            classpath = throwable.getStackTrace()[0].toString();
        }

        if (classpath == null) {
            return false;
        }

        //拦截GMS异常.
        if (throwable.getMessage().contains("Results have already been set") && classpath.contains(
                "com.google.android.gms")) {
            return true;
        }

        //拦截GMS的 NPE.
        if (classpath.contains("com.google.android.gms") && throwable instanceof NullPointerException) {
            return true;
        }


        //拦截ssl_NPE
        if (throwable instanceof NullPointerException && throwable.getMessage()
                .contains("ssl_session == null")) {
            return true;
        }

        return false;
    }
}

设置NeverCrash的初始化

class MyApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        NeverCrash.init { t, e -> Log.i("hy55", "t.name = ${t.name} + e.message = ${e.message}") }
    }
}

3、源码分析:

01.Thread.java中定义了处理异常接口UncaughtExceptionHandler
    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

02.ThreadGroup中定义了处理异常的流程:子线程的异常会让父线程来进行处理

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

03.RuntimeInit.java中定义了异常的具体处理方法

    //定义了异常的处理类DefaultUncaughtExceptionHandler
    @UnsupportedAppUsage
    protected static final void commonInit() {
        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");

        /*
         * set handlers; these apply to all threads in the VM. Apps can replace
         * the default handler, but not the pre handler.
         */
        LoggingHandler loggingHandler = new LoggingHandler();
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
        ......


    private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
        private final LoggingHandler mLoggingHandler;
        
        public KillApplicationHandler(LoggingHandler loggingHandler) {
            this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
        }

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                ensureLogging(t, e);

                // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
                if (mCrashing) return;
                mCrashing = true;

                // Try to end profiling. If a profiler is running at this point, and we kill the
                // process (below), the in-memory buffer will be lost. So try to stop, which will
                // flush the buffer. (This makes method trace profiling useful to debug crashes.)
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }

                // Bring up crash dialog, wait for it to be dismissed
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                if (t2 instanceof DeadObjectException) {
                    // System process is dead; ignore
                } else {
                    try {
                        Clog_e(TAG, "Error reporting crash", t2);
                    } catch (Throwable t3) {
                        // Even Clog_e() fails!  Oh well.
                    }
                }
            } finally {
                //杀死当前进程,App强制退出
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }


4、崩溃的感悟:

  • 01.Thread中UncaughtExceptionHandler用于处理异常,当异常发生且未捕获时,UncaughtExceptionHandler会抛出异常,并且该线程会消亡

    02.所以在Android中子线程死亡是允许的,主线程死亡就会导致ANR

    03.防止异常而不能只是捕获异常

    04.参考文章:https://blog.youkuaiyun.com/jamin0107/article/details/78865442

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值