1. 程式人生 > >快來看看你過去處理異常Exception的方式是否足夠優雅?

快來看看你過去處理異常Exception的方式是否足夠優雅?

Android-Exception

背景介紹

我們每天都需要與各種個樣的異常打交到,但是我們對異常瞭解嗎?對其處理方式正確嗎?瞭解的話就算了,不瞭解的可以看看下面的內容。

開啟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;
  }
}

總結

現在我們對異常有了一定的瞭解,從現在開始,在程式設計過程中要開始注意對異常的處理藝術了。

如果你覺得不錯的話,麻煩動動小手點個贊,或者加個關注哦!你的鼓勵是我分享的動力。