ハンドラ
sendMessageからhandleMessageまで
私のリズムに従って、Ctrl キーを押し、マウスでハンドラーをクリックして、幸せになり始めてください。クローナ!!
まず、最も一般的に使用されるメソッド sendMessage (メッセージ) を見つけます。
sendMessageDelayed(message, lateMillis) を直接呼び出します。
ただし、sendMessageDelayed はわずかな検証操作のみを実行します (DelayMillis の非負の検証)。
次に、sendMessageAtTime(message, SystemClock.uptimeMillis() + lateMillis); を直接呼び出します。
sendMessageAtTime: メインイベントが始まります
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
このメソッドから、MessageQueue(メッセージキュー)が表示されることがわかります。
メッセージ キューを作成した後、メッセージと遅延時間を渡します。
ハンドラー内の enqueueMessage:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;// Message 注入 handler
if (mAsynchronous) {
// {true:异步;false:同步}
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue は時間によってソートされた優先キューです。
MessageQueue の enqueueMessage:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
// 校验 handler
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
// 判断该消息 是否 被占用
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
//根据 when(也就是时间)来确定 新消息的位置
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg; // 完成链表的变换(完成消息插入操作)
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
MessageQueue 時間優先キューの新しいメッセージ挿入図:
sendMessage から MessageQueue へのルートが開かれたので、Looper行を見てみましょう。
sendMessage はメッセージ キューに新しいメッセージを追加し、Looper はループ内でメッセージを消費 (処理) します。
Looper のloop() メソッドは主に 2 つのことを行います: (最初に Looper が無限ループであることを宣言します)
-
メッセージキューからメッセージを取り出します。
Message msg = queue.next(); // might block ( next()可能阻塞 )
MessageQueueのnext()メソッドは、次のメッセージ(メッセージ)を取り出すメソッドです。
-
HandlerのdispatchMessage(msg)メソッドを呼び出してメッセージを実行します。
msg.target.dispatchMessage(msg);
ハンドラーのdispatchMessageメソッド:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg);//就是我们重写的方法。 } }
最後に、handleMessage(msg) メソッドを呼び出します。これにより、閉じたループが形成されます。素晴らしい!!!
問題探し
Looper はどこで初期化されますか?
ルーパーの初期化:
まず Looper の構築メソッドを見てみましょう: (これは実際にはプライベートであり、他のクラスによって開始された Looper ではないことを示しています)
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
Looper には独自の起動クラス メソッドがあります: prepare(true)
private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { //如果发现对应的threadLocal(key) 已经 有对应的Looper(value)了,抛出异常。 throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
ルーパーの場合、その力(糸)はどこから来るのでしょうか?
探検を続けましょう!(ThreadLocal.set() メソッドをクリックします)
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);// 获得当前线程的 threadLocalMap(用来保存线程上下文的) if (map != null) map.set(this, value);// 将此 threadLocal 与 looper 对象绑定到上述 map 中去。 else createMap(t, value); }
getMap メソッドは、対応するスレッドの threadLocals メンバー変数を返します。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
これでバインディングが完了します。
スレッドは、ThreadLocal (キー): Looper (値)マッピングのペアを格納する ThreadLocalMap に対応します。
ハンドラーがメモリ リークを引き起こすのはなぜですか? どうやって解決すればいいでしょうか?
Handler を使用するときは、通常、匿名の内部クラスを使用して handleMessage() メソッドをオーバーライドします。
この匿名内部クラスの使用がメモリ リークの原因となります。
では、他の内部クラスがメモリ リークを引き起こす可能性があるのでしょうか? なぜ?
この疑問は後々当然明らかになる。
まず、Handler の内部クラスがメモリ リークを引き起こす理由について説明します。
-
Java 構文の観点から見ると、非静的 (静的) 内部クラスは外部クラスのオブジェクト (this) を保持します。
このようにして、内部クラスは外部クラスのメンバー プロパティに直接アクセスできます。
-
ハンドラーの実行メカニズムから、メッセージがハンドラーにバインドされ、MessageQueue に入ることがわかります。
-
MessageQueue 内のメッセージは遅延メッセージ (遅延) である可能性があり、メッセージはかなり長い間存続する可能性があります。
-
メッセージ存続期間中は、ページが破棄されてもハンドラーが保持している this を解放することはできません。
-
GC リサイクル メカニズムの観点から見ると、強参照を持つオブジェクトは簡単にはリサイクルされず、JVM はむしろ例外をスローします。
純粋な内部クラスではメモリリークが発生しないことがわかりますが、この問題は Handler の独自の動作メカニズムに起因します。
では、どうすれば解決できるのでしょうか?
- Java 構文の観点からトリガーすると、非静的内部クラスは外部クラス オブジェクトを保持しますが、静的外部クラスは保持しません。Handler オブジェクトを静的にすることができます。
- 分析の中で、強参照はリサイクルするのが簡単ではないと述べましたが、同様に、弱参照 (WeakReference) を使用することで、必要に応じて JVM が外部クラス オブジェクトをリサイクルできるようになります (外部クラス オブジェクトを使用したい場合は、 ReferenceQueue に移動して取得します)。
新しいハンドラーをメインスレッドで直接使用できるのはなぜですか? ルーパーとの関係は分かりませんでした。なぜ?
アクティビティ内でハンドラーを使用する場合。
final Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };
最初から最後まで、ルーパーをどこに用意するかは気にせず、ただこのように使っただけで、使えるということは、ルーパーを用意する場所があるということです。
Android がイベントベースのフレームワークであることはわかっています。
何かをしたいなら、まず刺激が必要だ。
次に、APPの実行を開始するイベント:デスクトップ上のAPP アイコンをクリックする必要があります。
このプロセスでは一体何が起こっているのでしょうか。
いくつかの点を明確にしましょう。
- Android のメイン インターフェイス (デスクトップ) も APP であり、通常はランチャーと呼ばれます
- Zygote は、Android システムで新しいプロセスを作成するコア プロセスであり、Dalvik 仮想マシンの起動、必要なシステム リソースとシステム クラスの読み込み、system_server プロセスの起動、アプリ アプリケーション リクエストの処理の待機を担当します。
- アプリケーションは仮想マシン (Dalvik/Art) のインスタンスです。各プロセスには仮想マシンが 1 つだけあり、アプリケーション インスタンスは各プロセスに 1 つだけあります。単一プロセス アプリにはアプリケーション インスタンスが 1 つだけあり、マルチプロセス アプリには複数のアプリケーション インスタンスがあります。
- 各プログラムには独自のプログラム エントリがあり、APP のプログラム エントリは、ActivityThread クラスのメイン メソッドです。
実は、たくさんの言葉を引き出したいのですが、APP のプログラム エントリは ActivityThread にあり、その中に秘密があるはずです。
ActivityThread クラスのメイン メソッドの一部:
public static void main(String[] args) {
//...被忽略的代码段...
Looper.prepareMainLooper();
//...被忽略的代码段...
if (sMainThreadHandler == null) {
// 获得主线程的 handler
sMainThreadHandler = thread.getHandler();
}
//...被忽略的代码段...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
APP の入り口で Looper の初期化が行われていることがわかります。
Looper.prepareMainLooper:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);// 创建 Looper 对象,并与主线程绑定
synchronized (Looper.class) {
if (sMainLooper != null) {
//同样,针对主线程的 Looper 也只准有一个
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
このことから、実際にはこれらすべてがどこかに存在するはずであることがわかりました。
そして、つるをたどって H クラスを見つけました (Ctrl キーを押しながら getHandler メソッドをクリックします)。
class H extends Handler
この H は Handler クラスを継承し、多くのメソッドを書き換えています。これは、メイン スレッドの Handler が依然として大きく異なることを示しています。
クラス H: handleMessage メソッド ローカル:
詳しく見てみると、ここで扱われている状況は非常に重要なコンポーネントアクションであることがわかりました。
- 特に、APP を終了する操作 (EXIT_APPLICATION) は、実際にはメインスレッドの Looper を終了します。
APPの終了操作も可能であり、おそらくAndroidの処理機構がLooper+Handlerモードであることが分かります。
子スレッドにハンドラー機構を構築した場合、それをどのように終了すればよいでしょうか?
なぜ: (そうしないと無限ループが発生し、スレッドは決して終了せず、リソースは決して解放されません)
Looperのloop()メソッドは無限ループであることがわかっており、MessageQueueにメッセージが無い場合はブロックされてしまいます。
Message msg = queue.next(); // might block
MessageQueue.next() ループのローカル コード:
for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //当 nextPollTimeoutMillis 为 -1 时,此方法将从 native 层持续阻塞,直到有事件发生 //如果 nextPollTimeoutMillis 为 0 时,则无需等待,直接返回。 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { //...被忽略的代码段... if (msg != null) { //...被忽略的代码段... } else { // No more messages. 当没有消息时 nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. // 处理完所有挂起的消息后,立即处理退出消息。 if (mQuitting) { // 在调用 quit() dispose(); return null;//返回一个 null ,让loop()跳出循环。 } //...被忽略的代码段... }
Looper.loop() ループ内のローカル コード:
for (;;) { Message msg = queue.next(); // might block if (msg == null) { //当 next 方法 返回一个 null 时,跳出循环。 // No message indicates that the message queue is quitting. return; } //...被忽略的代码段... }
上記のコードから、loop() をループから抜け出すには、2 つの手順が必要であることがわかります。
スレッドをウェイクアップして実行を継続させます
NativePollOnce はスレッドをブロックしている可能性があり、nativeWake を呼び出してスレッドを起動する必要があります。
mQuitting = true にすると、loop() は return ブランチに入ります。
quit を呼び出すとき、MessageQueue.quit():
void quit(boolean safe) { if (!mQuitAllowed) { //主线程不允许自主 quit throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } //将其设置为 true ,为了next() 方法 返回一个 null,进而让 loop 跳出循环 mQuitting = true; //将 Message对象 的空间都置空。(Message的创建和维护采用了 享元设计模式) if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr);//将线程唤醒 } }
この時点で、ループはループから飛び出し、このハンドラー アクションはここで終了します。