Android——Exception異常的正確開啟方式
背景介紹
我們每天都需要與各種個樣的異常打交到,但是我們對異常瞭解嗎?對其處理方式正確嗎?瞭解的話就算了,不瞭解的可以看看下面的內容。
開啟Exception
Exception的分類
先來看看下面這張圖:
從圖中可以看出:
1. Error(錯誤)和Exception(異常)都繼承自Throwable類,我們重點關注Exception;
2. 異常類分為檢查異常(直接繼承自Exception,除RuntimeException)和可不檢查異常(Error和繼承自RuntimeException的);
throw和throws的區別
- throw通常在程式碼片段中用於直接丟擲異常。
public void test(){
throw new ClassNotFoundException();
}
- throws用在方法簽名上,可丟擲多種異常
public void test() throws IOException, ArrayIndexOutOfBoundsException{
do something...
}
自定義異常
只需要繼承Exception就可以了。
public class CustomException extends Exception{
public CustomException(String exceptionInfo){
super (exceptionInfo);
}
}
//使用CustomExeption
public void test(){
throw new CustomException("丟擲了自定義異常");
}
try-catch-finally
我們都很熟悉使用try-catch-finally去捕獲異常,但現在值得思考一下我們之前寫的try-catch-finally是否正確了。
Code Clean 指出,try-catch-finally程式碼塊在程式中定義了一個範圍,我們應該讓它的語意更明確,所以不應該把大段的程式碼放在其中,而應該抽離出來。來看看下面這個例子。
//這個方法中只定義了try-catch,而真正的操作放到deletePagerAndAllReference()中進行
public void delete(){
try{
deletePagerAndAllReference();
} catch (Exception e){
Log.e(e);
}
}
public void deletePagerAndAllReference() throws Exception{
//do delete
}
需要注意,catch中的return語句會在finally執行完成後才會被執行。
關於方法返回null的討論
在Code Clean 中,鮑勃大叔嚴厲批評了return null 這種駭人聽聞的做法,這讓程式中充滿了類似obj != null 的判斷。他建議在可能返回null的地方使用丟擲異常,或者直接返回一種特例情況。例如下面這樣:
List<Employee> employees = getEmployees();
if(employees != null){
// doSomething...
}
由於getEmployees()可能返回null值,所以我們不得不每次呼叫的時候都去檢查是否為null,但如果做如下更改:
public List<Employee> getEmployees(){
if(..there are no employees..){
return Collections.emptyList();
//沒有資料返回一個空的List,呼叫時就不必去檢查它是否為空了
}
}
或者像下面這樣:
public List<Employee> getEmployees(){
if(..there are no employees..){
throw new NullPointerException("嘿!List<Employee>不能為null,仔細檢查下吧!");
//沒有資料返回一個空的List,直接丟擲異常,讓呼叫者們知道,這個地方存在錯誤,不該讓List<Employee>為null的。
}
}
Android中處理未捕獲異常,並上報異常資訊
在我們的應用中,可能存在一些我們沒有捕獲的異常,對於這些異常,我們可以把它儲存下來,然後進行分析。來看看怎麼做。
首先我們需要implements Thread.UncaughtExceptionHandler 實現自己的異常處理類,然後呼叫* Thread.setDefaultUncaughtExceptionHandler()* 方法把我們的異常處理器設定到系統中,這樣有為捕獲的異常出現時,就能被我們自己處理了。
當然,有一個重要的方法需要重寫uncaughtException() 。下面看看完整例子。
public class CrashHandler implements UncaughtExceptionHandler {
private static final CrashHandler mInstance = new CrashHandler();
private UncaughtExceptionHandler mDefualtCrashHandler;
private Context mContext;
/**
* 防止被重複建立
*/
private CrashHandler() {}
public static CrashHandler getInstance() {
return mInstance;
}
public void init(Context context) {
mContext = context.getApplicationContext(); // 確保獲得的是系統級的Context
mDefualtCrashHandler = Thread.getDefaultUncaughtExceptionHandler(); // 獲取系統預設的異常處理器
Thread.setDefaultUncaughtExceptionHandler(this); // 把當前例項設定為系統預設異常處理器
}
/**
* 這個方法是我們重寫的重點,當系統出現未捕獲異常時,就會呼叫這個方法
*
* @param t 出現未捕獲異常的執行緒
* @param e 未捕獲的異常
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
saveExceptionToFile(e);
} catch (Exception ex) {
ex.printStackTrace();
}
if (mDefualtCrashHandler != null) {
//如果系統有預設異常處理就使用它處理
mDefualtCrashHandler.uncaughtException(t, e);
} else {
//否則我們自行結束程式
android.os.Process.killProcess(Process.myPid());
}
}
private void saveExceptionToFile(Throwable e) throws IOException{
if (FileUtils.ExistSDCard()){
long currentTime = System.currentTimeMillis();
String crashTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(currentTime);
File file = new File(FileUtils.getAppCrashDir()+"crash" + crashTime + ".txt");
file.createNewFile();
try{
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
pw.println(crashTime);
printPhoneInfo(pw);
pw.println();
e.printStackTrace(pw); //輸出錯誤資訊
pw.close();
} catch (Exception ex){
ex.printStackTrace();
}
}
}
/**
* 輸出手機資訊
*/
private void printPhoneInfo(PrintWriter pw) throws PackageManager.NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES);
pw.print("App version: ");
pw.print(pi.versionName);
pw.print("_");
pw.println(pi.versionCode);
//android版本號
pw.print("OS Version: ");
pw.print(Build.VERSION.RELEASE);
pw.print("_");
pw.println(Build.VERSION.SDK_INT);
//製造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);
//手機型號
pw.print("Model: ");
pw.println(Build.MODEL);
//cpu架構
pw.print("CPU ABI: ");
pw.println(Build.CPU_ABI);
}
}
看看怎麼使用。
public class IceApplication extends MultiDexApplication {
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = this;
//初始化異常處理類,這樣我們的異常類就生效了
CrashHandler.getInstance().init(context);
}
public static Context getAppContext(){
return context;
}
}
總結
現在我們對異常有了一定的瞭解,從現在開始,在程式設計過程中要開始注意對異常的處理藝術了。