千家信息网

怎么让APP永不崩溃

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,本篇内容介绍了"怎么让APP永不崩溃"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!让我的APP永不崩
千家信息网最后更新 2026年11月02日怎么让APP永不崩溃

本篇内容介绍了"怎么让APP永不崩溃"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

让我的APP永不崩溃

既然我们可以拦截崩溃,那我们直接把APP中所有的异常拦截了,不杀死程序。这样一个不会崩溃的APP用户体验不是杠杠的?

  • 有人听了摇摇头表示不赞同,这不小光跑来问我了:

"老铁,出现崩溃是要你解决它不是掩盖它!!"

  • 我拿把扇子扇了几下,有点冷但是故作镇定的说:

"这位老哥,你可以把异常上传到自己的服务器处理啊,你能拿到你的崩溃原因,用户也不会因为异常导致APP崩溃,这不挺好?"

  • 小光有点生气的说:

"这样肯定有问题,听着就不靠谱,哼,我去试试看"

小光的实验

于是小光按照网上一个小博主-积木的文章,写出了以下捕获异常的代码:

//定义CrashHandlerclass CrashHandler private constructor(): Thread.UncaughtExceptionHandler {    private var context: Context? = null    fun init(context: Context?) {        this.context = context        Thread.setDefaultUncaughtExceptionHandler(this)    }    override fun uncaughtException(t: Thread, e: Throwable) {}    companion object {        val instance: CrashHandler by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {            CrashHandler() }    }}//Application中初始化class MyApplication : Application(){    override fun onCreate() {        super.onCreate()        CrashHandler.instance.init(this)    }}//Activity中触发异常class ExceptionActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_exception)                btn.setOnClickListener {            throw RuntimeException("主线程异常")        }        btn2.setOnClickListener {            thread {                throw RuntimeException("子线程异常")            }        }    }}

小光一顿操作,写下了整套代码,为了验证它的猜想,写了两种触发异常的情况:子线程崩溃和主线程崩溃。

  • 运行,点击按钮2,触发子线程异常崩溃:

"咦,还真没啥影响,程序能继续正常运行"

  • 然后点击按钮1,触发主线程异常崩溃:

"嘿嘿,卡住了,再点几下,直接ANR了"

"果然有问题,但是为啥主线程会出问题呢?我得先搞懂再去找老铁对峙。"

小光的思考(异常源码分析)

首先科普下java中的异常,包括运行时异常和非运行时异常:

  • 运行时异常。是RuntimeException类及其子类的异常,是非受检异常,比如系统异常或者是程序逻辑异常,我们常遇到的有NullPointerException、IndexOutOfBoundsException等。遇到这种异常,Java Runtime会停止线程,打印异常,并且会停止程序运行,也就是我们常说的程序崩溃。

  • 非运行时异常。是属于Exception类及其子类,是受检异常,RuntimeException以外的异常。这类异常在程序中必须进行处理,如果不处理程序都无法正常编译,比如NoSuchFieldException,IllegalAccessException这种。

ok,也就是说我们抛出一个RuntimeException异常之后,所在的线程会被停止。如果主线程中抛出这个异常,那么主线程就会被停止,所以APP就会卡住无法正常操作,时间久了就会ANR。而子线程崩溃了并不会影响主线程也就是UI线程的操作,所以用户还能正常使用。

这样好像就说的通了。

等等,那为什么遇到setDefaultUncaughtExceptionHandler就不会崩溃了呢?

我们还得从异常的源码开始说起:

一般情况下,一个应用中所使用的线程都是在同一个线程组,而在这个线程组里只要有一个线程出现未被捕获异常的时候,JAVA 虚拟机就会调用当前线程所在线程组中的 uncaughtException()方法。

// ThreadGroup.java  private final ThreadGroup parent;    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);            }        }    }

parent表示当前线程组的父级线程组,所以最后还是会调用到这个方法中。接着看后面的代码,通过getDefaultUncaughtExceptionHandler获取到了系统默认的异常处理器,然后调用了uncaughtException方法。那么我们就去找找本来系统中的这个异常处理器--UncaughtExceptionHandler。

这就要从APP的启动流程说起了,之前也说过,所有的Android进程都是由zygote进程fork而来的,在一个新进程被启动的时候就会调用zygoteInit方法,这个方法里会进行一些应用的初始化工作:

    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {        if (RuntimeInit.DEBUG) {            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");        }        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");        //日志重定向        RuntimeInit.redirectLogStreams();        //通用的配置初始化          RuntimeInit.commonInit();        // zygote初始化        ZygoteInit.nativeZygoteInit();        //应用相关初始化        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);    }

而关于异常处理器,就在这个通用的配置初始化方法当中:

    protected static final void commonInit() {        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");       //设置异常处理器        LoggingHandler loggingHandler = new LoggingHandler();        Thread.setUncaughtExceptionPreHandler(loggingHandler);        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));        //设置时区        TimezoneGetter.setInstance(new TimezoneGetter() {            @Override            public String getId() {                return SystemProperties.get("persist.sys.timezone");            }        });        TimeZone.setDefault(null);        //log配置        LogManager.getLogManager().reset();        //***            initialized = true;    }

找到了吧,这里就设置了应用默认的异常处理器--KillApplicationHandler。

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);                //...                    // 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 {                // Try everything to make sure this process goes away.                Process.killProcess(Process.myPid());                System.exit(10);            }        }        private void ensureLogging(Thread t, Throwable e) {            if (!mLoggingHandler.mTriggered) {                try {                    mLoggingHandler.uncaughtException(t, e);                } catch (Throwable loggingThrowable) {                    // Ignored.                }            }        }

看到这里,小光欣慰一笑,被我逮到了吧。在uncaughtException回调方法中,会执行一个handleApplicationCrash方法进行异常处理,并且最后都会走到finally中进行进程销毁,Try everything to make sure this process goes away。所以程序就崩溃了。

关于我们平时在手机上看到的崩溃提示弹窗,就是在这个handleApplicationCrash方法中弹出来的。不仅仅是java崩溃,还有我们平时遇到的native_crash、ANR等异常都会最后走到handleApplicationCrash方法中进行崩溃处理。

另外有的朋友可能发现了构造方法中,传入了一个LoggingHandler,并且在uncaughtException回调方法中还调用了这个LoggingHandler的uncaughtException方法,难道这个LoggingHandler就是我们平时遇到崩溃问题,所看到的崩溃日志?进去瞅瞅:

private static class LoggingHandler implements Thread.UncaughtExceptionHandler {        public volatile boolean mTriggered = false;        @Override        public void uncaughtException(Thread t, Throwable e) {            mTriggered = true;            if (mCrashing) return;            if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {                Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);            } else {                StringBuilder message = new StringBuilder();                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);            }        }    }    private static int Clog_e(String tag, String msg, Throwable tr) {        return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);    }

这可不就是吗?将崩溃的一些信息--比如线程,进程,进程id,崩溃原因等等通过Log打印出来了。来张崩溃日志图给大家对对看:

好了,回到正轨,所以我们通过setDefaultUncaughtExceptionHandler方法设置了我们自己的崩溃处理器,就把之前应用设置的这个崩溃处理器给顶掉了,然后我们又没有做任何处理,自然程序就不会崩溃了,来张总结图。

小光又来找我对峙了

  • 搞清楚这一切的小光又来找我了:

"老铁,你瞅瞅,这是我写的Demo和总结的资料,你那套根本行不通,主线程崩溃就GG了,我就说有问题吧"

  • 我继续故作镇定:

"老哥,我上次忘记说了,只加这个UncaughtExceptionHandler可不行,还得加一段代码,发给你,回去试试吧"

    Handler(Looper.getMainLooper()).post {        while (true) {            try {                Looper.loop()            } catch (e: Throwable) {            }        }    }

"这,,能行吗"

小光再次的实验

小光把上述代码加到了程序里面(Application-onCreate),再次运行:

我去,真的没问题了,点击主线程崩溃后,还是可以正常操作app,这又是什么原理呢?

小光的再次思考(拦截主线程崩溃的方案思想)

我们都知道,在主线程中维护着Handler的一套机制,在应用启动时就做好了Looper的创建和初始化,并且调用了loop方法开始了消息的循环处理。应用在使用过程中,主线程的所有操作比如事件点击,列表滑动等等都是在这个循环中完成处理的,其本质就是将消息加入MessageQueue队列,然后循环从这个队列中取出消息并处理,如果没有消息处理的时候,就会依靠epoll机制挂起等待唤醒。贴一下我浓缩的loop代码:

    public static void loop() {        final Looper me = myLooper();        final MessageQueue queue = me.mQueue;        for (;;) {            Message msg = queue.next();             msg.target.dispatchMessage(msg);        }    }

一个死循环,不断取消息处理消息。再回头看看刚才加的代码:

    Handler(Looper.getMainLooper()).post {        while (true) {            //主线程异常拦截            try {                Looper.loop()            } catch (e: Throwable) {            }        }    }

我们通过Handler往主线程发送了一个runnable任务,然后在这个runnable中加了一个死循环,死循环中执行了Looper.loop()进行消息循环读取。这样就会导致后续所有的主线程消息都会走到我们这个loop方法中进行处理,也就是一旦发生了主线程崩溃,那么这里就可以进行异常捕获。同时因为我们写的是while死循环,那么捕获异常后,又会开始新的Looper.loop()方法执行。这样主线程的Looper就可以一直正常读取消息,主线程就可以一直正常运行了。

文字说不清楚的图片来帮我们:

同时之前CrashHandler的逻辑可以保证子线程也是不受崩溃影响,所以两段代码都加上,齐活了。

但是小光还不服气,他又想到了一种崩溃情况。。。

小光又又又一次实验

class Test2Activity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_exception)        throw RuntimeException("主线程异常")    }}

诶,我直接在onCreate里面给你抛出个异常,运行看看:

黑漆漆的一片~没错,黑屏了。

最后的对话(Cockroach库思想)

  • 看到这一幕,我主动找到了小光:

"这种情况确实比较麻烦了,如果直接在Activity生命周期内抛出异常,会导致界面绘制无法完成,Activity无法被正确启动,就会白屏或者黑屏了 这种严重影响到用户体验的情况还是建议直接杀死APP,因为很有可能会对其他的功能模块造成影响。或者如果某些Activity不是很重要,也可以只finish这个Activity。"

  • 小光思索地问: "那么怎么分辨出这种生命周期内发生崩溃的情况呢?"

"这就要通过反射了,借用Cockroach开源库中的思想,由于Activity的生命周期都是通过主线程的Handler进行消息处理,所以我们可以通过反射替换掉主线程的Handler中的Callback回调,也就是ActivityThread.mH.mCallback,然后针对每个生命周期对应的消息进行trycatch捕获异常,然后就可以进行finishActivity或者杀死进程操作了。"

主要代码:

                Field mhField = activityThreadClass.getDeclaredField("mH");        mhField.setAccessible(true);        final Handler mhHandler = (Handler) mhField.get(activityThread);        Field callbackField = Handler.class.getDeclaredField("mCallback");        callbackField.setAccessible(true);        callbackField.set(mhHandler, new Handler.Callback() {            @Override            public boolean handleMessage(Message msg) {                if (Build.VERSION.SDK_INT >= 28) {                //android 28之后的生命周期处理                    final int EXECUTE_TRANSACTION = 159;                    if (msg.what == EXECUTE_TRANSACTION) {                        try {                            mhHandler.handleMessage(msg);                        } catch (Throwable throwable) {                            //杀死进程或者杀死Activity                        }                        return true;                    }                    return false;                }                                //android 28之前的生命周期处理                switch (msg.what) {                    case RESUME_ACTIVITY:                    //onRestart onStart onResume回调这里                        try {                            mhHandler.handleMessage(msg);                        } catch (Throwable throwable) {                            sActivityKiller.finishResumeActivity(msg);                            notifyException(throwable);                        }                        return true;

代码贴了一部分,但是原理大家应该都懂了吧,就是通过替换主线程Handler的Callback,进行声明周期的异常捕获。

接下来就是进行捕获后的处理工作了,要不杀死进程,要么杀死Activity。

  • 杀死进程,这个应该大家都熟悉

  Process.killProcess(Process.myPid())  exitProcess(10)
  • finish掉Activity

这里又要分析下Activity的finish流程了,简单说下,以android29的源码为例。

    private void finish(int finishTask) {        if (mParent == null) {                        if (false) Log.v(TAG, "Finishing self: token=" + mToken);            try {                if (resultData != null) {                    resultData.prepareToLeaveProcess(this);                }                if (ActivityTaskManager.getService()                        .finishActivity(mToken, resultCode, resultData, finishTask)) {                    mFinished = true;                }            }         }     }            @Override    public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,            int finishTask) {        return mActivityTaskManager.finishActivity(token, resultCode, resultData, finishTask);    }

从Activity的finish源码可以得知,最终是调用到ActivityTaskManagerService的finishActivity方法,这个方法有四个参数,其中有个用来标识Activity的参数也就是最重要的参数--token。所以去源码里面找找token~

由于我们捕获的地方是在handleMessage回调方法中,所以只有一个参数Message可以用,那我么你就从这方面入手。回到刚才我们处理消息的源码中,看看能不能找到什么线索:

 class H extends Handler {        public void handleMessage(Message msg) {            switch (msg.what) {                case EXECUTE_TRANSACTION:                     final ClientTransaction transaction = (ClientTransaction) msg.obj;                    mTransactionExecutor.execute(transaction);                    break;                          }                }    }        public void execute(ClientTransaction transaction) {        final IBinder token = transaction.getActivityToken();        executeCallbacks(transaction);        executeLifecycleState(transaction);        mPendingActions.clear();        log("End resolving transaction");    }

可以看到在源码中,Handler是怎么处理EXECUTE_TRANSACTION消息的,获取到msg.obj对象,也就是ClientTransaction类实例,然后调用了execute方法。而在execute方法中。。。咦咦咦,这不就是token吗?

(找到的过于快速了哈,主要是activity启动销毁这部分的源码解说并不是今天的重点,所以就一笔带过了)

找到token,那我们就通过反射进行Activity的销毁就行啦:

    private void finishMyCatchActivity(Message message) throws Throwable {        ClientTransaction clientTransaction = (ClientTransaction) message.obj;        IBinder binder = clientTransaction.getActivityToken();              Method getServiceMethod = ActivityManager.class.getDeclaredMethod("getService");        Object activityManager = getServiceMethod.invoke(null);        Method finishActivityMethod = activityManager.getClass().getDeclaredMethod("finishActivity", IBinder.class, int.class, Intent.class, int.class);        finishActivityMethod.setAccessible(true);        finishActivityMethod.invoke(activityManager, binder, Activity.RESULT_CANCELED, null, 0);    }

"怎么让APP永不崩溃"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

线程 处理 方法 小光 消息 代码 程序 运行 进程 源码 循环 也就是 周期 处理器 就是 情况 应用 生命 问题 影响 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 山东特亿宝互联网科技有限公司 中国互联网科技股 互联网和科技创新 网络科技有限互联网 兼蜜互联网科技 互联网科技公司名称 金融科技和互联网金融 互联网科技素材视频竖屏 海南购全球互联网科技 南平创想时代互联网科技有限公司 互联网保险科技创业 科技类公司包括互联网吗 中国科技创新与世界互联网大会 保险科技服务互联网营销体系 深圳无忧互联网科技 广州百诚互联网科技有限公司官网 互联网与数字科技关系 银川新华互联网科技学校是国营吗 山东蛋芒互联网科技有限公司 广州全民伙伴互联网科技有限公司 互联网科技网 腾讯阿里互联网科技 金融科技与互联网金融 卓越科技(互联网 ) 成都互联网科技公司 腾讯科技 互联网女皇 中国互联网金融科技基金 开启互联网科技时代小说 互联网第三次科技革命特征 产业互联网科技服务公司
0