2.2 異常處理
Ian Darwin
2.2.1 問題
Java有一個精心定義的異常處理機制,但是需要花費一定的時間學習,才能高效地使用而不至于使用戶或者技術支持人員感到沮喪。
2.2.2 解決方案
Java提供了一個Exception層次結構,正確地使用它能夠帶來相當大的靈活性。Android提供了包括對話框和Toast的多種機制,用于通知用戶錯誤的情況。Android開發者應該熟悉這些機制,并學習高效使用它們的方法。
2.2.3 討論
Java從推出時就有兩類異常(實際上是Exception類的父類Throwable):檢測型(checked)異常和非檢測型(unchecked)異常。在Java標準版中,編程人員明顯要面對這樣一個事實:在編譯的時候可以檢測到某些情況,但其他的情況則無法檢測。例如,如果許多PC上安裝一個桌面應用程序,可能有些PC上的磁盤空間已經很緊張,無法保存數據;與此同時,其他PC上應用程序依賴的一些文件可能由于用戶的錯誤(而非編程人員的錯誤)、偶發事件、老鼠咬斷電纜等情況而丟失。因此,IOException被當作“檢查型異常”,意味著編程人員必須檢查這類異常,檢查可以通過文件使用方法中的try-catch子句或者方法定義中的throws子句來完成。所有經過良好訓練的Java開發人員都知道如下的通用規則:
Throwble是可拋出異常層次結構的根。Exception及其子類(RuntimeException(及其子類)除外)都是檢測型異常。其他異常為非檢測型異常。
上述規則意味著,Error及其所有子類都是非檢測異常(見圖2-1)。例如,如果你收到一個VMError異常,說明出現了一個運行時bug,作為應用程序編程人員,對此你沒有什么可做的。RuntimeException子類包含了名稱超長的ArrayIndexOutOfBoundsException等異常,它和它的友元都是非檢測型異常,因為在開發時測試及捕捉這些異常是你的責任(參見第3章)。
捕捉異常的場所
早期對檢測型異常的(過度)使用導致許多早期Java開發人員編寫的代碼中到處都是try/catch代碼塊,部分原因是:在早期的一些培訓項目和書籍中,對throws子句的使用沒有得到足夠的重視。隨著Java本身越來越多地轉向企業應用,較新的框架(如Hibernate和Spring)出現并強調非檢測型異常的使用,這類問題才得到改正。現在,盡可能捕捉靠近用戶的異常這一理念已經得到普遍的接受。準備(在程序庫或者多個應用程序中)重用的代碼不應該嘗試錯誤處理,它們所能做的是所謂“異常轉譯”(Exception Translation),也就是說,將與技術相關的(通常是檢查型)異常轉換為通用的非檢測型異常。例2-1展示了基本的模式。
例2-1:異常轉譯
public String readTheFile(String f) {BufferedReader is = null;try {is = new BufferedReader(new FileReader(f));String line = is.readLine();return line;} catch (FileNotFoundException fnf) {throw new RuntimeException("Could not open file " + f, fnf);} catch (IOException ex) {throw new RuntimeException("Could not read file " + f, ex);} finally {if (is != null) {try {is.close();} catch(IOException grr) {throw new RuntimeException("Error on close of " + f, grr);}}}
}
注意,即便在這個代碼中,檢測型異常的用法也很零亂:is.close()實際上不可能失敗,但是因為將它放在finally塊中(如果文件打開但是出現了某種錯誤,這樣可以確保文件關閉),就必須使用另一個try-catch結構。因此,檢測型異常(很可能)是不好的,在新的API中應該避免使用它,在必要時應該用非檢測型異常來掃清道路。
Oracle官方網站和其他人則支持相反的看法。在本書網站上的一條評論中,Al Sutton提出了如下看法:
檢測型異常迫使開發人員承認錯誤的情況可能發生,他們必須思考處理錯誤的方式。在許多情況下,除了日志和恢復可能沒有太多的事情可做,但是開發人員仍然考慮到此類錯誤發生的時候所應采取的措施。這個例子說明阻止方法調用者區分文件不存在(因而沒有必要重新讀取)和文件讀取問題(文件存在但是無法讀取)這兩種不同錯誤情況之間的區別。
Android忠于JavaAPI,有許多檢測型異常(包括例子中說明的那些),因此對這些異常應該以相同的方式處理。
對異常的處理
應用程序應該始終報告異常。當我看到捕捉的異常卻不采取任何措施的代碼時,我總會感到絕望。但是,異常只應該報告一次(不要既進行記錄,又轉譯/重新拋出異常)。普通異常的重點是表示異常情況(正如名稱所表示的那樣)。因為在Android設備上沒有系統管理員或者控制臺操作員,所以必須向用戶報告異常情況。
你應該考慮通過一個對話框或者Toast通知報告異常。移動設備上的異常處理與臺式機有所不同。用戶可能正在開車(或者操作其他機器),與人交談,因此你不應該猜測他們的注意力都在應用程序上。記住,Toast通知只在屏幕上出現幾秒鐘,你可能會錯過它。如果用戶必須采取行動更正錯誤,就應該使用對話框。我知道大部分的示例(甚至在本書中也是一樣)使用Toast,這是因為Toast通知需要的代碼比對話框少(相反,BlackBerry API簡化了對話框:Dialog.alert("mes sage"))。Toast簡單地彈出,然后消失。對話框則要求用戶確認異常情況,或者授權應用程序執行某些付費操作(例如,啟動互聯網訪問,以便運行需要下載地圖數據的應用程序)。
注意: 使用Toast來“彈出”不重要的信息,而使用對話框顯示重要的信息并得到確認。