Android 中線程間通訊原理分析:Looper, MessageQueue, Handler

轉載一篇我以前發在csdn上的博客。java

自問自答的兩個問題

在咱們去討論Handler,Looper,MessageQueue的關係以前,咱們須要先問兩個問題:android

  1. 這一套東西搞出來是爲了解決什麼問題呢?程序員

  2. 若是讓咱們來解決這個問題該怎麼作?網絡

以上者兩個問題,是我最近總結出來的,在咱們學習瞭解一個新的技術以前,最好是先能回答這兩個問題,這樣你才能對你正在學習的東西有更深入的認識。異步

第一個問題:google的程序員們搞出這一套東西是爲了解決什麼問題的?這個問題很顯而易見,爲了解決線程間通訊的問題。咱們都知道,Android的UI/View這一套系統是運行在主線程的,而且這個主線程是死循環的,來看看具體的證據吧。async

public final class ActivityThread {
    public static void main(String[] args) {
        
        //...
        
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

如上面的代碼示例所示,ActivityThread.main()方法做爲Android程序的入口,裏面我省略了一些初始化的操做,而後就執行了一句Looper.loop()方法,就沒了,再下一行就拋異常了。ide

loop()方法裏面實際上就是一個死循環,一直在執行着,不斷的從一個MQ(MessageQueue,後面我都縮寫成MQ了)去取消息,若是有的話,那麼就執行它或者讓它的發送者去處理它。函數

通常來講,主線程循環中都是執行着一些快速的UI操做,當你有手touch屏幕的時候,系統會產生事件,UI會處理這些事件,這些事件都會在主線程中執行,並快速的響應着UI的變化。若是主線程上發生一些比較耗時的操做,那麼它後面的方法就沒法獲得執行了,那麼就會出現卡頓,不流暢。oop

所以,Android並不但願你在主線程去作一些耗時的操做,這裏對「耗時」二字進行樸素的理解就好了,就是執行起來須要消耗的時間比較多的操做。好比讀寫文件,小的文件也許很快,但你沒法預料文件的大小,再好比訪問網絡,再好比你須要作一些複雜的計算等等。學習

爲了避免阻礙主線程流暢的執行,咱們就必須在須要的時候把耗時的操做放到其餘線程上去,當其餘線程完成了工做,再給一個通知(或許還帶着數據)給到主線程,讓主線程去更新UI什麼的,固然了,若是你要的耗時操做只是默默無聞的完成就好了,並不須要通知UI,那麼你徹底不須要給通知給到UI線程。這就是線程間的通訊,其餘線程作耗時操做,完成了告訴UI線程,讓它進行更新。爲了解決這個問題,Android系統給咱們提供了這樣一套方案來解決。

第二個問題:若是讓咱們來想一套方案來解決這個線程間通訊的問題,該怎麼作呢?

先看看咱們如今已經有的東西,咱們有一個一直在循環的主線程,它實現起來大概是這個樣子:

public class OurSystem {
    public static void main(String [] args) {
        for (;;) {
            //do something...
        }
    }
}

爲何主線程要一直死循環的執行呢?

關於這一點,我我的並無特別透徹的認知,但我猜想,對於有GUI的系統/程序,應該都有一個不斷循環的主線程,由於這個GUI程序確定是要跟人進行交互的,也就是說,須要等待用戶的輸入,好比觸碰屏幕,動動鼠標,敲敲鍵盤什麼的,這些事件確定是硬件層先得到一個響應/信號,而後會不斷的向上封裝傳遞。

若是說咱們一碰屏幕,一碰鼠標,就開啓一個新線程去處理UI上的變化,首先,這固然是能夠的!UI在什麼線程上更新其實都是能夠的嘛,並非說必定要在主線程上更新,這是系統給我設的一個套子。而後,問題也會複雜的多,若是咱們快速的點擊2下鼠標,那麼一瞬間就開啓了兩個新線程去執行,那麼這兩個線程的執行順序呢?兩個獨立的線程,咱們是沒法保證說先啓動的先執行。

因此第一個問題就是執行順序的問題。

第二個問題就是同步,幾個相互獨立的線程若是要處理同一個資源,那麼形成的結果都是使人困惑,不受控制的。另外一方面強行給全部的操做加上同步鎖,在效率上也會有問題。

爲了解決順序執行的問題,很是容易就想到的一種方案是事件隊列,各類各樣的事件先進入到一個隊列中,而後有個東西會不斷的從隊列中獲取,這樣第一個事件必定在第二個事件以前被執行,這樣就保證了順序,若是咱們把這個「取事件」的步驟放在一個線程中去作,那麼也順便解決了資源同步的問題。

所以,對於GUI程序會有一個一直循環的(主)線程,可能就是這樣來的吧。

這是一個很是純淨的死循環,咱們想要作一些事情的話,就得讓它從一個隊列裏面獲取一些事情來作,就像打印機同樣。所以咱們再編寫一個消息隊列類,來存放消息。消息隊列看起來應該是這樣:

public class OurMessageQueue() {
    private LinkedList<Message> mQueue = new LinkedList<Message>();
    
    // 放進去一條消息
    public void enQueue() {
        //...
    }
    
    // 取出一條消息
    public Message deQueue() {
        //...
    }
    
    // 判斷是否爲空隊列
    public boolean isEmpty() {
        //...
    }
}

接下來咱們的循環就須要改形成能從消息隊列裏獲取消息,並可以根據消息來作些事情了:

public class OurSystem {
    public static void main(String [] args) {
        
        // 初始化消息隊列
        OurMessageQueue mq = ...
    
        for (;;) {
            if (!mq.isEmpty()) {
                Message msg = mq.deQueue();
                //do something...
            }
        }
    }
}

如今咱們假象一下,咱們須要點擊一下按鈕,而後去下載一個超級大的文件,下載完成後,咱們再讓主線程顯示文件的大小。

首先,按一下按鈕,這個事件應該會被觸發到主線程來(具體怎麼來的我還尚不清楚,但應該是先從硬件開始,而後插入到消息隊列中,主線程的循環就能獲取到了),而後主線程開啓一個新的異步線程來進行下載,下載完成後再通知主線程來更新,代碼看上去是這樣的:

// 腦補的硬件設備……
public class OurDevice {
    
    // 硬件設備可能有一個回調
    public void onClick() {
    
        // 先拿到同一個消息隊列,並把咱們要作的事情插入隊列中
        OurMessageQueue mq = ...
        Message msg = Message.newInstance("download a big file");
        mq.enQueue(msg);
    }
}

而後,咱們的主線程循環獲取到了消息:

public class OurSystem {
    public static void main(String [] args) {
        
        // 初始化消息隊列
        OurMessageQueue mq = ...
    
        for (;;) {
            if (!mq.isEmpty()) {
                Message msg = mq.deQueue();
                
                // 是一條通知咱們下載文件的消息
                if (msg.isDownloadBigFile()) {
                
                    // 開啓新線程去下載文件
                    new Thread(new Runnable() {
                        void run() {
                            // download a big file, may cast 1 min...
                            // ...
                            // ok, we finished download task.
                            
                            // 獲取到同一個消息隊列
                            OurMessageQueue mq = ...
                            
                            // 消息入隊
                            mq.enQueue(Message.newInstance("finished download"));
                        }
                    }).start();
                }
                
                // 是一條通知咱們下載完成的消息
                if (msg.isFilishedDownload()) {
                    // update UI!
                }
            }
        }
    }
}

注意,主線程循環獲取到消息的時候,顯示對消息進行的判斷分類,不一樣的消息應該有不一樣的處理。在咱們獲取到一個下載文件的消息時,開啓了一個新的線程去執行,耗時操做與主線程就被隔離到不一樣的執行流中,當完成後,新線程中用同一個消息隊列發送了一個通知下載完成的消息,主線程循環獲取到後,裏面就能夠更新UI。

這樣就是一個我隨意腦補的,簡單的跨線程通訊的方案。

有以下幾點是值得注意的:

  • 主線程是死循環的從消息隊列中獲取消息。

  • 咱們要將消息發送到主線程的消息隊列,咱們須要經過某種方法能獲取到主線程的消息隊列對象

  • 消息(Message)的結構應該如何設計呢?

Android 中的線程間通訊方案

Looper

android.os.Looper from Grepcode

Android中有一個Looper對象,顧名思義,直譯過來就是循環的意思,Looper也確實幹了維持循環的事情。

Looper的代碼是很是簡單的,去掉註釋也就300多行。在官方文檔的註釋中,它推薦咱們這樣來使用它:

class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
              // process incoming messages here
            }
        };

        Looper.loop();
    }
}

先來看看prepare方法幹了什麼。

Looper.prepare()

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));
}

注意prepare(boolean)方法中,有一個sThreadLocal變量,這個變量有點像一個哈希表,它的key是當前的線程,也就是說,它能夠存儲一些數據/引用,這些數據/引用是與當前線程是一一對應的,在這裏的做用是,它判斷一下當前線程是否有Looper這個對象,若是有,那麼就報錯了,"Only one Looper may be created per thread",一個線程只容許建立一個Looper,若是沒有,就new一個新的塞進這個哈希表中。而後它調用了Looper的構造方法。

Looper 的構造方法

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Looper的構造方法中,很關鍵的一句,它new了一個MessageQueue對象,並本身維持了這個MQ的引用。

此時prepare()方法的工做就結束了,接下來須要調用靜態方法loop()來啓動循環。

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;

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        msg.target.dispatchMessage(msg);

        //...
    }
}

loop()方法,我作了省略,省去了一些不關心的部分。剩下的部分很是的清楚了,首先調用了靜態方法myLooper()獲取一個Looper對象。

public static Looper myLooper() {
    return sThreadLocal.get();
}

myLooper()一樣是靜態方法,它是直接從這個ThreadLocal中去獲取,這個剛剛說過了,它就相似於一個哈希表,key是當前線程,由於剛剛prepare()的時候,已經往裏面set了一個Looper,那麼此時應該是能夠get到的。拿到當前線程的Looper後,接下來,final MessageQueue queue = me.mQueue;拿到與這個Looper對應的MQ,拿到了MQ後,就開啓了死循環,對消息隊列進行不停的獲取,當獲取到一個消息後,它調用了Message.target.dispatchMessage()方法來對消息進行處理。

Looper的代碼看完了,咱們獲得了幾個信息:

  • Looper調用靜態方法prepare()來進行初始化,一個線程只能建立一個與之對應的LooperLooper初始化的時候會建立一個MQ,所以,有了這樣的對應關係,一個線程對應一個Looper,一個Looper對應一個MQ。能夠說,它們三個是在一條線上的。

  • Looper調用靜態方法loop()開始無限循環的取消息,MQ調用next()方法來獲取消息

MessageQueue

android.os.MessageQueue from Grepcode

對於MQ的源碼,簡單的看一下,構造函數與next()方法就行了。

MQ的構造方法

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

MQ的構造方法簡單的調用了nativeInit()來進行初始化,這是一個jni方法,也就是說,多是在JNI層維持了它這個消息隊列的對象。

MessageQueue.next()

Message next() {
    
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    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) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                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 {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (false) Log.v("MessageQueue", "Returning message: " + msg);
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
        }

    }
}

next()方法的代碼有些長,我做了一些省略,請注意到,這個方法也有一個死循環,這樣作的效果就是,在Looper的死循環中,調用了next(),而next()這裏也在死循環,表面上看起來,方法就阻塞在Looper的死循環中的那一行了,知道next()方法能返回一個Message對象出來。

簡單瀏覽MQ的代碼,咱們獲得了這些信息:

  • MQ的初始化是交給JNI去作的

  • MQ的next()方法是個死循環,在不停的訪問MQ,從中獲取消息出來返回給Looper去處理。

Message

android.os.Message from Grepcode

Message對象是MQ中隊列的element,也是Handler發送,接收處理的一個對象。對於它,咱們須要瞭解它的幾個成員屬性便可。

Message的成員變量能夠分爲三個部分:

  • 數據部分:它包括what(int), arg1(int), arg2(int), obj(Object), data(Bundle)等,通常用這些來傳遞數據。

  • 發送者(target):它有一個成員變量叫target,它的類型是Handler的,這個成員變量很重要,它標記了這個Message對象自己是誰發送的,最終也會交給誰去處理。

  • callback:它有一個成員變量叫callback,它的類型是Runnable,能夠理解爲一個能夠被執行的代碼片斷。

Handler

android.os.Handler from Grepcode

Handler對象是在API層面供給開發者使用最多的一個類,咱們主要經過這個類來進行發送消息與處理消息。

Handler的構造方法(初始化)

一般咱們調用沒有參數的構造方法來進行初始化,使用起來大概是這樣的:

Handler mHandler = new Handler() {
    handleMessage(Message msg) {
        //...
    }
}

沒有參數的構造方法最終調用了一個兩個參數的構造方法,它的部分源碼以下:

public Handler(Callback callback, boolean async) {
    //...
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

注意到,它對mLooper成員變量進行了賦值,經過Looper.myLooper()方法獲取到當前線程對應的Looper對象。上面已經提到過,若是Looper調用過prepare()方法,那麼這個線程對應了一個Looper實例,這個Looper實例也對應了一個MQ,它們三者之間是一一對應的關係。

而後它經過mLooper對象,獲取了一個MQ,存在本身的mQueue成員變量中。

Handler的初始化代碼說明了一點,Handler所初始化的地方(所在的線程),就是從將這個線程對應的Looper的引用賦值給Handler,讓Handler也持有

對於主線程來講,咱們在主線程的執行流中,new一個Handler對象,Handler對象都是持有主線程的Looper(也就是Main Looper)對象的。

一樣的,若是咱們在一個新線程,不調用Looper.prepare()方法去啓動一個Looper,直接new一個Handler對象,那麼它就會報錯。像這樣

new Thread(new Runnable() {
        @Override
        public void run() {
            //Looper.prepare(); 

            //由於Looper沒有初始化,因此Looper.myLooper()不能獲取到一個Looper對象
            Handler h = new Handler();
            h.sendEmptyMessage(112);

        }
     }).start();

以上代碼運行後會報錯:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

小結Handler的初始化會獲取到當前線程的Looper對象,並經過Looper拿到對應的MQ對象,若是當前線程的執行流並無執行過Looper.prepare(),則沒法建立Handler對象

Handler.sendMessage()

sendMessage消息有各類各樣的形式或重載,最終會調用到這個方法:

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);
}

它又調用了enqueueMessage方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

注意到它對Messagetarget屬性進行了賦值,這樣這條消息就知道本身是被誰發送的了。而後將消息加入到隊列中。

Handler.dispatchMessage()

Message對象進入了MQ後,很快的會被MQ的next()方法獲取到,這樣Looper的死循環中就能獲得一個Message對象,回顧一下,接下來,就調用了Message.target.dispatchMessage()方法對這條消息進行了處理。

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

private static void handleCallback(Message message) {
    message.callback.run();
}

public void handleMessage(Message msg) {
    //這個方法是空實現,讓客戶端程序員去覆寫實現本身的邏輯
}

dispatchMessage方法有兩個分支,若是callbackRunnable)不是null,則直接執行callback.run()方法,若是callbacknull,則將msg做爲參數傳給handleMessage()方法去處理,這樣就是咱們常見的處理方法了。

Message.target與Handler

特別須要注意Message中的target成員變量,它是指向本身的發送者,這一點意味着什麼呢?

意味着:一個有Looper的線程能夠有不少個Handler,這些Handler都是不一樣的對象,可是它們均可以將Message對象發送到同一個MQ中,Looper不斷的從MQ中獲取這些消息,並將消息交給它們的發送者去處理。一個MQ是能夠對應多個Handler的(多個Handler均可以往同一個MQ中消息入隊)

下圖能夠簡要的歸納下它們之間的關係。

Looper,MessageQueue,Handler,Message

總結

  • Looper調用prepare()進行初始化,建立了一個與當前線程對應的Looper對象(經過ThreadLocal實現),而且初始化了一個與當前Looper對應的MessageQueue對象。

  • Looper調用靜態方法loop()開始消息循環,經過MessageQueue.next()方法獲取Message對象。

  • 當獲取到一個Message對象時,讓Message的發送者(target)去處理它。

  • Message對象包括數據,發送者(Handler),可執行代碼段(Runnable)三個部分組成。

  • Handler能夠在一個已經Looper.prepare()的線程中初始化,若是線程沒有初始化Looper,建立Handler對象會失敗

  • 一個線程的執行流中能夠構造多個Handler對象,它們都往同一個MQ中發消息,消息也只會分發給對應的Handler處理。

  • Handler將消息發送到MQ中,Messagetarget域會引用本身的發送者,Looper從MQ中取出來後,再交給發送這個MessageHandler去處理。

  • Message能夠直接添加一個Runnable對象,當這條消息被處理的時候,直接執行Runnable.run()方法。

相關文章
相關標籤/搜索