在《程序的需求層次》這篇文章,我們可以看到程序可用性的重要性。當然,不讀這篇文章,我們也知道,它很重要。但是,可能沒覺得那么重要。在我寫過的所有程序里面,就有這么一個程序,由於前期不怎么重視異常處理,結果后期經常崩潰,導致公司所有人都對它不放心,即使后來已經改善了很多,也總會把它作為一個事提起(作為開發的我們,心里會很不爽的啊!)。更嚴重的是,程序崩潰可能丟失用戶數據,對用戶來說可能是災難性的。我也經常在下載App的時候,看用戶評論,里面有各種對閃退的恐懼、唾棄以及無奈。所以在這篇文章,我們主要看看在android 開發中,怎么避免程序崩潰。
在進入具體的討論之前,我想說說提高可用性的基本策略。我覺得可以從三個方面入手:
- 減錯
- 容錯
- 糾錯
減錯就是減少錯誤,這部分應該是大家做得最多的,我們一般都有專門的測試部門,來幫忙發現錯誤,然后更改,目的就是減少產品正式面市后的錯誤。容錯,就是在錯誤發生后,盡可能的讓程序其他部分正常運行的策略。今天,我們說的避免程序crash就是屬於這種類型。它主要有三個子策略:限定范圍、回滾、使用備份。最后一個是糾錯,這個大家用的就更少了,就是程序發現錯誤,然后自動把錯誤解決的策略。也包括三個主要的子策略:重試、檢查依賴、重置。廢話就這么多了,更具體的以后有時間再說。今天我們主要是使用限定范圍策略來和閃退say byebye啦。
首先,我們來看看哪些異常會導致App崩潰。我們知道java異常有兩種,一種是Error,一種是Exception。Error是我們自己處理不來的,一般是虛擬機錯誤或者內存錯誤。還好,這種錯誤發生的概率不是很大。更多是Exception, 並且是運行時異常,導致的程序掛掉(還有一種Exception是非運行時異常,這個一般我們都會被強制處理,不然編譯器會報錯,所以不會導致程序掛掉)。下面的程序就是模擬程序崩潰的App。
它有4個按鈕,前三個按鈕點擊后,會在UI線程、異步任務、自定義線程里向外拋數組越界異常(一種運行時異常)。
public void onExceptionBt(View view)
{
int[] array = new int[]{1,2,3};
int a1 = array[5]; //這里會拋出異常
}
結果都導致了程序崩潰。
第四個按鈕對應的是用try – catch語句捕獲UI線程里的異常,限制它的擴散。
public void onExceptionInCatchBt(View view) { try { onExceptionBt(view); } catch(Exception e) { ExceptionUtil.log(e, this); } }
結果,第四個按鈕點擊后,程序沒有崩潰,而是我們打印的出錯信息。
所以,我們的主要策略之一就是利用try – catch,限定異常范圍。
有人就說了,在UI線程的每個入口方法里寫上try-catch,是不是有點苦逼啊。的確是,為了簡化,我的建議是自定義一個Handler,重寫它的dispatchMessage方法,把try-catch放在里面。
package com.lanbeetou.android.avaiable; import android.content.Context; import android.os.Handler; import android.os.Message; public class ExceptionHandler extends Handler { private static ExceptionHandler handler; private Context context; private ExceptionHandler(Context context) { super(); this.context = context; } public static ExceptionHandler getExceptionHandler(Context context) { if(handler == null) { handler = new ExceptionHandler(context); } return handler; } @Override public void dispatchMessage(Message msg) { try { super.dispatchMessage(msg); } catch(Exception e) { ExceptionUtil.log(e, context); } } }
這樣,入口方法的調用都轉發給自定義的Handler,由Handler統一處理異常。
public void onExceptionBt(View view) { ExceptionHandler.getExceptionHandler(this).post(new Runnable() { @Override public void run() { int[] array = new int[]{1,2,3}; int a1 = array[5]; } }); }
還嫌麻煩?有更好的辦法?大家湊和着用吧。
好消息是異步操作或者自定義線程的異常捕獲,不用這么苦逼,可以通過Thread.setUncaughtExceptionHandler方法來設置默認的未捕獲異常處理方法。
package com.lanbeetou.android.avaiable; import java.lang.Thread.UncaughtExceptionHandler; import android.content.Context; public class UncaughtHandler implements UncaughtExceptionHandler { private Context context; public void init(Context context) { this.context = context; Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(Thread arg0, Throwable e) { ExceptionUtil.log(e, context); } }
設置成功后,比如說此時發生了一個異常,並且沒有捕獲,則該異常所在的線程會被終止,同時該異常處理方法會被調用。所以對於UI線程來說,這個方法不適用,不然UI線程被終止,和崩潰無異。而對應其他線程,雖然被停止,但是Ui線程還是活的,所以還有繼續工作的可能。如果這些其它線程也很重要,不能隨便終止,那么還是乖乖采用第一種方法吧。