天天看點

Android中ANR的監測與定位

一、原理

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。

Android中ANR的監測與定位

代碼下載下傳戳這裡