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
    }
}

·
·
·
·
·

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android Studio JNI流程首先在java代碼聲明本地方法 用到native關鍵字 本地方法不用去實現...
    MigrationUK閱讀 11,932評論 7 123
  • 1.什么是Activity?問的不太多,說點有深度的 四大組件之一,一般的,一個用戶交互界面對應一個activit...
    JoonyLee閱讀 5,754評論 2 51
  • 2018年Android 面試題 IT開發仔2018-03-21 15:26:46 在這“金三銀四”的季節,我準備...
    王培921223閱讀 2,541評論 3 24
  • 【Android Service】 Service 簡介(★★★) 很多情況下,一些與用戶很少需要產生交互的應用程...
    Rtia閱讀 3,168評論 1 21
  • 一頭是荒蕪的沙漠 一頭是萬物生長的人心 我在這頭,連接著那頭 后來你來這走了一遭 萬物生長的人心 被吞沒在荒蕪的沙漠
    春天里的流浪漢閱讀 354評論 0 0