Android多線程源碼詳解一:handler、looper、message、messageQueue

以前面試,面試官問到多線程通信,巴拉巴拉說了些基礎實現後面試官問handlerThread的底層實現,就卡住了。因此把Android多線程的知識點複習整理一下,寫出來加深印象。javascript

Android多線程通信的核心是handler、looper、message、messageQueue,這篇文章就先記錄下這套系統的源碼要點,具體的實現方法下一篇文章再寫。java

內容爲本身看源碼的理解,若有問題,歡迎留言探討,共同進步。面試

Thread

用法一:算法

handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1:
                        mThread.setText(msg.obj.toString());
                }
            }
        };
        ...
new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("coco", "thread:" + Thread.currentThread().getName());
                        Message message = handler.obtainMessage();
                        message.obj = "thread_msg";
                        message.what = 1;
                        handler.sendMessage(message);
                    }
                }).start();
主線程中初始化handler,實現handleMessage,子線程中sendMessage,實現通信。(ps:handler內存泄漏後面寫)

**方法二:**

handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Message message = Message.obtain(handler);
                        message.obj = "thread_msg1";
                        message.what = 1;
                        handler.sendMessage(message);
                    }
                });
複製代碼

這種方法跟第一種實現原理是同樣的,直接返回sendMessageDelayed(getPostMessage(r), 0),經過getPostMessage從Runnable中獲取message,而後放到messageQueue中。數據結構

handler

handler是多線程通信的控制器,負責消息的發送與處理,handler的初始化代碼以下:多線程

//FIND_POTENTIAL_LEAKS爲常量,值爲false,即第一個if語句不會執行。內部的代碼邏輯是判斷handler的建立方式,決定是否須要打
//印內存泄漏的log,若是是該handler對象是經過匿名類、成員類、內部類、非靜態類的話,有可能形成內存泄漏,須要打印log
if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
//首先對looper進行判空,若是爲空就拋出異常,因此若是在子線程中初始化handler,必定要先初始化looper,主線程在系統建立時就初
//始化了looper,因此能夠直接建立handler。
mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
複製代碼

mQueue是獲取的mLooper的mQueue,因此mQueue也是當前線程相關的,具體緣由在looper的源碼分析中會講。mAsynchronous是判斷是否有異步消息,Android會優先處理異步消息,具體的實如今messageQueue中會講到。異步

public final Message obtainMessage() {
        return Message.obtain(this);
    }
複製代碼

obtainMessage方法是從message的公共池中取出一個message,相對於直接new出來,效率更高。async

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

dispatchMessage方法是handler在接收到message後進行分發時調用的,msg.callback是一個Runnable對象,在message建立時傳入,或者經過setCallback方法設置,默認爲空;mCallback是Callback對象,在handler初始化的時候傳入,默認也爲空。因此沒有特定設置的狀況下,會直接走到handlerMessage中,即咱們建立handler時複寫的回調方法。ide

looper

looper的主要成員變量以下:函數

MessageQueue mQueue跟looper綁定的消息隊列。

Thread mThreadlooper所在線程對象。

looper的初始化代碼以下:

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));
    }
複製代碼

sThreadLocal的數據結構爲ThreadLocal
,在prepare中首先判斷sThreadLocal是否爲空,代表一個線程只能有一個looper對象,符合單例模式的設計思想。

sThreadLocal.set(new Looper(quitAllowed))該方法是new一個Looper,並將該Looper與當前線程的threadLocalMap關聯起來,因此該looper屬於調用prepare方法的線程。

接下來是最重要的loop方法,loop與prepare方法都是靜態方法,經過Looper.prepare跟Looper.loop調用便可,因此在loop開始的時候要先獲取當前thread的looper與messageQueue。

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;
        Binder.clearCallingIdentity();
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }
            ...
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }
複製代碼

Binder.clearCallingIdentity()方法是爲了清除當前線程傳入的IPC標識,若是在其餘線程傳入IPC請求,當前線程又要調用當前線程的本地接口,先清除傳入的IPC標識,那麼調用本地接口時就不須要進行權限驗證。

而後經過 for(;;) 進行無限循環,直到queue.next不爲空,接着調用target(即當前looper綁定的handler,在handler初始化的時候綁定)的 dispatchMessage(msg) 方法,以後走到初始化handler時複寫的 handleMessage 中。

最後經過 recycleUnchecked() 將當前的msg放入到消息池中。

threadLocal、threadLocalMap
threadLocal是一個數據對象類,由該類實例化的對象是線程相關的,即不一樣線程經過同一個threadLocal對象操做的也是各自的線程的備份數據,該功能是由threadLocalMap實現。

threadLocalMap是一個自定義hashmap,內部持有一個tables變量,類型爲Entry[]。Entry爲threadLocalMap內部類,繼承了ThreadLocal的虛引用,以便實例化的ThreadLocal對象在不用時能夠回收;內部只有一個成員變量value,這裏的結構爲Looper。

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
複製代碼

threadLocalMap是Thread的一個變量,因此每個線程只有一個threadLocalMap。

threadLocal的操做都是以threadLocalMap來實現的,如get()方法,首先獲取當前Thread,而後經過獲取Thread的threadLocalMap,而後map.getEntry(this)(傳入this是由於自定義hashmap的hash算法須要用到threadLocal中的threadLocalHashCode變量)獲取當前線程對應的value(looper),以保證在子線程中處理的looper、message都是主線程的looper、message,避免了不一樣線程數據的同步問題。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

其餘的set、setInitialValue等方法也是跟get相似,經過threadLocalMap實現。

由上面的代碼能夠看出,整個多線程通信的核心就是threadLocal與threadLocalMap。以普通的子線程發送消息,主線程接收消息的demo爲例:handler在主線程建立,因此在初始化時綁定的looper是主線程的looper;主線程的looper在初始化的時候調用prepare,跳轉到構造函數建立實例的時候會建立messageQueue並綁定,因此messageQueue也是對應的主線程的looper的內部隊列;message不管是obtain仍是new出來的,在經過sentMessage發出後,會綁定到當前handler上。綜上所述,雖然消息的建立與發送都是在子線程中完成,但因爲threadLocal機制,這一系列的實例都是在主線程中完成的,因此不會有不一樣線程通信的同步問題。

message

message是handler機制中的信息載體,實現了Parcelable接口,主要經過一下變量保存數據:

int what整形變量,讓接受者區分message的標識。

int arg1, arg2整形變量,可存儲簡單的int數據。

Object obj發送任意Object對象給接受者。

targetmessage所關聯的handler

message的初始化推薦經過Handler.obtainMessage()或者Message.obtain(Handler h),會返回消息池中的message,避免了message的建立與銷燬,效率更高。

首先看一下message的初始化:

public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }
...
public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
複製代碼

sPool是消息池,實際結構爲Message,經過next對象指向下一個message,而後一串message構成消息池。消息池的上限是50,沒有初始化,因此第一次調用obtain的時候,也是經過new Message建立的對象,在每次looper.loop()中獲取到消息後,將處理完的message經過recycleUnchecked方法添加到消息池中,直到達到上限 50 ,在達到上限50前,消息都不銷燬,只會將成員變量初始化。

不管是經過Handler.obtainMessage()仍是直接經過Message.obtain(Handler h),都會調用Message.obtain(),而後將target設爲綁定的handler對象,該方法會先判斷sPool是否爲空,若是不爲空,就將sPool返回,而後將sPool指向下一個Message。

message queue

消息隊列,實際數據結構爲鏈表,模擬的隊列特性,初始化、銷燬等操做的實現都在native層。

mQuitAllowedBoolean變量,標識messageQueue是否能夠停止。

messageQueue中的 enqueueMessage 方法是消息隊列的入隊方法,在handler調用sendMessage後,會調用該方法將msg放入到消息隊列中。

boolean enqueueMessage(Message msg, long when) {
        //判斷入隊的消息的tartget是否爲空,類型爲handler,即判斷message是否綁定了handler
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //判斷msg是否被使用,由msg的flag變量與常量的位運算結果控制
        //初始化的message的flag默認爲0,計算結果爲未使用;
        //使用後flag變爲1,計算結果爲已使用;
        //若是該msg爲異步消息,flag爲2.
        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標記爲已使用
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 若是消息隊列爲空,將mMessages指向msg,msg的next指向空節點,入隊完成
                msg.next = p;
                mMessages = msg;
                //needWeke標識隊列是否須要喚醒,默認的mBlocked爲false,looper調用loop開始輪訓後設爲true
                needWake = mBlocked;
            } else {
                // 根據mBlocked、是不是同步屏障message、該消息是不是異步的判斷是否須要喚醒隊列
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //若是消息隊列不爲空,採用尾插法將新的msg插入到隊尾,但mMessages仍指向第一個message
                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;
            }
            // 若是須要,則喚醒隊列,具體喚醒操做在native層實現
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製代碼

看完了消息的入隊,再看一下消息的出隊,消息的出隊是經過next()方法實現的,裏面的東西比較多,只看下主要邏輯。

Message next() {
        ...
        // 開始循環,判斷須要返回哪個message
        for (;;) {
            ...
            synchronized (this) {
                // Try to retrieve the next message. Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                //msg.target爲空,說明碰到了同步屏障,出循環後,prevMsg指向同步屏障,msg指向最近的一個異步消息
                //同步屏障由postSyncBarrier方法添加,再使用完後須要刪除屏障,不然會一直循環查找異步消息,沒法拋出同步消息
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready. Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) {
                            //prevMsg不爲空,說明prevMsg指向同步屏障,說明msg指向異步消息,須要優先拋出異步消息
                            prevMsg.next = msg.next;
                        } else {
                            //prevMsg爲空,說明沒有異步消息,拋出msg,將mMessages指向下一個message
                            mMessages = msg.next;
                        }
                        //清空msg的next節點,並設爲使用中,而後返回
                        msg.next = null;
                        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;
                }
                ...
        }
    }
複製代碼

上述next方法中,返回須要處理的message,優先處理異步消息,消息處理按照先進先出的順序執行。

異步消息的處理時間更快,須要將消息設爲異步(message.setAsynchronous(true)),並配合postSyncBarrier、removeSyncBarrier實現。postSyncBarrier是往隊列中添加同步屏障,removeSyncBarrier是刪除隊列中的同步屏障,若是隻添加沒有刪除,那麼next沒法拋出同步消息。

private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }
複製代碼

根據源碼能夠看到,同步屏障實質上也是一個message,只不過target爲null,不一樣於普通message的尾插法,同步屏障是經過頭插法實現的,因此next拋出message的時候回直接處理異步消息。

同步屏障的刪除源碼比較簡單,這裏就不貼出來了。只說明一下,同步屏蔽刪除後也會優先加入消息緩衝池中,消息池滿了後才銷燬。

messageQueue雖然叫消息隊列,但實際的邏輯結構是message組成的鏈表,普通狀況下模擬的隊列的先進先出的特性,但遇到異步消息時,也不會徹底遵照隊列特性,實現頭部插入功能。

相關文章
相關標籤/搜索