HandlerChecker は WatchDog のコア内部クラス実装であり、このクラスのビジネス ロジックを理解すれば、基本的に WatchDog の原理をマスターできます。
このクラスの実装を分析するために、部分的に分解してみましょう。
/**
* Used for checking status of handle threads and scheduling monitor callbacks.
*/
public final class HandlerChecker implements Runnable {
// 构造函数中传入的Handler对象
private final Handler mHandler;
// 构造函数中传入的名称
private final String mName;
// 构造函数中传入的参数,默认为30秒
private final long mWaitMax;
// WatchDog监测的Monitor对象列表,schedule过程中
// 遍历的是这个列表,schedule过程下面详述。
private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
// WatchDog监测的Monitor对象列表,addMonitor最终
// 是把要监测的对象添加到这个列表中,每一次schedule
// 开始时,会将mMonitorQueue元素复制到mMonitors中,
// 然后mMonitorQueue会清空。schedule过程下面详述。
private final ArrayList<Monitor> mMonitorQueue = new ArrayList<Monitor>();
// 每一次schedule过程是否完成
private boolean mCompleted;
// 当前schedule的监测对象
private Monitor mCurrentMonitor;
// 本次schedule过程的开始时间,用于后面判断是否超时
private long mStartTime;
// 暂停 schedule的调用次数,如果大于0则schedule不会真正执行
private int mPauseCount;
HandlerChecker は Runnable インターフェイスを実装しており、アノテーション セクションで各属性の役割が簡単に説明されており、以下の各関数を分析すると、その役割をさらに理解できます。
HandlerChecker(Handler handler, String name, long waitMaxMillis) {
mHandler = handler;
mName = name;
mWaitMax = waitMaxMillis;
mCompleted = true;
}
コンストラクターについては特に解析する必要はなく、属性宣言部分については既に説明済みです。
void addMonitorLocked(Monitor monitor) {
// We don't want to update mMonitors when the Handler is in the middle of checking
// all monitors. We will update mMonitors on the next schedule if it is safe
mMonitorQueue.add(monitor);
}
これは、最終的に WatchDog の addMonitor メソッドを呼び出すときに呼び出されます。コメント部分の翻訳は、ハンドラーはスケジュール監視プロセス中に mMonitors リストを更新せず、次のスケジュール中にのみ mMonitors リストを更新することを意味します。これが実際に意味するのは、スケジュール プロセス中にオブジェクトが addMonitor 関数を呼び出すと、新しい監視対象オブジェクトがまず mMonitorQueue に追加され、次にスケジュールの現在のラウンドが終了して次のスケジュールが開始されるときに mMonitors に更新されるということです。
public void scheduleCheckLocked() {
// mCompleted 初始值为true
// 或者一次schedule完成后为true,否则为false
if (mCompleted) {
// Safe to update monitors in queue, Handler is not in the middle of work
// 我们前面已经分析过,每次schedule开始时,会将
// mMonitorQueue复制到mMonitors中并清空。
mMonitors.addAll(mMonitorQueue);
mMonitorQueue.clear();
}
// mMonitors.size() == 0 表示没有被监测的对象
// isPolling 为true表示当前线程中的消息机制处于
// 正常轮询中,未发生阻塞
// mPauseCount > 0表示有地方调用了pauseLocked
// 方法
if ((mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling())
|| (mPauseCount > 0)) {
// Don't schedule until after resume OR
// If the target looper has recently been polling, then
// there is no reason to enqueue our checker on it since that
// is as good as it not being deadlocked. This avoid having
// to do a context switch to check the thread. Note that we
// only do this if we have no monitors since those would need to
// be executed at this point.
// 结束此轮schedule
mCompleted = true;
return;
}
// 如果本轮schedule未结束,则不允许新的schedule开始
if (!mCompleted) {
// we already have a check in flight, so no need
return;
}
// 经过上面等检查,真正的check动作开始
mCompleted = false;
mCurrentMonitor = null;
// 记录本次check开始时间,后面用于判断是否超时
mStartTime = SystemClock.uptimeMillis();
// 进入run函数
mHandler.postAtFrontOfQueue(this);
}
この部分は主に、現在のスレッドと mMonitor のオブジェクトがタイムアウトしたかどうかを確認するもので、チェックが開始され、run 関数がトリガーされる前にいくつかのチェックが実行されます。
@Override
public void run() {
// Once we get here, we ensure that mMonitors does not change even if we call
// #addMonitorLocked because we first add the new monitors to mMonitorQueue and
// move them to mMonitors on the next schedule when mCompleted is true, at which
// point we have completed execution of this method.
// 遍历mMonitors列表
final int size = mMonitors.size();
for (int i = 0 ; i < size ; i++) {
synchronized (mLock) {
// 返回当前check的对象
mCurrentMonitor = mMonitors.get(i);
}
// 回调当前被监测对象的monitor方法
mCurrentMonitor.monitor();
}
// 执行到这里说明上面的循环执行完毕,没有发生死锁问题,
// 本次schedule过程结束
synchronized (mLock) {
mCompleted = true;
mCurrentMonitor = null;
}
}
このように、監視対象オブジェクトのmonitorメソッドをコールバックすることが重要ですが、monitorメソッドのコールバックが成功したということは、なぜこの一連のチェックを終了しても問題がないことを意味するのでしょうか。監視対象オブジェクトの監視メソッドの実装を確認する必要があります。WindowManagerService を例に挙げると、その監視メソッドは次のとおりです。
// Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection).
@Override
public void monitor() {
synchronized (mGlobalLock) {
}
}
モニターは mGlobalLock のロックを保持している空のメソッドにすぎませんが、このメソッドが正常に実行できれば、mGlobalLock が保持されている場所でデッドロックが発生していないことになります。WatchDog で監視したい場合は、実装した監視メソッドで監視したいロックを保持するだけです。
boolean isOverdueLocked() {
return (!mCompleted) && (SystemClock.uptimeMillis() > mStartTime + mWaitMax);
}
このラウンドのスケジュールがタイムアウトしたかどうかを確認します (デフォルトは 30 秒)。
public int getCompletionStateLocked() {
if (mCompleted) {
// 本轮schedule成功完成
return COMPLETED;
} else {
// 获取check开始到现在的时间差
long latency = SystemClock.uptimeMillis() - mStartTime;
// 小于mWaitMax/2(15秒)时,正在check中,返回WAITING
if (latency < mWaitMax/2) {
return WAITING;
// 大于15秒小于30秒时,返回WAITED_HALF
} else if (latency < mWaitMax) {
return WAITED_HALF;
}
}
// 走到这里说明check到时间已经超过30秒,已经超时
return OVERDUE;
}
この関数は、現在のスケジュールの完了ステータスを取得するために使用されます。このステータスは、さまざまな条件に従って COMPLETED、WAITING、WAITED_HALF、OVERDUE (タイムアウト) ステータスに分けられます。
public Thread getThread() {
return mHandler.getLooper().getThread();
}
public String getName() {
return mName;
}
現在のスレッドとスレッド名を取得します。
String describeBlockedStateLocked() {
if (mCurrentMonitor == null) {
return "Blocked in handler on " + mName + " (" + getThread().getName() + ")";
} else {
return "Blocked in monitor " + mCurrentMonitor.getClass().getName()
+ " on " + mName + " (" + getThread().getName() + ")";
}
}
この関数を見ると、現在のスレッドの名前を返す (mCurrentMonitor == null)、または現在のスレッドの名前と監視対象オブジェクトのクラス名を返す (mCurrentMonitor != null) ことを意味します。実際、このメソッドはタイムアウトが検出された場合にのみ呼び出されます。したがって、その機能は、チェック プロセス中にタイムアウトが発生した場合に、現在のスレッドまたはクラス名の情報を返すことです。メソッド呼び出し部分については、次の章で詳しく紹介します。
/** Pause the HandlerChecker. */
public void pauseLocked(String reason) {
mPauseCount++;
// Mark as completed, because there's a chance we called this after the watchog
// thread loop called Object#wait after 'WAITED_HALF'. In that case we want to ensure
// the next call to #getCompletionStateLocked for this checker returns 'COMPLETED'
mCompleted = true;
Slog.i(TAG, "Pausing HandlerChecker: " + mName + " for reason: "
+ reason + ". Pause count: " + mPauseCount);
}
/** Resume the HandlerChecker from the last {@link #pauseLocked}. */
public void resumeLocked(String reason) {
if (mPauseCount > 0) {
mPauseCount--;
Slog.i(TAG, "Resuming HandlerChecker: " + mName + " for reason: "
+ reason + ". Pause count: " + mPauseCount);
} else {
Slog.wtf(TAG, "Already resumed HandlerChecker: " + mName);
}
}
mPauseCount の値を記録および更新するには、2 つの相反するメソッドが使用されます。前に分析したように、mPauseCount が 0 より大きい場合、実際のチェックは実行されません。
この時点で、HandlerChecker クラスのすべての解析が完了しました。
この章は終わりです。