blog

Androidのメッセージング機構のコア

Androidのメッセージングメカニズムを理解している人なら、ハンドラーメッセージングメカニズムを使うには、まずprepare()を呼び出す必要があることを知っています。 prepare()はオブジェ...

Apr 24, 2020 · 5 min. read
シェア

frameworks/base/core/java/android/os/Looper.java

final MessageQueue mQueue;
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));
}
private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed);
 mThread = Thread.currentThread();
}

Androidのメッセージ機構を理解している人は、ハンドラのメッセージ機構を使う場合、まずLooper.prepare()を呼び、最後にLooper.loop()を呼びます。

  • prepare() は、MessageQueue オブジェクトを作成します。
  • loop() は無限ループに入り、メッセージを取得しようと MessageQueue.next() メソッドを呼び出し続けます。

従来のメッセージング・フレームワークには、メッセージ、メッセージ・キュー、メッセージ・センター、メッセージ・プロデューサー、メッセージ・コンシューマーがあります。一般的にメッセージ・センターはループとして設計され、無限ループでメッセージ・キューからメッセージを読み込みます。メッセージ・キューが空になると、しばらくスリープします。

frameworks/base/core/java/android/os/MessageQueue.java

MessageQueue(boolean quitAllowed) {
 mQuitAllowed = quitAllowed;
 mPtr = nativeInit();
}
// メッセージをメッセージ・キューに入れ、nativeWake()を呼び出してメッセージ・ハンドラをウェイクアップする。Looper
boolean enqueueMessage(Message msg, long when) {
 ... ...
 synchronized (this) {
 ... ...
 // We can assume mPtr != 0 because mQuitting is false.
 if (needWake) {
 nativeWake(mPtr);
 }
 }
 return true;
}
// メッセージ・キューからメッセージを読み込もうとし、メッセージ・キューが空であれば、nativePollOnce()を呼び出してメッセージが到着するのを待つ。
Message next() {
 ... ...
 int pendingIdleHandlerCount = -1; // -1 only during first iteration
 int nextPollTimeoutMillis = 0;
 for (;;) {
 nativePollOnce(ptr, nextPollTimeoutMillis);
 ... ...
 }
 }

nativeInit/nativeWake/nativePollOnceここには3つのネイティブメソッドがあり、JNIでは次のようになります。

frameworks/base/core/jni/android_os_MessageQueue.cpp

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);
}
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);
}
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
 NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
 nativeMessageQueue->wake();
}

NativeMessageQueue をインスタンス化し、それぞれ pollOnce/wake メソッドを呼び出します。

NativeMessageQueue::NativeMessageQueue() :
 mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
 mLooper = Looper::getForThread();
 if (mLooper == NULL) {
 mLooper = new Looper(false);
 Looper::setForThread(mLooper);
 }
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
 mPollEnv = env;
 mPollObj = pollObj;
 mLooper->pollOnce(timeoutMillis);
 mPollObj = NULL;
 mPollEnv = NULL;
 if (mExceptionObj) {
 env->Throw(mExceptionObj);
 env->DeleteLocalRef(mExceptionObj);
 mExceptionObj = NULL;
 }
}
void NativeMessageQueue::wake() {
 mLooper->wake();
}

LooperのpollOnce/wakeメソッドのさらなる呼び出し。

system/core/libutils/Looper.cpp

Looper::Looper(bool allowNonCallbacks) :
 mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
 mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
 mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
 // ウェイクアップ用のイベントハンドラmWakeEventFdを作成する。
 mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
 rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
 // エポールハンドルの作成
 mEpollFd = epoll_create(EPOLL_SIZE_HINT);
 struct epoll_event eventItem;
 memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
 eventItem.events = EPOLLIN;
 eventItem.data.fd = mWakeEventFd;
 // 書き込みイベントをリッスンするために、epollリスナーにmWakeEventFdを追加する。
 int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
 int result = 0;
 for (;;) {
 ... ...
 result = pollInner(timeoutMillis);
 }
}
int Looper::pollInner(int timeoutMillis) {
 struct epoll_event eventItems[EPOLL_MAX_EVENTS];
 // イベントを待ち受ける
 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
 ... ...
 return result;
}
void Looper::wake() {
 uint64_t inc = 1;
 // メッセージキューをウェイクアップするためにmWakeEventFdにデータを書き込む。
 // このメソッドを呼び出した後epoll_waitこの関数は、コールチェーンのすべての段階でpollInner->pollOnce->pollOnce->android_os_MessageQueue_nativePollOnce->nativePollOnce->next
 // このときLooper.loop()next()の呼び出しは戻り、ループ本体は実行を続ける。
 ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
}

AndroidのHandlerメッセージングメカニズムのメッセージループのコア実装です。Handlerのメッセージループは、Linuxカーネルのepollメカニズムを利用しており、メッセージキューが空になるとハングしてCPUリソースを解放し、メッセージキューに新しいメッセージが到着した時だけ、メッセージ処理のためのCPUリソースを取り戻します。Handlerのメッセージループは、メッセージキューが空になったときにハングしてCPUリソースを解放するためにLinuxカーネルのepollメカニズムを使用します。

従来の実装と比較して、Androidのハンドラーメッセージングメカニズムはより多くのシステムリソースを節約します。

従来の実装と比較して、AndroidのHandlerメッセージングメカニズムのプロデューサーは、メッセージをメッセージキューに入れながら、メッセージハンドラに通知することができます。

Read next

h5ページのパフォーマンス指標を解釈する

H5ページの読み込みプロセスは、主に2つの部分を含んでいます:H5ページコンテナの読み込みとリソースファイルの読み込み。 ブラウザウィンドウの下のデータメトリクスを比較します。 最大ペイント:ページのロードが開始されてから、最大のテキストブロックまたは画像要素が画面にレンダリングされるまでの時間を測定します。 フルパッケージのロード経過時間 =...

Apr 24, 2020 · 4 min read