一、原理
1. ANR監測原理
判斷ANR的方法其實很簡單,我們在子線程裡向主線程發消息,如果過了固定時間後,消息仍未處理,則說明已發生ANR了。
看懂了直接看2,沒看懂繼續看。
Android應用程式的所有互動操作和響應,都是通過主線程的消息機制來進行的。例如當使用者點選了某個Button,系統會向主線程發送消息,主線程的Looper從主線程消息隊列中取出消息并處理,處理完目前消息,主線程Looper再去取出下一個消息。當主線程做了耗時的任務,主線程的Looper就無法從消息隊列中取出新的消息,所表現出的就是程式卡頓,甚至是ANR。同理,我們在子線程往主線程發送一個消息,要是消息無法得到及時處理,那說明程式發生ANR了。
2. 定位耗時操作的原理
當程式ANR後,我們可以通過主線程Looper拿到主線程Thread,然後通過getStackTrace拿到主線程目前的調用棧,進而定位到發生ANR的地方,定位到耗時操作。
二、代碼實作
1. 首先我們定義一個線程,用來監測主線程。
在該線程中,我們首先給主線程發送消息,然後睡眠指定時間,之後監測消息是否被處理,若未被處理,則抛出ANR異常。
為什麼叫ANRWatchDog:了解嵌入式的人對看門狗應該很熟悉,在嵌入式中,看門狗定時器在程式跑飛時,可定時複位程式,而我們必須定期喂狗(将定時器清零),表示程式正常運作。
watchDogHandler:用來給主線程發送消息,并處理消息。
lastTimeTick/timeTick:用來判斷消息是否被處理
public class ANRWatchDog extends Thread {
public static final int MESSAGE_WATCHDOG_TIME_TICK = 0;
/**
* 判定Activity發生了ANR的時間,必須要小于5秒,否則等彈出ANR,可能就被使用者立即殺死了。
*/
public static final int ACTIVITY_ANR_TIMEOUT = 2000;
private static int lastTimeTick = -1;
private static int timeTick = 0;
private Handler watchDogHandler = new android.os.Handler() {
@Override
public void handleMessage(Message msg) {
timeTick++;
timeTick = timeTick % Integer.MAX_VALUE;
}
};
@Override
public void run() {
while (true) {
watchDogHandler.sendEmptyMessage(MESSAGE_WATCHDOG_TIME_TICK);
try {
Thread.sleep(ACTIVITY_ANR_TIMEOUT);
} catch (InterruptedException e) {
e.printStackTrace();
}
//如果相等,說明過了ACTIVITY_ANR_TIMEOUT的時間後watchDogHandler仍沒有處理消息,已經ANR了
if (timeTick == lastTimeTick) {
throw new ANRException();
} else {
lastTimeTick = timeTick;
}
}
}
}
2. 啟動這個線程
為了確定該線程在程式啟動後第一時間運作,是以自定義一個Application,在onCreate中開啟這個線程。
public class MyApplication extends Application {
@Override
public void onCreate() {
new ANRWatchDog().start();
super.onCreate();
}
}
3. 定義發生ANR時的操作,這裡是自定義了一個異常,ANR時抛出。
public class ANRException extends RuntimeException {
public ANRException() {
super("應用程式無響應,快來改BUG啊!!");
Thread mainThread = Looper.getMainLooper().getThread();
setStackTrace(mainThread.getStackTrace());
}
}
4. 在Activity中模拟耗時操作
在我的demo中是直接Thread.sleep(10000),讓主線程睡10秒。
5. Manifest檔案中,注冊Activity,配置Application
三、運作結果
程式過了幾秒後抛出了ANRException,如下圖所示。箭頭指的地方就是産生ANR的地方(耗時操作),在本程式中是Thread.sleep。
代碼下載下傳戳這裡