Android 解决应用崩溃后重启的问题

在开发过程中,想必你也一定遇到过这样的问题,当我们的应用发生Crash时异常退出,然后又自动启动跳转到未知页面,此时应用在崩溃前保存的全局变量被重置,用户状态丢失,显示数据错乱。更让我们头疼的是,这种崩溃后重启的情况,并不是每次都会遇到,那么究竟是因为什么呢?

经测试,在 Android 的 API 21 ( Android 5.0 ) 以下,Crash 会直接退出应用,但是在 API 21 ( Android 5.0 ) 以上,系统会遵循以下原则进行重启:

  • 包含 Service,如果应用 Crash 的时候,运行着Service,那么系统会重新启动 Service。
  • 不包含 Service,只有一个 Activity,那么系统不会重新启动该 Activity。
  • 不包含 Service,但当前堆栈中存在两个 Activity:Act1 -> Act2,如果 Act2 发生了 Crash ,那么系统会重启 Act1。
  • 不包含 Service,但是当前堆栈中存在三个 Activity:Act1 -> Act2 -> Act3,如果 Act3 崩溃,那么系统会重启 Act2,并且 Act1 依然存在,即可以从重启的 Act2 回到 Act1。

看了上述解释,我们终于知道应用在什么种情况下才会重启。

面对这样的问题,我们提供两种解决思路,一是允许应用自动重启,并在重启时恢复应用在崩溃前的运行状态。二是禁止应用自动重启,而是让用户在应用发生崩溃后自己手动重启应用。

本文主要提供禁止应用自动启动的思路和代码实现。
网上有很多开源的库供大家选择,但个人觉得一个类就可以解决的问题,没必要再引入依赖到项目中。

获取应用Crash时的回调

Android提供一个UncaughtExceptionHandler的接口,该接口在应用发生Crash时,会回调接口中的uncaughtException方法。

因此我们可以构建一个类,继承UncaughtExceptionHandler接口,并覆写uncaughtException方法,在覆盖方法中处理Crash问题并退出应用。

class CrashCollectHandler : Thread.UncaughtExceptionHandler {

    //当UncaughtException发生时会转入该函数来处理
    override fun uncaughtException(t: Thread?, e: Throwable?) {
        if (!handleException(e) && mDefaultHandler!=null){
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler?.uncaughtException(t,e)
        }else{
            try {
                //给Toast留出时间
                Thread.sleep(2000)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            //退出程序
            android.os.Process.killProcess(android.os.Process.myPid())
            System.exit(0)

        }

    }
}

handleException方法主要是为了弹出Toast和收集crash信息

    fun handleException(ex: Throwable?):Boolean {
        if (ex == null){
            return false
        }
        Thread{
            Looper.prepare()
            toast("很抱歉,程序出现异常,即将退出")
            Looper.loop()
        }.start()
        //收集设备参数信息
        collectDeviceInfo(mContext);
        //保存日志文件
        saveCrashInfo2File(ex);
        // 注:收集设备信息和保存日志文件的代码就没必要在这里贴出来了
        //文中只是提供思路,并不一定必须收集信息和保存日志
        //因为现在大部分的项目里都集成了第三方的崩溃分析日志,如`Bugly` 或 `啄木鸟等`
        //如果自己定制化处理的话,反而比较麻烦和消耗精力,毕竟开发资源是有限的
        return true
    }

设置程序的默认处理器

    fun init(pContext: Context) {
        this.mContext = pContext
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        // 设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this)
    }

最后在Application中调用并初始化

    class APP:Application{
        override fun onCreate() {
            super.onCreate()
            /**捕获Crash,解决程序崩溃后启动的问题*/
            CrashCollectHandler.instance.init(this)
        }
    }
   

如果按照上边的代码执行,你会发现应用依然会在崩溃时重启。 (看到这里,心里已经开始骂娘了,写的什么鸟博客,完全解决不了我的问题啊。 )

别急,此刻你的内心和当初的我是一模一样的。

为了让你印象深刻, 请继续往下边看。

退出栈内所有的Acitvity

如果应用在发生崩溃时,回退栈内依然存在没有退出的Activity,即使调用了System.exit(0)方法,应用依然会自动重启。因此我们就需要在应用退出之前,先清除栈内所有的Activity。

在Application中定义一个存储Activity的list,并定义三个管理Activity集合的方法。

class App:Application{
    fun addActivity(activity: Activity) {
        if (!activityList.contains(activity)) {
            activityList.add(activity)
        }
    }

    fun removeActivity(activity: Activity) {
        if (activityList.contains(activity)) {
            activityList.remove(activity)
        }
    }

    fun removeAllActivity() {
        activityList.forEach {
            if (it != null) {
                it.finish()
            }
        }
    }
}

在BaseActivity中执行管理Activity集合的方法。

open class BaseActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        App.i.addActivity(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        App.i.removeActivity(this)
    }
}

在上边我们已经学习过的方法uncaughtException中添加退出所有Activity的方法

class CrashCollectHandler : Thread.UncaughtExceptionHandler {

    //当UncaughtException发生时会转入该函数来处理
    override fun uncaughtException(t: Thread?, e: Throwable?) {
            ...
            //退出程序
            App.i.removeAllActivity()
            android.os.Process.killProcess(android.os.Process.myPid())
            System.exit(0)
            ...
        }

    }
}

OK,大功告成,跑一下程序试试,果然在应用崩溃后不会发生再次启动应用的情况。

文中使用的是Kotlin语言,如果你使用的编译语言是Java,把上述代码转化为java执行即可。

最后

最后再贴一下完整的CrashCollectHandler类:

class CrashCollectHandler : Thread.UncaughtExceptionHandler {
    var mContext: Context? = null
    var mDefaultHandler:Thread.UncaughtExceptionHandler ?=null
    
    companion object {
        val instance by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { CrashCollectHandler() }
    }
    
    fun init(pContext: Context) {
        this.mContext = pContext
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()
        // 设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this)
    }
    
    //当UncaughtException发生时会转入该函数来处理
    override fun uncaughtException(t: Thread?, e: Throwable?) {
        if (!handleException(e) && mDefaultHandler!=null){
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler?.uncaughtException(t,e)
        }else{
            try {
                //给Toast留出时间
                Thread.sleep(2000)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            //退出程序
            App.i.removeAllActivity()
            android.os.Process.killProcess(android.os.Process.myPid())
            System.exit(0)

        }

    }
    
      fun handleException(ex: Throwable?):Boolean {
        if (ex == null){
            return false
        }
        Thread{
            Looper.prepare()
            toast("很抱歉,程序出现异常,即将退出")
            Looper.loop()
        }.start()
        //收集设备参数信息
        //collectDeviceInfo(mContext);
        //保存日志文件
        //saveCrashInfo2File(ex);
        // 注:收集设备信息和保存日志文件的代码就没必要在这里贴出来了
        //文中只是提供思路,并不一定必须收集信息和保存日志
        //因为现在大部分的项目里都集成了第三方的崩溃分析日志,如`Bugly` 或 `啄木鸟等`
        //如果自己定制化处理的话,反而比较麻烦和消耗精力,毕竟开发资源是有限的
        return true
    }
}

·
·
·
·
·

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容

  • Android Studio JNI流程首先在java代码声明本地方法 用到native关键字 本地方法不用去实现...
    MigrationUK阅读 11,829评论 7 123
  • 1.什么是Activity?问的不太多,说点有深度的 四大组件之一,一般的,一个用户交互界面对应一个activit...
    JoonyLee阅读 5,723评论 2 51
  • 2018年Android 面试题 IT开发仔2018-03-21 15:26:46 在这“金三银四”的季节,我准备...
    王培921223阅读 2,502评论 3 24
  • 【Android Service】 Service 简介(★★★) 很多情况下,一些与用户很少需要产生交互的应用程...
    Rtia阅读 3,134评论 1 21
  • 一头是荒芜的沙漠 一头是万物生长的人心 我在这头,连接着那头 后来你来这走了一遭 万物生长的人心 被吞没在荒芜的沙漠
    春天里的流浪汉阅读 338评论 0 0