Watchdog是什麼
Watchdog是android framework中一個java類(線上源碼), 也可以認為是一種系統檢查和處理的機制。比如在Android系統中,SystemServer程序會對應用程式進行卡頓檢測和處理(即ANR檢測等),那麼誰來檢測和處理SystemServer程序的服務呢?如果AMS、PMS等核心服務出現了卡死誰來處理呢?答案是Watchdog,在Android中,設計了Watchdog機制來檢測和處理系統核心服務是否能正常工作。
Watchdog怎麼工作
Watchdog作為系統服務的檢查者,在系統啟動過程的較早階段就已經啟動了。
啟動Watchdog
#SystemServer.java
private void startBootstrapServices() {
// 目前方法在android系統啟動後首先調用,主要作用是啟動系統基礎服務,而Watchdog是在啟動基礎服務之前就啟動了
final Watchdog watchdog = Watchdog.getInstance();
watchdog.start();
//.....
}
#Watchdog.java
public class Watchdog extends Thread {
static Watchdog sWatchdog;
static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000;
final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<>();
final HandlerChecker mMonitorChecker;
public static Watchdog getInstance() {
if (sWatchdog == null) {
//單例模式,說明Watchdog在SystemServer程序内隻有一個執行個體
sWatchdog = new Watchdog();
}
return sWatchdog;
}
private Watchdog() {
super("watchdog");
//為每一個我們想檢測的線程初始化HandlerChecker對象,該對象是Runnable的子類
// 注意,此處并不會檢測背景線程,因為背景線程可能會運作很長時間,無法保證及時性
// 建立螢幕檢查者,這裡加入的是前台線程的Handler, DEFAULT_TIMEOUT為60s
mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
"foreground thread", DEFAULT_TIMEOUT);
//将螢幕檢查者加入隊列
mHandlerCheckers.add(mMonitorChecker);
// 添加主線程的檢查者
mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
"main thread", DEFAULT_TIMEOUT));
// 添加UI線程檢查者
mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
"ui thread", DEFAULT_TIMEOUT));
// 添加IO線程檢查者
mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
"i/o thread", DEFAULT_TIMEOUT));
// 添加Display線程檢查者
mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
"display thread", DEFAULT_TIMEOUT));
// 添加動畫線程檢查者
mHandlerCheckers.add(new HandlerChecker(AnimationThread.getHandler(),
"animation thread", DEFAULT_TIMEOUT));
// 添加視窗動畫線程檢查者
mHandlerCheckers.add(new HandlerChecker(SurfaceAnimationThread.getHandler(),
"surface animation thread", DEFAULT_TIMEOUT));
// 建立并添加Binder線程檢查者
addMonitor(new BinderThreadMonitor());
//檔案相關
mOpenFdMonitor = OpenFdMonitor.create();
}
public void addMonitor(Monitor monitor) {
synchronized (this) {
mMonitorChecker.addMonitorLocked(monitor);
}
}
}
整理下Watchdog的建立源碼,其重點如下:
- Watchdog繼承自Thread,本質上是線程
- 在Watchdog是單例,該對象建立時添加了7個HandlerChecker
- 将BinderThreadMonitor加入了mMonitorChecker對象
那麼HandlerChecker和Monitor到底是個什麼東西呢 ?
#Watchdog.java
public final class HandlerChecker implements Runnable {
private final Handler mHandler; //目前檢查者工作使用的Handler
private final String mName; //目前檢查者名字
private final long mWaitMax; //最大等待時間
private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();
private boolean mCompleted; //目前檢查是否已完成
private Monitor mCurrentMonitor; //目前正在執行的監控器
private long mStartTime; //檢測的開始時間
private int mPauseCount; //暫停次數
HandlerChecker(Handler handler, String name, long waitMaxMillis) {
mHandler = handler;
mName = name;
mWaitMax = waitMaxMillis;
mCompleted = true;
}
void addMonitorLocked(Monitor monitor) {
//這裡先将監控器添加到mMonitorQueue集合而不是mMonitors集合,主要是為了安全,不想在mMonitors集合中的Monitor正在執行的時候更新集合,
mMonitorQueue.add(monitor);
}
}
public interface Monitor {
void monitor();
}
現在我們大緻知道Watchdog、HandleChecker和Monitor分别是什麼了,接下來看下watchdog.start(),watchdog是一個線程,是以直接到其run方法
#Watchdog.java
public void run() {
boolean waitedHalf = false;
//無限循環
while (true) {
final List<HandlerChecker> blockedCheckers;
final String subject;
final boolean allowRestart;//是否允許重新啟動系統
int debuggerWasConnected = 0;//debug連結數量
synchronized (this) {
long timeout = CHECK_INTERVAL; //檢測周期:debug下為5s,正式版本為30s
for (int i=0; i<mHandlerCheckers.size(); i++) {
HandlerChecker hc = mHandlerCheckers.get(i);
//調用對應HandlerChecker的scheduleCheckLocked方法,該方法會觸發真正的檢查,稍後分析
hc.scheduleCheckLocked();
}
//...
long start = SystemClock.uptimeMillis();
//此處timeout > 0表明後續代碼一定會在檢測周期過後才會觸發
while (timeout > 0) {
try {
wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
}
boolean fdLimitTriggered = false;
if (mOpenFdMonitor != null) {
fdLimitTriggered = mOpenFdMonitor.monitor();
}
if (!fdLimitTriggered) {
final int waitState = evaluateCheckerCompletionLocked();//評估HandlerChecker檢查者的狀态
//處理檢測結果,稍後分析
}
}
Watchdog的run方法大緻做了如下事情:
- 周遊檢查者集合,觸發每一個HandlerChecker的scheduleCheckLocked來開啟檢查
- 等待一個檢查周期後,開始擷取檢查結果
- 根據檢查結果進行處理
接下來我們詳細看下watchdog的run方法的三個流程:
1.發起檢測
public void scheduleCheckLocked() {
//mCompleted預設是true
if (mCompleted) {
// 這裡把mMonitorQueue集合中的資料添加搭配mMonitor中,因為此時HandlerChecker不在工作隊列中,這樣不會因為多線程操作觸發快速失效機制
mMonitors.addAll(mMonitorQueue);
mMonitorQueue.clear();
}
if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
|| (mPauseCount > 0)) {
//沒有需要監視的服務,或者目前MessageQueue正在poll時,不用檢查
mCompleted = true;
return;
}
if (!mCompleted) {
// 不用重複檢查
return;
}
mCompleted = false;
mCurrentMonitor = null;
mStartTime = SystemClock.uptimeMillis();
mHandler.postAtFrontOfQueue(this);//将目前HandlerChecker添加到mHandler所屬MessageQueue隊列中頭
}
scheduleCheckLocked方法做了兩件事:
- 将新添加的Monitor轉移到mMonitor集合中
- 重置狀态,并向持有的mHandler中post一個Runnable(HandlerChecker自身)。由消息機制可以了解其會在mHandler所屬線程執行目前HandlerChecker的run方法,如下
public void run() {
//周遊mMonitors集合中每一個元素
final int size = mMonitors.size();
for (int i = 0 ; i < size ; i++) {
synchronized (Watchdog.this) {
mCurrentMonitor = mMonitors.get(i);
}
//執行其monitor方法來檢測,由于其是在HandlerCheck的run方法中調用的,是以是運作在Handler對應的線程中的。
mCurrentMonitor.monitor();
}
synchronized (Watchdog.this) {
mCompleted = true;
mCurrentMonitor = null;
}
}
由源碼分析可得
- HandlerChecker的run方法在Handler所屬線程執行,其執行内容為周遊每個Monitor,執行monitor方法
- 所有Monitor檢查完了之後,設定mCompleted為true,表示完成檢查
2 擷取檢查結果
由Watchdog的run方法得知,檢查結果主要是擷取evaluateCheckerCompletionLocked方法的傳回值,如下
#HandlerChecker
static final int COMPLETED = 0;
static final int WAITING = 1;
static final int WAITED_HALF = 2;
static final int OVERDUE = 3;
private int evaluateCheckerCompletionLocked() {
int state = COMPLETED;
for (int i=0; i<mHandlerCheckers.size(); i++) {
HandlerChecker hc = mHandlerCheckers.get(i);
//取得目前HandlerChecker集合中state最大值
state = Math.max(state, hc.getCompletionStateLocked());
}
return state;
}
public int getCompletionStateLocked() {
//在檢測開始時mCompleted為false,檢查結束時為true
if (mCompleted) {
return COMPLETED;
} else {
long latency = SystemClock.uptimeMillis() - mStartTime;
if (latency < mWaitMax/2) {
//如果目前耗費時間小于最大等待時長的一半,傳回WAITING
return WAITING;
} else if (latency < mWaitMax) {
//如果目前耗費時間大于等于最大等待時長的一半,且小于最大等待時長,傳回WAITED_HALF
return WAITED_HALF;
}
}
return OVERDUE;//表示已經逾時了,即阻塞或者挂起了
}
由源碼可知,檢查結果一共有4種:
- COMPLETED:表示HandlerChecker檢測完成,本次沒有阻塞
- WAITING:表示本次檢測還未完成,目前耗時小于最大耗時的一半
- WAITED_HALF:表示本次檢測還未完成,目前耗時大于最大耗時的一半
-
OVERDUE: 表示本次檢測逾時了
那麼Watchdog針對上述4種檢測結果,會如何處理呢?
3 處理檢測結果
處理檢測結果的代碼在Watchdog的run方法中,如下:
#Watchdog.java
public void run() {
boolean waitedHalf = false;
//無限循環
while (true) {
final List<HandlerChecker> blockedCheckers;
final String subject;
final boolean allowRestart;//是否允許重新啟動系統
int debuggerWasConnected = 0;//debug連結數量
synchronized (this) {
//... 檢測階段,前面分析過了
if (!fdLimitTriggered) {
final int waitState = evaluateCheckerCompletionLocked();//評估HandlerChecker檢查者的狀态
if (waitState == COMPLETED) {
// 全部完成,則認為一切工作正常,本次檢查結束 重置waitedHalf為false
waitedHalf = false;
continue;//開始下一輪檢測
} else if (waitState == WAITING) {
// 在等待狀态,目前耗時小于最大限制的一半,
continue;//再檢查一次
} else if (waitState == WAITED_HALF) {
if (!waitedHalf) {
//在等待狀态,目前耗時大于最大限制的一半,但還未逾時
ArrayList<Integer> pids = new ArrayList<Integer>();
pids.add(Process.myPid());
//通過AMS儲存目前程序的堆棧資訊
ActivityManagerService.dumpStackTraces(pids, null, null,
getInterestingNativePids());
waitedHalf = true;
}
continue;//在檢查一次
}
// 走到這裡表明一些檢查器已經檢查出問題了
blockedCheckers = getBlockedCheckersLocked();//擷取已阻塞的那些HandlerChecker
subject = describeCheckersLocked(blockedCheckers);//建構提示字元串
} else {
}
allowRestart = mAllowRestart;
}
//代碼走到這裡,表示系統大機率已挂起了
ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
if (mPhonePid > 0) pids.add(mPhonePid);
通過AMS儲存再次目前程序的堆棧資訊
final File stack = ActivityManagerService.dumpStackTraces(
pids, null, null, getInterestingNativePids());
//等待5s,為了确認堆棧資訊都被寫入
SystemClock.sleep(5000);
// 觸發核心以轉儲所有阻塞的線程,并将所有CPU上的回溯到核心日志
doSysRq('w');
doSysRq('l');
// ...dropbox相關
IActivityController controller;
synchronized (this) {
controller = mController;
}
if (controller != null) {
//将阻塞狀态報告給activity controller,
try {
Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
//傳回值為1表示繼續等待,-1表示殺死系統
int res = controller.systemNotResponding(subject);
if (res >= 0) {
waitedHalf = false;
continue; //設定ActivityController的某些情況下,可以讓發生Watchdog時繼續等待
}
} catch (RemoteException e) {
}
}
//當debugger沒有attach時,才殺死程序
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
if (debuggerWasConnected >= 2) {
Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
} else if (debuggerWasConnected > 0) {
Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");
} else if (!allowRestart) {
Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
} else {
Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
//周遊輸出阻塞線程的棧資訊
for (int i=0; i<blockedCheckers.size(); i++) {
Slog.w(TAG, blockedCheckers.get(i).getName() + " stack trace:");
StackTraceElement[] stackTrace
= blockedCheckers.get(i).getThread().getStackTrace();
for (StackTraceElement element: stackTrace) {
Slog.w(TAG, " at " + element);
}
}
Slog.w(TAG, "*** GOODBYE!");
//殺死程序system_server
Process.killProcess(Process.myPid());
System.exit(10);
}
waitedHalf = false;
}
}
}
private ArrayList<HandlerChecker> getBlockedCheckersLocked() {
ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
for (int i=0; i<mHandlerCheckers.size(); i++) {
HandlerChecker hc = mHandlerCheckers.get(i);
if (hc.isOverdueLocked()) {
//統計逾時那些checker
checkers.add(hc);
}
}
return checkers;
}
處理檢測結果也是針對4種不同檢測結果分開進行的:
- COMPLETED:繼續下一輪檢測
- WAITING:繼續下一輪檢測
- WAITED_HALF:dump堆棧,并繼續下一輪檢測
-
OVERDUE: 逾時,dump堆棧和核心線程,如果沒有特例,則殺掉程序并重新開機
有細心的小夥伴
總結與反思
Watchdog是系統檢測的最後保障,其設計采用了單線程+多個HanlderChecker的結構,流程上主要包含啟動監控檢測、添加監控、擷取結果并處理。其巧妙的地方在于把檢測過程的耗時分攤到了各Handler對應的工作線程,保證了Watchdog檢測階段的性能。該方案也可稍作變通,以解決其他問題,比如檢測卡頓