1. 程式人生 > >從原始碼的角度解析Handler、Looper、Message和MessageQueue

從原始碼的角度解析Handler、Looper、Message和MessageQueue

導語

雖然很基礎的一個東西,然是最近面試中還是常常最被問到,而且都是到原始碼層,因此決定再造一次輪子!

這裡寫圖片描述

作為一名Android程式猿,想必在最開始都碰到這麼一個問題,就是在子執行緒中彈出一個 Toast,會丟擲以下的異常:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler
.<init>(Handler.java:121) at android.widget.Toast$TN.<init>(Toast.java:322) at android.widget.Toast.<init>(Toast.java:91) at android.widget.Toast.makeText(Toast.java:238) at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:25) at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:21
)

按傳統的說法就是 Toast 只能在UI執行緒中顯示,實際上不是的,應該是 Toast 只能在帶有 Looper 的執行緒中顯示。

另一個常見的場景,就是在子執行緒建立一個 Handler 也會丟擲與上述一樣的異常:

Java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
    at android.os.Handler.<init>(Handler.java:121)

而在主執行緒為什麼不會有該異常呢?這就是原始碼的東西了,在程式入口方法已經呼叫過以下程式碼,來建立一個主執行緒了:

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false);

Looper.loop();

這個時候疑問就來了,什麼是 Looper?

先來看一張圖,初步瞭解一下 HandlerLooperMessageMessageQueue 之間的關係

這裡寫圖片描述

對於一個執行緒來說,Handler 允許傳送和處理與該執行緒的 MessageQueue 相關聯的 MessageRunnable 物件。每一個 Handler 例項與單個執行緒的 MessageQueue 相關聯。

當我們建立一個 Handler 例項的時候,它將會繫結到當前所處的執行緒及其對應的訊息佇列,然後就是往訊息佇列裡面傳遞訊息或者 Runabble物件,並在訊息佇列中取出訊息來處理,或者取出 Runnable 物件進行執行!

Handler

Handler 作用

從本質上來講,Handler 主要有以下兩個作用

  • 排程訊息和runnable物件去被執行,換句話說,就是在同一個執行緒中處理一些訊息
  • 使得某個訊息動作在不同執行緒中執行,也就是往其他執行緒的訊息佇列裡插入訊息

訊息的排程主要有以下幾種方式:

  • post(Runnable)
  • postAtTime(Runnable,long)
  • postDelayed(Runnable,long)
  • sendMessage(Message)
  • sendMessageAtTime(Message,long)
  • sendMessageDelayed(Message,long)
  • sendEmptyMessage(int)
  • sendEmptyMessageAtTime(int, long)
  • sendEmptyMessageDelayed(int, long)

最後實際上都是呼叫 sendEmptyMessageAtTime(Message,long) 方法

從上面的這些方法中可以看出:

  • post開頭的幾個方法,允許將 Runnable 物件插入到訊息佇列以便呼叫。
  • sendMessage 對應的幾個方法,可以將 Message 插入到 MessageQueue,然後通過 HandlerhandleMessage 方法來處理相應的訊息。

Message

Message 結構

Message 類主要包含以下幾個引數

public int what; // sendEmptyMessage 裡面的 what,在 ```handleMessage``` 方法可以對不同的 Message.what 值做相應處理。

public Object obj; // Message 可以攜帶一個物件

Handler target; // 處理該訊息的Handler

Message next;

Runnable callback; // 訊息處理動作

1、從next引數可知,訊息佇列實際上是一個連結串列結構;

2、來看一下 Handler 的 dispatchMessage 方法:

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

從中我們可以知道,如果 Message 有定義 callback,那麼訊息處理會交由callback 去執行,否則,交由 Handler 的 handleMessage 去執行。

Message 建立及傳送

一般傳送一條訊息,我們會呼叫一下程式碼:

handler.obtainMessage(int what, Object obj).sendToTarget();

那麼,我們就簡單分析一下訊息建立的流程

1、Handler.obtainMessage

public final Message obtainMessage(int what, Object obj)
{
    return Message.obtain(this, what, obj);
}

2、 Message.obtain 建立訊息

public static Message obtain(Handler h, int what, Object obj) {
    Message m = obtain();
    m.target = h; // 指定了處理訊息的Handler
    m.what = what;
    m.obj = obj;

    return m;
}

3、 Message.sendToTarget 傳送訊息

public void sendToTarget() {
    target.sendMessage(this); // 呼叫 Handler的sendMessage
}

4、Handler.sendMessage(Message) 傳送訊息,最後實際上是呼叫sendMessageAtTime方法,往MessageQueue裡面插入一條訊息

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

MessageQueue 結構

private final boolean mQuitAllowed; // 是否允許MessageQueue退出;

private long mPtr; // MessageQueue 是通過呼叫 C++ native MessageQueue 實現的,這個 mPtr 就是指向 native MessageQueue;

Message mMessages; // 表示儲存訊息連結串列的 Head

private boolean mQuitting; // 當前MessageQueue是否正在終止;

private boolean mBlocked; // 表示next()方法呼叫是否被block在timeout不為0的pollOnce上;

MessageQueue 主要包含兩個操作:插入和讀取。讀取操作本身會伴隨著刪除操作,插入和讀取對應的方法分別是 enqueueMessage()next()

插入訊息

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        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;
        // 插入到連結串列的頭部,條件:連結串列為null或者當前訊息的對應的觸發時間比連結串列頭的觸發時間小,也就是比連結串列頭早執行
        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 {
            // 通過觸發時間,將訊息插入到佇列中合適的位置
            // 如果需要喚醒執行緒處理則呼叫C++中的nativeWake()函式.
            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;
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

讀取訊息

訊息迴圈讀取,是在 Looper.loop() 方法呼叫之後,最後來執行 MessageQueue.next() 方法,我們來看一下該方法:

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    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);

        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) {
                // 過濾掉同步訊息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // 還未達到下一條訊息的觸發時間,為下一條待處理的訊息設定就緒時間 
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

通過上面原始碼可知:

首先會去判斷handler是否為null,是的話就跳過所有的同步訊息,查詢到需要最先處理的非同步訊息。如果第一個待處理的訊息還沒有到要觸發時間,則設定啟用等待時間;否則這個訊息就是需要處理的訊息,將該訊息設定為 inuse狀態,並將佇列設定為非 blocked 狀態,然後返回該訊息。

next() 方法是一個無限迴圈的方法,如果訊息佇列中沒有訊息,那麼 next() 方法會一直阻塞,當有新訊息到來時,next() 會將這條訊息返回同時也將這條訊息從連結串列中移除。

Looper

首先,在理解 Looper 之前,我們需要稍微瞭解一下 ThreadLocal 這個類。

ThreadLocal 是用於為每個執行緒建立一個單獨的變數副本,提供了保持物件的方法和避免參數傳遞的複雜性。ThreadLocal 類有一個泛型引數,設定了儲存到 ThreadLocal 容器中的資料型別。

實際上在 ThreadLocal 類中有一個靜態內部類 ThreadLocalMap (其類似於Map),用鍵值對的形式儲存每一個執行緒的變數副本,ThreadLocalMap 中元素的key為當前 ThreadLocal 物件,而value對應執行緒的變數副本,每個執行緒可能存在多個 ThreadLocal

那麼,在 Looper中,也儲存該著為每個執行緒單獨建立的 ThreadLocal,裡面儲存著該執行緒對應的 Looper

Looper 建立

我們來看一下 Looper.prepare() 方法:


static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        // 這也就意味著 prepare 方法,建立了當前執行緒的一個 Looper,並且每個執行緒 只能建立一次
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

Looper 開啟迴圈

來看一下 Looper.loop() 方法:

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the 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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        // 從佇列中取出一條訊息
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            // 分發訊息給 Handler 處理
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // 回收釋放
        msg.recycleUnchecked();
    }
}

執行緒阻塞問題

細心的童鞋就會有個疑問,主執行緒對應的這個 Looper,在呼叫 Looper.loop() 方法之後,開啟了無限死迴圈,那麼為什麼不會造成執行緒阻塞,導致 UI 動不了?

這個問題實際上就需要了解一下 Activity 的啟動過程了。這裡就不細說了,主要先了解一下 ActivityThread.H 這個類,它是繼承於 Handler,我們可以看一下他的訊息處理:

private final class H extends Handler {

    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);
        switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                ActivityClientRecord r = (ActivityClientRecord)msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo);
                handleLaunchActivity(r, null);
            } break;
            case RELAUNCH_ACTIVITY: {
                ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                handleRelaunchActivity(r, msg.arg1);
            } break;
            case PAUSE_ACTIVITY:
                handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);
                maybeSnapshot();
                break;
            case PAUSE_ACTIVITY_FINISHING:
                handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2);
                break;
            case STOP_ACTIVITY_SHOW:
                handleStopActivity((IBinder)msg.obj, true, msg.arg2);
                break;
            case STOP_ACTIVITY_HIDE:
                handleStopActivity((IBinder)msg.obj, false, msg.arg2);
                break;
            case SHOW_WINDOW:
                handleWindowVisibility((IBinder)msg.obj, true);
                break;
            case HIDE_WINDOW:
                handleWindowVisibility((IBinder)msg.obj, false);
                break;
            case RESUME_ACTIVITY:
                handleResumeActivity((IBinder)msg.obj, true,
                        msg.arg1 != 0);
                break;
            case SEND_RESULT:
                handleSendResult((ResultData)msg.obj);
                break;
            case DESTROY_ACTIVITY:
                handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
                        msg.arg2, false);
                break;
            case BIND_APPLICATION:
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            case NEW_INTENT:
                handleNewIntent((NewIntentData)msg.obj);
                break;
            case RECEIVER:
                handleReceiver((ReceiverData)msg.obj);
                maybeSnapshot();
                break;
            case CREATE_SERVICE:
                handleCreateService((CreateServiceData)msg.obj);
                break;
            case BIND_SERVICE:
                handleBindService((BindServiceData)msg.obj);
                break;
            case UNBIND_SERVICE:
                handleUnbindService((BindServiceData)msg.obj);
                break;
            case SERVICE_ARGS:
                handleServiceArgs((ServiceArgsData)msg.obj);
                break;
            case STOP_SERVICE:
                handleStopService((IBinder)msg.obj);
                maybeSnapshot();
                break;
            case REQUEST_THUMBNAIL:
                handleRequestThumbnail((IBinder)msg.obj);
                break;
            case CONFIGURATION_CHANGED:
                handleConfigurationChanged((Configuration)msg.obj);
                break;
            case CLEAN_UP_CONTEXT:
                ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
                cci.context.performFinalCleanup(cci.who, cci.what);
                break;
            case GC_WHEN_IDLE:
                scheduleGcIdler();
                break;
            case DUMP_SERVICE:
                handleDumpService((DumpServiceInfo)msg.obj);
                break;
            case LOW_MEMORY:
                handleLowMemory();
                break;
            case ACTIVITY_CONFIGURATION_CHANGED:
                handleActivityConfigurationChanged((IBinder)msg.obj);
                break;
            case PROFILER_CONTROL:
                handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj);
                break;
            case CREATE_BACKUP_AGENT:
                handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
                break;
            case DESTROY_BACKUP_AGENT:
                handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
                break;
            case SUICIDE:
                Process.killProcess(Process.myPid());
                break;
            case REMOVE_PROVIDER:
                completeRemoveProvider((IContentProvider)msg.obj);
                break;
            case ENABLE_JIT:
                ensureJitEnabled();
                break;
            case DISPATCH_PACKAGE_BROADCAST:
                handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
                break;
            case SCHEDULE_CRASH:
                throw new RemoteServiceException((String)msg.obj);
        }
        if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
    }
}

那麼,對於建立一個Service來說,我們看一下 ApplicationThread 的排程:

public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
    updateProcessState(processState, false);
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;

    // 傳送建立 Service 的訊息
    sendMessage(H.CREATE_SERVICE, s);
}

不難看出,實際上建立 Service、包括執行其生命週期,最後都是交由 ActivityThread.H 處理,包括 Activity 的生命週期,也是一樣,所以 Looper 雖然死迴圈,但是本質上我們UI的展示、更新,也是通過 Handler 來處理了,所以並不會造成真正的UI阻塞。

所以,簡單來講,ActivityThread 實際上就是開啟了一個訊息迴圈,來執行 ActivityService 等等的相關操作,一旦這個訊息迴圈停止了,則意味著App程序也結束了。

但是,如果 handlerMessage 是在主執行緒執行,其處理儘可能不要執行耗時操作,避免UI卡頓或發生 ANR。

結語

最後,再回過頭去看看前文中的那張圖,相信你就能基本理解了!

感謝閱讀!