性能优化最佳实践#CRASH机制

58 阅读4分钟

性能优化最佳实践#启动优化

性能优化最佳实践#UI卡顿优化

性能优化最佳实践#内存优化

性能优化最佳实践#Crash机制

性能优化最佳实践#ANR优化

性能优化最佳实践#体积包优化

一.Crash产生的原因

通常crash产生有以下三种原因

1.java或native层未捕获的异常

2.anr导致的crash*

3.WTF:what a terrible failure 如activity在manifest里面未配置而启动


二.系统是如何处理Crash

我们Thread都会有dispatchUncaughtException来处理未捕获的异常,RuntimeInit的commonInit方法为其设置了默认的处理方法,那就是打印日志和杀死应用:





Thread.java


//当前线程是否设置了handler,如果有则执行,没有就到所在的ThreadGroup中获取
//ThreadGroup实现了UncaughtExceptionHandler接口它的逻辑是:




//当前线程的handler
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

// Android-changed: Make dispatchUncaughtException() public, for use by tests.
public final void dispatchUncaughtException(Throwable e) {
    // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
    Thread.UncaughtExceptionHandler initialUeh =
    Thread.getUncaughtExceptionPreHandler(); //当前线程的异常处理的handler
    if (initialUeh != null) {
        try {
            initialUeh.uncaughtException(this, e);
        } catch (RuntimeException | Error ignored) {
            // Throwables thrown by the initial handler are ignored
        }
    }
    // END Android-added: uncaughtExceptionPreHandler for use by platform.

    getUncaughtExceptionHandler().uncaughtException(this, e);
}
ThreadGroup.java

    public void uncaughtException(Thread t, Throwable e) {
    //该ThreadGroup如果有父ThreadGroup,则直接调用父Group的uncaughtException方法
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            //如果设置了全局默认的UncaughtExceptionHandler接口,则调用全局的
            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);
            }
        }
    }


RuntimeInit.java

protected static final void commonInit() {

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

private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // Don't re-enter if KillApplicationHandler has already run
        if (mCrashing) return;
        if (mApplicationObject == null) {
            // The "FATAL EXCEPTION" string is still used on Android even though
            // apps can set a custom UncaughtExceptionHandler that renders uncaught
            // exceptions non-fatal.
            Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
        } else {
            StringBuilder message = new StringBuilder();
            // The "FATAL EXCEPTION" string is still used on Android even though
            // apps can set a custom UncaughtExceptionHandler that renders uncaught
            // exceptions non-fatal.
            message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
            final String processName = ActivityThread.currentProcessName();
            if (processName != null) {
                message.append("Process: ").append(processName).append(", ");
            }
            message.append("PID: ").append(Process.myPid());
            Clog_e(TAG, message.toString(), e);
        }
    }
}

/**
     * Handle application death from an uncaught exception.  The framework
     * catches these for the main threads, so this should only matter for
     * threads created by applications.  Before this method runs,
     * {@link LoggingHandler} will already have logged details.
     */
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread t, Throwable e) {
        try {
            // 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
                 //调用ams的handleApplicationCrash
                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 {
                // Try everything to make sure this process goes away.
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }
    }

所以我们处理crash的默认流程是先判断当前线程是否处理了,如果没有就执行当前线程的group的,然后在group中会判断其父group是否有,如果有就调用父group的,没有就调用默认的全局的。

class CrashActivity:AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // CrashHandler.getInstance(this)
        setContentView(R.layout.activity_crash)
        //        Thread.setDefaultUncaughtExceptionHandler { t, e ->
        //            println("CrashActivity:$t,$e")
        //
        //        }
    }

    fun crash(view: View) {

        var group=MyThreadGroup(Thread.currentThread().threadGroup,"myGroup")//设置自己的group
        Thread (group){
            //1.先去执行当前线程的
            //  public UncaughtExceptionHandler getUncaughtExceptionHandler() {
            //        return uncaughtExceptionHandler != null ?
            //            uncaughtExceptionHandler : group;
            //    }
            // 2.如果没有执行线程group的异常处理机制
            //1.找下有没有父类的group,有的话限制执行父类的,没有的话执行全局,如果全局依旧没有---
            //            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);
            //                    }
            //                }
            //            }
            println("name:${Thread.currentThread().name},group:${Thread.currentThread().threadGroup.parent}")
            var a=1/0//

        }.start()

    }
}

三.自定义处理机制

1.打印堆栈在合适的时间上报数据。
2.设置自己的CrashExceptionHandler,重启应用。

package com.seven.crash

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.os.Process
import android.util.Log
import android.widget.Toast
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.IOException
import java.io.PrintWriter
import java.lang.Exception
import java.lang.Thread.UncaughtExceptionHandler
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale


/**
 *
 * Time: 2025/7/24
 *
 * Author: seven
 *
 * Description:
 *
 */

class CrashHandler private constructor(_mContext: Context) : Thread.UncaughtExceptionHandler {
    private val TAG = "CrashHandler"
    private var mDefaultCrashHandler: UncaughtExceptionHandler? = null //原来默认的处理的全局对象
    private var mContext: Context

    init {
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler()
        Thread.setDefaultUncaughtExceptionHandler(this)
        this.mContext = _mContext
    }


    /**
     * 双重校验
     */
    companion object {

        @SuppressLint("StaticFieldLeak")
        @Volatile
        private var instance: CrashHandler? = null
        fun getInstance(mContext: Context) =
        instance ?: synchronized(this) {
            instance ?: CrashHandler(mContext).also { instance = it }
        }
    }

    override fun uncaughtException(t: Thread, e: Throwable) {
        //        可以拦截默认的looper就不会让activity卡死  https://blog.csdn.net/chenlove1/article/details/71423032/
        //        Handler(Looper.getMainLooper()).post {
        //
        //            while (true){
        //                try {
        //                    Looper.loop();
        //                }catch (e:Exception){
        //                    e.printStackTrace()
        //                }
        //            }
        //        }
        //用户未处理且系统默认处理
        if (null != mDefaultCrashHandler && !handleException(e)) {
            mDefaultCrashHandler?.uncaughtException(t, e)
        } else {
            //直接退出
            android.os.Process.killProcess(android.os.Process.myPid())
            System.exit(10);

        }
    }

    private fun handleException(ex: Throwable?): Boolean {
        Log.d(TAG, "handleException:$ex")
        if (null == ex) {
            Log.d(TAG, "handleException--- ex==null")
            return false
        }
        //收集crash信息
        dumpExceptionToSDCard(ex)
        Thread {
            Looper.prepare()
            //https://juejin.cn/post/6844904103538065422 为什么可以在子线程中展示
            Toast.makeText(mContext, "很抱歉,程序异常,正在退出!", Toast.LENGTH_SHORT).show()
            Looper.loop()
        }.start()
        //        upLoadToServer()
        //        restartApp()
        return true
    }

    /**
     * 重启应用
     */
    private fun restartApp() {
        Log.d(TAG, "restartApp: ")
        val LaunchIntent = mContext.packageManager.getLaunchIntentForPackage(mContext.packageName)
        LaunchIntent!!.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        mContext.startActivity(LaunchIntent)
        Process.killProcess(Process.myPid())
        System.exit(10)
    }
    /**
     * 记录信息到本地文件
     * @param ex Throwable
     */
    private fun dumpExceptionToSDCard(ex: Throwable) {
        Log.d(TAG, "dumpExceptionToSDCard: $ex")
        if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
            Log.i(TAG, "no sdcard skip dump ")
            return
        }
        val mLodPath = Environment.getExternalStorageDirectory().absolutePath + "/crashHandler/"
        val file = File(mLodPath)
        if (!file.exists()) {
            file.mkdirs()
        }
        val time: String = SimpleDateFormat(
            "yyyy-mm-dd-HH:mm:ss",
            Locale.CHINA
        ).format(Date(System.currentTimeMillis()))
        Log.i(TAG, "$mLodPath$time.trace")
        val logFile = File(mLodPath, "$time.trace")
        try {
            val pw = PrintWriter(BufferedWriter(FileWriter(logFile)))
            pw.println(time)
            dumpPhoneInfo(pw)
            pw.println()
            ex.printStackTrace(pw)
            pw.close()
        } catch (e1: IOException) {
            e1.printStackTrace()
        }
    }

    private fun dumpPhoneInfo(pw: PrintWriter) {
        //应用的版本名称和版本号
        val pm: PackageManager = mContext.packageManager
        var pi: PackageInfo? = null
        try {
            pi = pm.getPackageInfo(mContext.packageName, PackageManager.GET_ACTIVITIES)
            if (pi != null) {
                pw.print("App Version: ")
                pw.print(pi.versionName)
                pw.print('_')
                pw.println(pi.versionCode)

                //android版本号
                pw.print("OS Version: ")
                pw.print(Build.VERSION.RELEASE)
                pw.print("_")
                pw.println(Build.VERSION.SDK_INT)

                //手机制造商
                pw.print("Vendor: ")
                pw.println(Build.MANUFACTURER)

                //手机型号
                pw.print("Model: ")
                pw.println(Build.MODEL)

                //cpu架构
                pw.print("CPU ABI: ")
                pw.println(Build.CPU_ABI)
            }
        } catch (e: PackageManager.NameNotFoundException) {
            e.printStackTrace()
        }
    }
}