Android 性能優化(三)認識錯誤Error和異常Exception及棧軌跡StackTrace

目錄

一、定義

throwable

exception

error

二、異常類型

三、異常處理5個關鍵字 

try

catch

throw

throws

finally

四、模擬面試

面試題1:try語句可以嵌套嗎?

面試題2:下面代碼有問題嗎?

面試題3:try塊中加退出語句return會怎樣?

五、異常的棧軌跡(Stack Trace)

1、printStackTrace()

2、getStackTrace()方法

 3、fillInStackTrace()


 前言

Android 性能優化(一)閃退治理、卡頓優化、耗電優化、APK瘦身 ,這篇中我強調“運行穩定大於一切”,保證程序運行中不出現Crash,要比卡頓、耗電、安裝包大小等方面更爲重要。

當一個方法發生錯誤時,此方法會產生一個對象並將其交給運行時系統。 這個對象就叫異常對象,它包含了錯誤信息、異常類型以及程序的狀態。創建一個異常對象並將其交給運行時系統稱之爲拋出異常。造成Crash的原因有很多,而在程序運行過程中Throwable拋出的異常或錯誤就是其中最普遍的成因。本篇將對Throwable下的Exception和error有一個比較清晰的認識。


一、定義

  • throwable

可拋出的意思,是根基類,子類有異常exception和錯誤error。在java中只有Throwable類型的實例才能被拋出(throw)或者捕獲(catch),它是異常處理機制的基本類型。

  • exception

表示程序運行中出現的非正常狀態,並告訴我們程序發生了什麼問題,且程序自身可以進行攔截或處理的異常。

  • error

是指程序無法處理的錯誤,其中包括程序運行時 JVM出現的問題。


二、異常類型

Throwable分爲檢查型異常(Checked Exception)和非檢查型異常(Unchecked Exception)。

  • 檢查型異常必須在源碼中進行try-catch捕獲處理,這是編譯檢查的一部分。
  • 類似NullPointerException,ArrayIndexOfBoundException就是非檢查型異常,通常是可以通過編碼避免的邏輯錯誤。
  • 編譯期不檢查,如果拋出了非檢查型異常,那就是編碼邏輯有問題,需要開發者解決。


三、異常處理5個關鍵字 

異常處理過程:一般情況下是用try來執行一段程序,如果系統會拋出(throw\throws)一個異常對象,可以通過它的類型來捕獲(catch)它,或通過總是執行代碼塊(finally)來處理。

需要注意:try-catch代碼段會產生額外的性能開銷,Java每實例化一個Exception,都會對當時的棧進行快照。

try

  • 用來指定一段可能產生異常的代碼塊;是一個“監控區域”的概念。

catch

  • 緊跟在try塊後面,用來指定你想要捕獲的異常的類型;

throw

  • 語句用來明確地拋出一個異常;

throws

  • 用來聲明一個方法可能拋出的各種異常;

finally

  • 爲確保一段代碼不管發生什麼異常狀況都要被執行;

四、模擬面試

 

面試題1:try語句可以嵌套嗎?

答:可以的。每當遇到一個try語句,指定的一段可能產生異常的代碼塊就會被放入異常棧中,直到所有的try語句都完成。如果下一級的try語句沒有對某種異常進行處理(catch),異常棧就會執行出棧操作,交給上一層處理,直到遇到有處理這種異常的try語句或者最終將異常拋給JVM。

 

面試題2:下面代碼有問題嗎?

try {
     Thread.sleep(500L);
} catch (Exception e) {
}

答:這段代碼違反了異常處理的兩個基本原則。  第一,儘量不要捕獲類似Exception這樣的通用異常,而是應該捕獲特定異常。例如,這裏Thread.sleep() 應當捕獲 InterruptedException。第二,在異常處理(catch)中不要生吞(swallow)異常。這是特別注意的事情,因爲很可能會導致非常難以診斷的詭異情況。

 

面試題3:try塊中加退出語句return會怎樣?

  try塊中有return語句時,仍然會執行finally塊中的語句,然後方法再返回。

 


五、異常的棧軌跡(Stack Trace)

參考:Java異常的棧軌跡(Stack Trace) - wawlian - 博客園

1、printStackTrace()

  Exception類本身除了定義了幾個構造器之外,所有的方法都是從其父類Throwable繼承過來的,而且和異常相關的方法都是從父類繼承過來的。其中就有一個 printStackTrace() 方法。

這個方法會將Throwable對象的棧軌跡信息打印到標準錯誤輸出流上。輸出的大體樣子如下:

java.lang.NullPointerException
         at MyClass.mash(MyClass.java:9)
         at MyClass.crunch(MyClass.java:6)
         at MyClass.main(MyClass.java:3)

輸出的第一行是toString()方法的輸出,後面幾行的內容都是之前通過fillInStackTrace()方法保存的內容。

 

2、getStackTrace()方法

    對printStackTrace()方法所打印信息進行訪問。它會返回一個棧軌跡元素的數組 StackTraceElement[]。

    下面是一個使用getStackTrace()訪問這些軌跡棧元素並打印輸出的例子:

public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出問題啦!");
    }
    public static void g() throws Exception{
        f();
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            System.out.println("e.printStackTrace:");
            e.printStackTrace();
       
            System.out.println("e.getStackTrace:");
            for(StackTraceElement elem : e.getStackTrace()) {
                System.out.println(elem);
            }
        }
    }
}

getStackTrace()的輸出和printStackTrace()的輸出基本上是一樣的,如下: 

e.printStackTrace:
java.lang.Exception: 出問題啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:6)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:10)

e.getStackTrace:
TestPrintStackTrace.f(TestPrintStackTrace.java:3)
TestPrintStackTrace.g(TestPrintStackTrace.java:6)
TestPrintStackTrace.main(TestPrintStackTrace.java:10)

 

 3、fillInStackTrace()

我們在前面也提到了這個方法。要說清楚這個方法,首先要講一下捕獲異常之後重新拋出的問題。

首先,在catch代碼塊中捕獲到異常,調用printStackTrace()打印棧軌跡,又重新throw出去。在上一級的方法調用中,再捕獲這個異常並且打印出棧軌跡信息,這兩個棧軌跡信息會一樣嗎?我們看一下代碼:

public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出問題啦!");
    }
    public static void g() throws Exception{
        try {
            f();
        }catch(Exception e) {
            e.printStackTrace();
            throw e;
        }
         
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}

在main方法中調用g()方法,並且捕獲拋出的異常。此時 f()方法拋出異常“出問題啦!”,按理說兩次打印棧軌跡的信息應該不同,第二次打印的信息應該沒有關於f()的信息。輸出結果如下:事實上,兩次打印棧軌跡信息是一樣的!

java.lang.Exception: 出問題啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:16)
java.lang.Exception: 出問題啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:16)

因此,當捕獲到異常立即拋出,在上級方法調用中再次捕獲這個異常,兩次打印的棧軌跡信息是一樣的。

因爲,沒有將當前線程當前狀態下的軌跡棧的狀態保存進Throwabe中。而fillInStackTrace()方法剛好做的就是這樣的保存工作。

public Throwable fillInStackTrace()

可見,fillInStackTrace()方法的返回值是保存了當前棧軌跡信息的Throwable對象。g()方法代碼改動如下:

  public static void g() throws Exception{
        try {
            f();
        }catch(Exception e) {
            e.printStackTrace();
            throw (Exception)e.fillInStackTrace();
        }
    }

輸出結果:

java.lang.Exception: 出問題啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:17)
java.lang.Exception: 出問題啦!
    at TestPrintStackTrace.g(TestPrintStackTrace.java:11)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:17)

 我們看到,在main方法中打印棧軌跡已經沒有了f()相關的信息了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章