English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
メッセージはメッセージキューに保存されており、メッセージループスレッドはこのメッセージキューを中心に無限ループに入り、スレッドが退出するまで繰り返します。キューにメッセージがあれば、メッセージループスレッドはそれを取り出し、対応するHandlerに処理を委譲します。キューにメッセージがなければ、メッセージループスレッドは待機状態に入り、次のメッセージの到着を待ちます。Androidアプリケーションを開発する際に、UIメインスレッドをブロックしてANRが発生しないように、重いタスクを実行する場合は、通常、特定のタスクを完了するためのサブスレッドを作成します。サブスレッドを作成する際には、Threadオブジェクトを生成してメッセージループを持たないサブスレッドを作成するか、メッセージループを持つサブスレッドを作成する二つの選択があります。メッセージループを持つサブスレッドを作成するには、以下の二つの方法があります。一つは、Androidが提供するHandlerThreadクラスを利用してメッセージループを持つスレッドオブジェクトを作成することです。もう一つは、スレッドのrun()メソッドを実装し、以下のようにメッセージループを起動することです:
一、メッセージメカニズムの使用
通常、メッセージはメッセージスレッドとHandlerで構成されています。以下では、PowerManagerServiceのメッセージHandlerを見てみましょう:
mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_DISPLAY, false /*allowIo*/); mHandlerThread.start(); mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
ここのServiceThreadはHandlerThreadであり、Handlerを作成する際には、HandlerThreadのlooperを入れる必要があります。そうしないと、デフォルトで現在のスレッドのlooperが使用されます。
そして、各handlerは以下のようになります:
private final class PowerManagerHandler extends Handler { public PowerManagerHandler(Looper looper) { super(looper, null, true /*async*/); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_USER_ACTIVITY_TIMEOUT: handleUserActivityTimeout(); break; case MSG_SANDMAN: handleSandman(); break; case MSG_SCREEN_BRIGHTNESS_BOOST_TIMEOUT: handleScreenBrightnessBoostTimeout(); break; case MSG_CHECK_WAKE_LOCK_ACQUIRE_TIMEOUT: checkWakeLockAquireTooLong(); Message m = mHandler.obtainMessage(MSG_CHECK_WAKE_LOCK_ACQUIRE_TIMEOUT); m.setAsynchronous(true); mHandler.sendMessageDelayed(m, WAKE_LOCK_ACQUIRE_TOO_LONG_TIMEOUT); break; } } }
二、メッセージメカニズムの原理
それではHandlerThreadのメイン関数のrun関数を見てみましょう:
public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper();//値を設定した後notifyall、getLooper関数はmLooperを返します notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
それではLopperのprepare関数を見てみましょう、最後にLooperオブジェクトが新しく作成され、スレッドのローカル変数に置かれます。
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
LooperのコンストラクタではMessageQueueが作成されます
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
それでは、MessageQueueの構造関数を見てみましょう。nativeInitはnativeメソッドであり、戻り値はmPtrに保存されています。明らかに、そのポインタはlong型の変数で保存されています。
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }
native関数では、NativeMessageQueueオブジェクトを作成し、ポインタ変数を返します。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) { NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); if (!nativeMessageQueue) { jniThrowRuntimeException(env, "Unable to allocate native queue"); return 0; } nativeMessageQueue->incStrong(env); return reinterpret_cast<jlong>(nativeMessageQueue); }
NativeMessageQueueの構造関数は、mLooperを取得し、取得できない場合は新しいLooperを作成します。
NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) { mLooper = Looper::getForThread(); if (mLooper == NULL) { mLooper = new Looper(false); Looper::setForThread(mLooper); } }
それでは、Looperの構造関数を見てみましょう。eventfdを呼び出してfdを作成していることが表示されています。eventfdはプロセスやスレッド間の通信に主に用いられます。このブログのeventfd紹介を確認してみましょう。
Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false), mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { mWakeEventFd = eventfd(0, EFD_NONBLOCK); LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "wakeイベントfdを作成できませんでした。errno=%d", errno); AutoMutex _l(mLock); rebuildEpollLocked(); }
2.1 c層でepollを作成します。
rebuildEpollLocked関数についてもう少し詳しく見てみましょう。epollが作成され、mWakeEventFdがepollに追加され、mRequestsのfdもepollに追加されています。
void Looper::rebuildEpollLocked() { // 古いepollインスタンスがあれば閉じます。 if (mEpollFd >= 0) { #if DEBUG_CALLBACKS ALOGD("%p ~ rebuildEpollLocked - このエポックセットを再構築します。 #endif close(mEpollFd); } // 新しいepollインスタンスを割り当ててwake pipeを登録します。 mEpollFd = epoll_create(EPOLL_SIZE_HINT); LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "epollインスタンスの作成に失敗しました。errno=%d", errno); struct epoll_event eventItem; memset(&eventItem, 0, sizeof(epoll_event)); // データフィールドの未使用メンバーをゼロアウトします eventItem.events = EPOLLIN; eventItem.data.fd = mWakeEventFd; int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem); LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance. errno=%d", errno); for (size_t i = 0; i < mRequests.size(); i++) { const Request& request = mRequests.valueAt(i); struct epoll_event eventItem; request.initEventItem(&eventItem); int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem); ALOGE("Error adding epoll events for fd %d while rebuilding epoll set, errno=%d", request.fd, errno); } } }
HandlerThreadのrun関数に戻ってみましょう、Looperのloop関数をさらに分析します
public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
Looperのloop関数を見てみましょう:
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue;//LooperのmQueueを取得 // このスレッドのアイデンティティがローカルプロセスのものであることを確認してください // そして、そのアイデンティティトークンの実際の内容を追跡します。 Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might blockこの関数はブロックする可能性があります、ブロックは主にepoll_waitです if (msg == null) { // メッセージがなくなることは、メッセージキューが終了していることを示します。 return; } // これはローカル変数に保持されるべきもので、UIイベントがloggerを設定する場合に対応するためです。 Printer logging = me.mLogging;//自分で作成したプリント if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // dispatchingの過程で、 // threadのidentityが破損していなかったことを確認してください。 final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }
MessageQueueクラスのnextメソッドは、まずnativePollOnce関数を呼び出し、その後メッセージキューからMessageを取り出します
Message next() { // メッセージループが既に終了し、廃棄された場合にはここに戻ります。 // アプリケーションがquitの後でlooperを再起動しようとすると、このことが起こることがあります // これはサポートされていません。 final long ptr = mPtr;//以前に保持していたポインタ if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis);
以下では、nativePollOnceというネイティブ関数に注目し、先ほどのポインタをNativeMessageQueueに強制変換し、そのpollOnce関数を呼び出します
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->pollOnce(env, obj, timeoutMillis); }
2.2 cレベルepoll_waitブロック
pollOnce関数、この関数の前のwhileは通常はありません。indentが0以上の場合を処理していますが、このような場合は通常ありませんので、pollInner関数を見てください。
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { int result = 0; for (;;) { while (mResponseIndex < mResponses.size()) { const Response& response = mResponses.itemAt(mResponseIndex++); int ident = response.request.ident; if (ident >= 0) { int fd = response.request.fd; int events = response.events; void* data = response.request.data; #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce"} - returning signalled identifier %d: " "fd=%d, events=0x%x, data=%p", this, ident, fd, events, data); #endif if (outFd != NULL) *outFd = fd; if (outEvents != NULL) *outEvents = events; if (outData != NULL) *outData = data; return ident; } } if (result != 0) { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce"} - returning result %d", this, result); #endif if (outFd != NULL) *outFd = 0; if (outEvents != NULL) *outEvents = 0; if (outData != NULL) *outData = NULL; return result; } result = pollInner(timeoutMillis); } }
pollInner関数は主にepoll_waitをブロック呼び出し、javaレベルが各ブロック時間をcレベルに伝え、mWakeEventFdまたは以前にaddFdで追加されたfdにイベントが来るまで待ち、その後epoll_waitが戻されます。
int Looper::pollInner(int timeoutMillis) { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce"} - waiting: timeoutMillis=%d", this, timeoutMillis); #endif // 次のメッセージが到着するタイムアウトに基づいてタイムアウトを調整してください。 if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime); if (messageTimeoutMillis >= 0 && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) { timeoutMillis = messageTimeoutMillis; } #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce"} - 次のメッセージが%" PRId64 "ns、調整されたタイムアウト: timeoutMillis=%d", this, mNextMessageUptime - 今、timeoutMillis); #endif } // ポール。 int result = POLL_WAKE; mResponses.clear();//mResponsesを空にします mResponseIndex = 0; // アイドル状態に入る寸前です。 mPolling = true; struct epoll_event eventItems[EPOLL_MAX_EVENTS]; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);//epoll_waitのメインスレッドがここでブロックされ、このブロック時間はJava層から伝わってきます // もはやアイドル状態でない。 mPolling = false; // ロックを取得。 mLock.lock(); // 必要に応じてepollセットを再構築。 if (mEpollRebuildRequired) { mEpollRebuildRequired = false; rebuildEpollLocked(); Done: ; } // ポールのエラーをチェック。 if (eventCount < 0) { if (errno == EINTR) { Done: ; } ALOGW("ポールが予期せぬエラーで失敗しました、エラー番号=%d", errno); result = POLL_ERROR; Done: ; } // pollタイムアウトを確認します。 if (eventCount == 0) { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce"} - タイムアウト", this); #endif result = POLL_TIMEOUT; Done: ; } // すべてのイベントを処理します。 #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ pollOnce"} - fdsからイベントを処理しています。", this, eventCount); #endif for (int i = 0; i < eventCount; i++) { int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; if (fd == mWakeEventFd) {//唤醒スレッドのイベントを通知します。 if (epollEvents & EPOLLIN) { awoken(); } else { ALOGW("wake event fd上で予期しないepollイベント 0x%xを無視しています。", epollEvents); } } else { ssize_t requestIndex = mRequests.indexOfKey(fd);//以前のaddFdのイベント if (requestIndex >= 0) { int events = 0; if (epollEvents & EPOLLIN) events |= EVENT_INPUT; if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT; if (epollEvents & EPOLLERR) events |= EVENT_ERROR; if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP; pushResponse(events, mRequests.valueAt(requestIndex));//mResponsesに置きます。 } else { ALOGW("fd %d 上の予期しない epollイベント 0x%x を無視しています ", fd); "no longer registered.", epollEvents, fd); } } } 完了: ; // 保留中のメッセージコールバックを呼び出します。 mNextMessageUptime = LLONG_MAX; while (mMessageEnvelopes.size() != 0) {// これは主にCレベルのメッセージで、Javaレベルのメッセージは自分で管理されています。 nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0); if (messageEnvelope.uptime <= now) { // エンベロープをリストから削除します。 // handleMessageの呼び出しまでhandlerに強い参照を保持します。 // 終わる前に、handlerを削除できるようにそれをドロップします *before* // 私たちはロックを再取得します。 { // handlerを取得する sp<MessageHandler> handler = messageEnvelope.handler; Message message = messageEnvelope.message; mMessageEnvelopes.removeAt(0); mSendingMessage = true; mLock.unlock(); #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce"} - sending message: handler=%p, what=%d", this, handler.get(), message.what); #endif handler->handleMessage(message); } // release handler mLock.lock(); mSendingMessage = false; result = POLL_CALLBACK; } else { // キューの先頭に残された最後のメッセージが次のウェイクアップ時間を決定します。 mNextMessageUptime = messageEnvelope.uptime; break; } } // ロックを解放します。 mLock.unlock(); // すべてのレスポンスコールバックを呼び出します。 for (size_t i = 0; i < mResponses.size(); i++) {//これは以前のaddFdイベントの処理で、主にmResponsesを巡回し、そのコールバックを呼び出しています。 Response& response = mResponses.editItemAt(i); if (response.request.ident == POLL_CALLBACK) { int fd = response.request.fd; int events = response.events; void* data = response.request.data; #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce"} - invoking fd event callback %p: fd=%d, events=0x%x, data=%p", this, response.request.callback.get(), fd, events, data); #endif // callbackを呼び出します。ファイルディスクリプタが閉じられる可能性があることに注意してください。 // 関数が返される前にcallback(そして再利用される可能性のある)を呼び出しますので、 // その後のファイルディスクリプタの削除時に少し注意を払う必要があります。 int callbackResult = response.request.callback->handleEvent(fd, events, data); if (callbackResult == 0) { removeFd(fd, response.request.seq); } // 私たちがすぐにresponse構造体のcallbackリファレンスをクリアする必要があります // 次のpollまで、response vector自体をクリアしません。 response.request.callback.clear(); result = POLL_CALLBACK; } } return result; }
Looperのloop関数をさらに分析し、コードのデバッグに役立つ自分のプリントを出力することができます。以前はMessageのtargetのdispatchMessageを呼び出してメッセージを分配していました。
for (;;) { Message msg = queue.next(); // ブロックする可能性があります。 if (msg == null) { // メッセージがなくなることは、メッセージキューが終了していることを示します。 return; } // これはローカル変数に保持されるべきもので、UIイベントがloggerを設定する場合に対応するためです。 Printer logging = me.mLogging;//自分のプリント if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // dispatchingの過程で、 // threadのidentityが破損していなかったことを確認してください。 final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }
2.3 デバッグプリントを追加
まず自分が追加したプリントを見てみましょう、LopperのsetMessageLogging関数を使ってプリントできます
public void setMessageLogging(@Nullable Printer printer) { mLogging = printer; } Printerはインターフェースです public interface Printer { /** * テキストの行をオプション出力に書き込みます。終端は必要ありません * 新しい行を含む指定された文字列。 */ void println(String x); }
2.4 Java層のメッセージ配信処理
メッセージの配信を見てみましょう、まずはHandlerのobtainMessage関数を呼び出します
Message msg = mHandler.obtainMessage(MSG_CHECK_WAKE_LOCK_ACQUIRE_TIMEOUT); msg.setAsynchronous(true); mHandler.sendMessageDelayed(msg, WAKE_LOCK_ACQUIRE_TOO_LONG_TIMEOUT);
まずobtainMessageがMessageのobtain関数を呼び出します
public final Message obtainMessage(int what) { return Message.obtain(this, what); }
Messageのobtain関数は新しいMessageを作成し、そのtargetをそのHandlerに設定します
public static Message obtain(Handler h, int what) { Message m = obtain();//これは新しいMessageを作成します m.target = h; m.what = what; return m; }
それでは、以前に分派したメッセージと関連させます
msg.target.dispatchMessage(msg);最後にHandlerのdispatchMessage関数を呼び出し、最後にHandlerの中で、異なる状況に応じてメッセージを処理します。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg);//これはRunnableを含むpost形式で送信する場合です } else { if (mCallback != null) {//これはhandlerが引数を渡すときにmCallbackをcallbackとして渡した場合です if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg);//最後に自分で実装したhandleMessage処理を行います } }
2.3 java層 メッセージ送信
私たちがjava層のメッセージ送信を見てみると、主にHandlerのsendMessage postなどの関数を呼び出し、最終的には以下の関数を呼び出します。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException(" this + 「sendMessageAtTime()がmQueueなしで呼び出されました」; Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
さらに、java層から送信されるメッセージは最終的にはenqueueMessage関数が呼び出されます
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
最終的にはenqueueMessage内で、メッセージをメッセージキューに追加し、必要に応じてcレベルのnativeWake関数を呼び出します
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("メッセージにはターゲットが必要です。"); } if (msg.isInUse()) { throw new IllegalStateException(msg + 「このメッセージは既に使用されています」); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException(" msg.target + 「送信先スレッドのHandlerにメッセージを送信しています」); 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) { // 新しいヘッドがあれば、ブロックされた場合にイベントキューをwakeアップさせます。 msg.next = p; mMessages = msg; needWake = mBlocked; } else { // キューの中間に挿入されています。通常、wakeを必要としません // キューの先頭にバリアがあれば、イベントキューを上昇させません // そして、メッセージはキューの中で最も早い非同期メッセージです。 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // mQuittingがfalseであるため、mPtr != 0と仮定できます。 if (needWake) { nativeWake(mPtr); } } return true; }
このネイティブメソッドを見てみると、最後にLooperのwake関数も呼び出しています
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->wake();}} } void NativeMessageQueue::wake() { mLooper->wake();}} }
Looperクラスのwake関数は、mWakeEventfdにいくつかの内容を書き込むだけであり、このfdは通知用にのみ使用され、pipeに似ています。最終的にepoll_waitが唤醒され、スレッドはブロックされずに続行し、c層のメッセージを最初に送信し、以前にaddFdに追加されたイベントを処理し、java層のメッセージを処理します。
void Looper::wake() { #if DEBUG_POLL_AND_WAKE ALOGD("%p ~ wake", this); #endif uint64_t inc = 1; ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t))); if (nWrite != sizeof(uint64_t)) { if (errno != EAGAIN) { ALOGW("Could not write wake signal, errno=%d", errno); } } }
2.4 c層でのメッセージ送信
在c層でもメッセージを送信できますが、主にLooperのsendMessageAtTime関数を呼び出します。引数にはhandlerがコールバックとしてあります。メッセージはmMessageEnvelopesに格納されます。
void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler, const Message& message) { #if DEBUG_CALLBACKS ALOGD("%p ~ sendMessageAtTime - uptime=%" PRId64 ", handler=%p, what=%d", this, uptime, handler.get(), message.what); #endif size_t i = 0; { // acquire lock AutoMutex _l(mLock); size_t messageCount = mMessageEnvelopes.size(); while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) { i += 1; } MessageEnvelope messageEnvelope(uptime, handler, message); mMessageEnvelopes.insertAt(messageEnvelope, i, 1); // 最適化:Looperが現在メッセージを送信している場合、 // wake()の呼び出しをスキップすることができます。なぜなら、Looperが次に処理するのは // messagesは次のwakeアップ時間を決定するためにあります。実際には、 // このコードがLooperスレッド上で実行されているかどうかに関係なく、 if (mSendingMessage) { return; } } // ロックを解放 // 新しいメッセージを先頭にキューイングする際にのみpollループをwakeします。 if (i == 0) { wake(); } }
pollOnceの中で、epoll_waitの後、mMessageEnvelopesに含まれるメッセージを巡回し、そのhandlerのhandleMessage関数を呼び出します
while (mMessageEnvelopes.size() != 0) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0); if (messageEnvelope.uptime <= now) { // エンベロープをリストから削除します。 // handleMessageの呼び出しまでhandlerに強い参照を保持します。 // 終わる前に、handlerを削除できるようにそれをドロップします *before* // 私たちはロックを再取得します。 { // handlerを取得する sp<MessageHandler> handler = messageEnvelope.handler; Message message = messageEnvelope.message; mMessageEnvelopes.removeAt(0); mSendingMessage = true; mLock.unlock(); #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce"} - sending message: handler=%p, what=%d", this, handler.get(), message.what); #endif handler->handleMessage(message); } // release handler mLock.lock(); mSendingMessage = false; result = POLL_CALLBACK; } else { // キューの先頭に残された最後のメッセージが次のウェイクアップ時間を決定します。 mNextMessageUptime = messageEnvelope.uptime; break; } }
Looper_test.cppファイルにはLooperの使用方法が多く記載されています。それを見てみましょう
sp<StubMessageHandler> handler = new StubMessageHandler(); mLooper->sendMessageAtTime(now + ms2ns(100), handler, Message(MSG_TEST1)); StubMessageHandlerがMessageHandlerを継承するにはhandleMessageメソッドを実装する必要があります class StubMessageHandler : public MessageHandler { public: Vector<Message> messages; virtual void handleMessage(const Message& message) { messages.push(message); } };
MessageとMessageHandlerクラスについても簡単に見てみましょう
struct Message { Message() : what(0) { } Message(int what) : what(what) { } /* メッセージの種類。(インタプリテーションはハンドラに任せられる) */ int what; }; /** * Looperメッセージハンドラのためのインターフェース。 * * Looperはメッセージハンドラに対して強い参照を持つたびに、 * ハンドラに渡すメッセージを送信します。Looper::removeMessagesを呼び出すことを確認してください。 * ハンドラに向けて送られる未処理のメッセージを取り除くために、ハンドラ * 破棄可能です。 */ class MessageHandler : public virtual RefBase { protected: virtual ~MessageHandler() { } public: /** * メッセージを処理します。 */ virtual void handleMessage(const Message& message) = 0; };
2.5 c層addFd
Looper.cppのaddFdにfdをスレッドepollに追加することもできます。fdにデータが来たら、対応するデータを処理することもできます。まず、addFd関数を見てみましょう。callBackコールバックがあることに注意してください。
int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) { return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data); } int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) { #if DEBUG_CALLBACKS ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident, events, callback.get(), data); #endif if (!callback.get()) { if (!mAllowNonCallbacks) { ALOGE("NULLコールバック設定の無効な試みですが、このループアーで許可されていません"); return -1; } if (識別子 < 0) { ALOGE("無効なNULLコールバック設定試みです。識別子 < 0"); return -1; } } else { ident = POLL_CALLBACK; } { // acquire lock AutoMutex _l(mLock); Request request; request.fd = fd; request.ident = ident; request.events = events; request.seq = mNextRequestSeq++; request.callback = callback; request.data = data; if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1 struct epoll_event eventItem; request.initEventItem(&eventItem); ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex < 0) { int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);//加入epoll ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno); return -1; } mRequests.add(fd, request);//放入mRequests中 } else { int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);//更新 if (errno == ENOENT) { // ENOENTを許容します。なぜなら、古いファイルディスクリプタが // コールバックが未登録の前に閉じられており、その間に新しい // 同じ番号のファイルディスクリプタが作成され、現在は // 初めて登録されている場合に発生するエラーです。 // コールバックがサイド効果を持つときに-ファイルディスクリプタを閉じる効果 // 返却前に自身を登録解除する前に、コールバックシーケンス番号 // チェックは、競合が無害であることをさらに確実にします。 // // 残念ながら、カーネルの制限のため、epollを再構築する必要があります // 再設定する必要があります。それは、私たちが保持している旧ファイルハンドルが含まれている可能性があります // 今は削除することができません。ファイルディスクリプタがもはや有効でないためです。 // pollシステムを使用していたら、このような問題は発生しなかったでしょう // call instead, but that approach carries others disadvantages. #if DEBUG_CALLBACKS ALOGD("%p ~ addFd - EPOLL_CTL_MODがファイルディスクリプタ" "リサイクル中、EPOLL_CTL_ADDに戻る、エラーコード=%d" this, errno); #endif epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem); ALOGE("エラーデータがfd %dのepollイベントを変更または追加中に発生しました、エラーコード=%d" fd, errno); return -1; } scheduleEpollRebuildLocked(); } else { ALOGE("エラーデータがfd %dのepollイベントを変更中に発生しました、エラーコード=%d", fd, errno); return -1; } } mRequests.replaceValueAt(requestIndex, request); } } // ロックを解放 return 1; }
pollOnce関数内で、まずmRequests内の一致するfdを探し、pushResponse内で新しいResponseを作成し、ResponseとRequestをマッチングします。
} else { ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex >= 0) { int events = 0; if (epollEvents & EPOLLIN) events |= EVENT_INPUT; if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT; if (epollEvents & EPOLLERR) events |= EVENT_ERROR; if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP; pushResponse(events, mRequests.valueAt(requestIndex)); } else { ALOGW("fd %d 上の予期しない epollイベント 0x%x を無視しています ", fd); "no longer registered.", epollEvents, fd); } }
以下は、mResponses内のResponseを巡回し、そのrequestの回调を呼び出すことになります。
for (size_t i = 0; i < mResponses.size(); i++) { Response& response = mResponses.editItemAt(i); if (response.request.ident == POLL_CALLBACK) { int fd = response.request.fd; int events = response.events; void* data = response.request.data; #if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS ALOGD("%p ~ pollOnce"} - invoking fd event callback %p: fd=%d, events=0x%x, data=%p", this, response.request.callback.get(), fd, events, data); #endif // callbackを呼び出します。ファイルディスクリプタが閉じられる可能性があることに注意してください。 // 関数が返される前にcallback(そして再利用される可能性のある)を呼び出しますので、 // その後のファイルディスクリプタの削除時に少し注意を払う必要があります。 int callbackResult = response.request.callback->handleEvent(fd, events, data); if (callbackResult == 0) { removeFd(fd, response.request.seq); } // 私たちがすぐにresponse構造体のcallbackリファレンスをクリアする必要があります // 次のpollまで、response vector自体をクリアしません。 response.request.callback.clear(); result = POLL_CALLBACK; } }
それでは、Looper_test.cppがどのように使用されているかもう一度見てみましょう?
Pipe pipe; StubCallbackHandler handler(true); handler.setCallback(mLooper, pipe.receiveFd, Looper::EVENT_INPUT);
私たちがhandlerのsetCallback関数を見てみましょう
class CallbackHandler { public: void setCallback(const sp<Looper>& looper, int fd, int events) { looper->addFd(fd, 0, events, staticHandler, this);//就是调用了looper的addFd函数,并且回调 } protected: virtual ~CallbackHandler() { } virtual int handler(int fd, int events) = 0; private: static int staticHandler(int fd, int events, void* data) {//这个就是回调函数 return static_cast(data)->handler(fd, events); } }; class StubCallbackHandler : public CallbackHandler { public: int nextResult; int callbackCount; int fd; int events; StubCallbackHandler(int nextResult) : nextResult(nextResult), callbackCount(0), fd(-1), events(-1) { } protected: virtual int handler(int fd, int events) {//这个是通过回调函数再调到这里的 callbackCount += 1; this->fd = fd; this->events = events; return nextResult; } };
我们结合Looper的addFd一起来看,当callback是有的,我们新建一个SimpleLooperCallback
int Looper::addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data) { return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data); }
ここにはLooper_callbackFuncがtypedefされています
typedef int (*Looper_callbackFunc)(int fd, int events, void* data);
そしてSimpleLooperCallbackを見てみましょう
class SimpleLooperCallback : public LooperCallback { protected: virtual ~SimpleLooperCallback(); public: SimpleLooperCallback(Looper_callbackFunc callback); virtual int handleEvent(int fd, int events, void* data); private: Looper_callbackFunc mCallback; };SimpleLooperCallback::SimpleLooperCallback(Looper_callbackFunc callback) : mCallback(callback) { } SimpleLooperCallback::~SimpleLooperCallback() { } int SimpleLooperCallback::handleEvent(int fd, int events, void* data) { return mCallback(fd, events, data); }
最終的にはcallbackが呼ばれます->handleEvent(fd, events, data)が呼ばれ、callbackはSimpleLooperCallback、このdataは先ほど渡されたCallbackHandlerのthisポインタです
したがって最終的にはstaticHandlerが呼ばれ、data->handler、これはthis->handler、最後に仮想関数が呼ばれ、StubCallbackHandlerのhandler関数に移行します。
もちろん、複雑にすることなく、第2のaddFd関数を直接使用することもできます。もちろん、callBackは自分でクラスを定義してLooperCallBackクラスを実現すれば、もっと簡単になります。
int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);
2.6 java層のaddFd
いつもc層のLooperの中でだけaddFdができると思っていましたが、実はjava層でもjniを通じてこの機能が提供されています。
MessageQueueのaddOnFileDescriptorEventListenerを使ってこの機能を実現できます
public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd, @OnFileDescriptorEventListener.Events int events, @NonNull OnFileDescriptorEventListener listener) { if (fd == null) { throw new IllegalArgumentException("fd must not be null"); } if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } synchronized (this) { updateOnFileDescriptorEventListenerLocked(fd, events, listener); } }
それではOnFileDescriptorEventListenerというコールバックについてもう少し見てみましょう
public interface OnFileDescriptorEventListener { public static final int EVENT_INPUT = 1 << 0; public static final int EVENT_OUTPUT = 1 << 1; public static final int EVENT_ERROR = 1 << 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag=true, value={EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR}) public @interface Events {} @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events); }
次にupdateOnFileDescriptorEventListenerLocked関数を呼び出しました
private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,) OnFileDescriptorEventListener listener) { final int fdNum = fd.getInt$(); int index = -1; FileDescriptorRecord record = null; if (mFileDescriptorRecords != null) { index = mFileDescriptorRecords.indexOfKey(fdNum); if (index >= 0) { record = mFileDescriptorRecords.valueAt(index); if (record != null && record.mEvents == events) { return; } } } if (events != 0) { events |= OnFileDescriptorEventListener.EVENT_ERROR; if (record == null) { if (mFileDescriptorRecords == null) { mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>(); } record = new FileDescriptorRecord(fd, events, listener);//fdはFileDescriptorRecordオブジェクトに保存されます mFileDescriptorRecords.put(fdNum, record);//mFileDescriptorRecordsに保存されました } else { record.mListener = listener; record.mEvents = events; record.mSeq += 1; } nativeSetFileDescriptorEvents(mPtr, fdNum, events);//native関数を呼び出します } else if (record != null) { record.mEvents = 0; mFileDescriptorRecords.removeAt(index); } }
native最後にNativeMessageQueueのsetFileDescriptorEvents関数を呼び出しました
static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz, jlong ptr, jint fd, jint events) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->setFileDescriptorEvents(fd, events); }
setFileDescriptorEvents関数、このaddFdは二つ目のaddFdを呼び出しているため、NativeMessageQueueがLooperCallbackを継承していることが確かです
void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) { if (events) { int looperEvents = 0; if (events & CALLBACK_EVENT_INPUT) { looperEvents |= Looper::EVENT_INPUT; } if (events & CALLBACK_EVENT_OUTPUT) { looperEvents |= Looper::EVENT_OUTPUT; } mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this, reinterpret_cast<void*>(events)); } else { mLooper->removeFd(fd); } }
果たして、handleEvent関数を実装する必要があります
class NativeMessageQueue : public MessageQueue, public LooperCallback { public: NativeMessageQueue(); virtual ~NativeMessageQueue(); virtual void raiseException(JNIEnv* 環境、const char* msg, jthrowable exceptionObj); void pollOnce(JNIEnv* env, jobject obj, int timeoutMillis); void wake(); void setFileDescriptorEvents(int fd, int events); virtual int handleEvent(int fd, int events, void* data);
handleEventはlooperのepoll_waitの後で、追加したfdにデータがあるとこの関数が呼び出されます
int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) { int events = 0; if (looperEvents & Looper::EVENT_INPUT) { events |= CALLBACK_EVENT_INPUT; } if (looperEvents & Looper::EVENT_OUTPUT) { events |= CALLBACK_EVENT_OUTPUT; } if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) { events |= CALLBACK_EVENT_ERROR; } int oldWatchedEvents = reinterpret_cast<intptr_t>(data); int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj, gMessageQueueClassInfo.dispatchEvents, fd, events); //コールバックを呼び出します if (!newWatchedEvents) { return 0; // fdを登録解除します } if (newWatchedEvents != oldWatchedEvents) { setFileDescriptorEvents(fd, newWatchedEvents); } return 1; }
最終的にjavaのMessageQueueのdispatchEventsはJNIレベルで逆呼び出されるもので、それから以前に登録されたコールバック関数を呼び出します
// ネイティブコードから呼び出されます。 private int dispatchEvents(int fd, int events) { // ファイルディスクリプタレコードと変更される可能性のあるあらゆる状態を取得します。 final FileDescriptorRecord record; final int oldWatchedEvents; final OnFileDescriptorEventListener listener; final int seq; synchronized (this) { record = mFileDescriptorRecords.get(fd);//fdを通じてFileDescriptorRecordを取得します if (record == null) { return 0; // 虚警、リスナーが登録されていません } oldWatchedEvents = record.mEvents; events &= oldWatchedEvents; // 現在の監視セットに基づいてイベントをフィルタリングします if (events == 0) { return oldWatchedEvents; // 虚警、監視イベントが変更されました } listener = record.mListener; seq = record.mSeq; } // ロックの外でリスナーを呼び出します。 int newWatchedEvents = listener.onFileDescriptorEvents(//リスナーのコールバック record.mDescriptor, events); if (newWatchedEvents != 0) { newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR; } // リスナーがファイルディスクリプタセットを変更した場合、ファイルディスクリプタレコードを更新します。 // 監視するイベントとリスナー自体が更新されていないままのものです。 if (newWatchedEvents != oldWatchedEvents) { synchronized (this) { int index = mFileDescriptorRecords.indexOfKey(fd); if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record && record.mSeq == seq) { record.mEvents = newWatchedEvents; if (newWatchedEvents == 0) { mFileDescriptorRecords.removeAt(index); } } } } // ネイティブコードが対応するための新しいイベントセットを返します。 return newWatchedEvents; }
これで本文のすべての内容が終わりです。皆様の学習に役立つことを願っています。また、呐喊教程を多くのサポートをお願いします。
声明:本文の内容はインターネットから取得しており、著作権者は所有しており、インターネットユーザーにより自発的に貢献し、自己でアップロードされたものであり、本サイトは所有権を持ちません。また、人工的な編集は行われておらず、関連する法的責任も負いません。著作権侵害が疑われる内容がある場合は、以下のメールアドレスまでご連絡ください:notice#oldtoolbag.com(メールを送信する際には、#を@に変更してください。届出を行い、関連する証拠を提供してください。一旦確認がついた場合、本サイトは侵害疑いの内容をすぐに削除します。)