Android進階;App的異常崩潰處理

安卓開發高階技術分享發表於2019-01-18

做任何軟體,都需要考慮異常情況的處理,這是軟體的可維護性的一部分。
異常崩潰是一種罕見的極端異常情況,這種情況下,針對終端使用者的UI反饋、事故裝置的資訊採集、向後臺維護人員的資料反饋等,都需要精心的設計。

UI反饋

  • 要做反饋,首先要抓到所有的異常崩潰。
    異常崩潰都是App程式的異常,每個App程式都執行在該App的Application中,所以我們可以在Application上集中抓到所有的程式異常:
    private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
    private Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() {
        if (uncaughtExceptionHandler == null) {
            uncaughtExceptionHandler = CrashHandler.getInstance(this,this);
        }
        return uncaughtExceptionHandler;
    }
    private void init(){
        Thread.setDefaultUncaughtExceptionHandler(getUncaughtExceptionHandler());
    }

我們抓到所有的程式異常,然後統一拋給一個CrashHandler類去處理,這個CrashHandler要實現UncaughtExceptionHandler介面:

public class CrashHandler implements UncaughtExceptionHandler{
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
    }
}
  • 盡力保持使用者資料的完整性,並設法恢復崩潰前的介面。
    如果要在崩潰時重啟App,就需要在退出App前,再次StartActivity
            Intent i = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName());
            i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            mContext.startActivity(i);
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(0);

其中,android.os.Process.killProcess(Dalvik虛擬機器方法)和System.exit(0)(常規java方法)起到的作用一樣。
這裡有一個關於Activity棧的陷阱,上面的程式碼可以重啟入口Activity,但是,如果這時你這個App的Activity棧裡還有其他的Activity,這些Activity是仍然存在的,不會被銷燬。
推薦的做法是在Application中維護一個Activity的列表,專門管理所有的Activity,在必要時,通過這個列表,去銷燬所有的Activity

    private ActivityStack stack;//擴充套件LinkList自定義一個activity列表
    //用set注入
    @Override
    public void initActivityStack(ActivityStack stack) {
        this.stack=stack;
    }
    //activity在oncreate時做新增//
    @Override
    public void addActivity(Activity activity) {
        if(stack!=null)stack.addStack(activity);
    }
    //activity在ondestroy時做刪減//
    @Override
    public void removeActivity(Activity activity) {
        if(stack!=null)stack.remove(activity);
    }
    @Override
    public void clearAll() {
        if(stack!=null)stack.clearAll();
    }

在這個自定義的activity列表裡,通過呼叫Activity的finish,來銷燬Activity

public void clearAll() {
        if(stack!=null&&stack.size()>0) {
            for (Activity activity : stack) {
                if (activity != null) {
                    activity.finish();
                }
            }
            stack.clear();
        }
    }

注意,為了避免Application持有Activity導致記憶體洩露,在Activity的生命週期裡不能只寫入列,還要記得寫出列。

  • 然後要提示使用者發生了一些事情,這裡要謹慎措辭,最好根據產品特性設計一些符合產品氣質的提示語,這裡就不展開了。
  • 最後,在自動重啟和退出App之間尋找平衡,比如第一次崩潰當然可以自動重啟,如果遇到特殊因素導致連續崩潰(如:介面問題或執行環境問題),就需要人為限制重啟的次數或頻率(比如,記錄上次自動重啟的時間,判斷兩次重啟的時間間隔是否過窄),避免成為使用者眼中的流氓軟體。

資訊採集

對異常崩潰瞭解的越多,就越容易處理它,所以我們要儘可能地採集相關資訊。
對研發來說,最有用的當然是異常程式碼行(如MainActivity第61行)和異常原因(如空指標異常),在研發環境裡,我們可以通過logcat讀到這些資訊,那沒什麼可說的,我們要考慮的是,如果異常崩潰發生在萬里之外的生產環境,我們要怎樣採集資訊。

  • 首先,要建立和儲存本地log資料夾,專門儲存這些資訊,一方面,網路不是一直可靠的,儲存到本地可以避免資料丟失;另一方面,無論是遠端資料上傳還是現場同僚手動拷貝,都需要有這樣一個資料夾。
  • 然後,要抓取異常程式碼行和異常原因,也就是你在logcat裡讀到的那些異常堆疊資訊,所有的Java異常都會丟擲一個Throwable,這裡面就能找到這些異常堆疊資訊(需要用printwriter去讀)
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();

        String result = writer.toString();
  • 最後,有些崩潰是在特定的軟硬體環境下出現的,我們需要知道這些環境資訊:
        Map<String, String> infos = new HashMap<String, String>();
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
            } catch (Exception e) {
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }

資料反饋

首選當然是通過後臺網路默默反饋(在WIFI環境下,避免消耗使用者流量)。
如果是以檔案為單位上傳反饋,只要做好鎖檔案和銷燬檔案即可。
如果是線上實時上傳反饋,就需要為每次崩潰編號,或根據編號依次上傳,或在後臺進行合併過濾。
需要注意的是,本地日誌檔案不能過大,如果超過一定大小限制,要有自動清理機制,比如刪除日期最早的那個檔案,是的,強烈建議根據日期來建立多個日誌檔案。

附錄;

附錄一;Android高階技術大綱

附錄二;Android進階實戰技術視訊

 

獲取方式;

加Android進階群;701740775。即可前往免費領取。免費備註一下csdn