1. 程式人生 > >Anroid訊息機制原理分析

Anroid訊息機制原理分析

前言

最近通過微信公眾號推文以及部落格文章學習了Android的訊息機制的原理,然後抽空寫下了這篇文章,對自己學到知識進行梳理,也方便以後自己查閱。
因為Android的UI是執行緒不安全的,而我們處理完成非同步任務的時候,常常都需要更新我們的UI介面,這樣,就產生了一個矛盾:也就是我的子執行緒需要更新UI,而這個操作又必須在主執行緒中完成。所以,就有了我們接下來要分析的訊息機制。

訊息機制的大概流程:

啟動系統後,會在主執行緒建立一個Looper物件,然後通過這個Looper物件開啟一個死迴圈,迴圈會不斷地從訊息佇列中(MessageQueue)中取出待處理的訊息(Message),並回調Handler中的方法處理訊息。訊息佇列中的訊息是從哪裡來的呢?沒錯,就是通過Handler傳送過去的。流程也可用下面的圖表示出來:

在這裡插入圖片描述

訊息機制原始碼分析:

我們在使用到訊息機制進行更新UI的時候的操作步驟是怎麼樣的呢?

  1. 在主執行緒中建立一個Handler物件,傳入一個Handler.Callback介面的匿名類物件,並重寫該物件的handlerMessage()方法。
  2. 在子執行緒中建立一個Message物件,把要傳送的訊息放在Message物件中,然後呼叫Handler的sendMessage()方法把Message插入到MessageQueue中
  3. 最後主執行緒中重寫的handlerMessage()方法會被呼叫,更新UI的操作就完成啦

我們就跟著上面的步驟去看一下訊息機制裡面都做了些什麼吧。沒錯,我們就要去看看原始碼了,其實看原始碼不難,跟著思路來走就不會亂了。首先進入Handler的構造方法:

   //我們傳入Handler.Callback物件呼叫的構造方法 
    public Handler(Callback callback) {
        this(callback, false);
    }

    public Handler(Callback callback, boolean async) {
        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物件
       //如果Looper物件為空,就直接丟擲異常,提示使用者沒有使用Looper.prepare()建立Looper物件
       //我們還沒建立過Looper物件,會不會報錯呢?不急,我們接著往下看 ,肯定與Looper.prepare()有關
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //獲取MessageQueue訊息佇列
        //其實MessageQueue裡面的資料結構是一個單鏈表
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

好啦,這樣,我們的一個Handler物件就建立好了(Looper.prepare()這個方法我們後面會講到,先順著思路走下去),並且我們可以看到,Handler中持有Looper和MessageQueue的引用。接下來我們就要用到這個Handler物件往訊息佇列中插入訊息了,讓我們看看Handler.sendMessage(),看它是如何往訊息佇列中插入訊息的

   //我們使用時呼叫的方法 
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
   
   //然後來到這裡
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    //進而又來到這裡
    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);
    }
    
    //接著又來到了這裡
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    	//target其實就是Handler物件,通過msg.target=this實現Message與Handler的繫結
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //最後,呼叫了MessageQueue的enqueueMessage方法
        return queue.enqueueMessage(msg, uptimeMillis);
    }

	//下面的原始碼中省略了一些無關、影響閱讀的原始碼,想看完整原始碼的可以自行去AS中檢視
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        synchronized (this) {
            Message p = mMessages;
            if (p == null ) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
            } else {
                Message prev;
                //前面說過,MessageQueue資料結構是單鏈表,下面就是遍歷單鏈表,找到連結串列尾部
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;//把訊息插入到尾部
            }
        }
        return true;
    }

這時候,我們就已經成功把訊息(Message)插入到MessageQueue中了,並且我們知道了Handler把自己封裝進了Message當中。走完這兩步,我們好像走進了一個死衚衕,走不下去了?先來看看下面幾個問題:

  1. Looper物件是在哪裡被建立的?
  2. Handler中的handlerMessage()方法是怎麼被回撥的?

我們在上面的步驟中,沒有建立過Looper物件,而Looper物件卻已經存在了,那唯一的一種可能就是,當我們啟動系統的時候,系統已經幫我們建立好Looper物件了。我們在ActivityThread類中找到main()方法,ActivityThread類是一個隱藏類,我們可以在SDK資料夾中查找出來:

public static void main(String[] args) {
        ......
        Looper.prepareMainLooper(); //建立一個Looper物件

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

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop(); //開啟迴圈

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

果然,我們能在main()發現Looper。main()方法中通過Looper.prepareMainLooper()建立一個Looper物件,prepareMainLooper()方法實際上就是呼叫Looper.prepare(),沒錯,就是我們之前發現的方法,接下來讓我們看看這個方法做了什麼吧

	public static void prepareMainLooper() {
        prepare(false);
        ......
    }
    
	......
	
	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()中就是new了一個Looper,並把Looper set進ThreadLocal中(ThreadLocal是什麼來的?我暫時還沒有去了解,有時間會補充),所以Looper在系統啟動時就建立好了,我們建立Handler時才不會報錯
Looper建立完成之後,然後Looper.loop()就會開啟一個迴圈,開啟迴圈做什麼呢?就是之前說過的,從MessageQueue中不斷的取出訊息來

   //剔除了一些影響閱讀的程式碼後,可以看到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;
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
               ......
            }
        }
    }

很明顯可以看出loop方法一直遍歷MessageQueue,阻塞執行緒,直到獲取到一個Message,然後呼叫Message的一個成員變數target的dispatchMessage方法。之前說過了,target其實就是Handler,dispatchMessage方法最終就呼叫我們重寫的Handler的handlerMessage方法

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

以上就是我們在主執行緒中訊息機制的原理,讓我們整理一下上面的知識:

  • Handler:在訊息機制中,是作為訊息的傳送方和處理方。訊息在一個執行緒中通過Handler傳送到MessageQueue中。
  • Looper:在訊息機制中,是作為訊息佇列的管家。不停的從訊息佇列中獲取訊息,獲取到訊息後,根據Message中繫結的Handler物件呼叫Handler中的dispatchMessage方法,進而呼叫到開發者重寫的handlerMessage方法進行訊息的處理
  • MessageQueue和Message:MessageQueue就是存放Message的地方,實質上是一個單鏈表的結構,有新的Message來的時候,就把它放在表尾,當Looper來取訊息時就把表頭的Message給它。Looper拿到訊息後,可以從Message中拿到訊息的傳送方。

知道訊息機制的原理後,我們就可以輕鬆的在任何執行緒下使用handler了:

    new Thread() {
         @Override
         public void run() {
             Looper.prepare();
             Looper.loop();
             Handler handler = new Handler(new Handler.Callback() {
                 @Override
                 public boolean handleMessage(Message msg) {
                     return false;
                 }
             })
         }
     }.start();
  1. 首先用Looper.prepare()建立一個Looper並初始化Looper持有的MessageQueue
  2. 然後用Looper.loop()方法開啟迴圈,從MessageQueue中取訊息,並呼叫handler的dispatchMessage方法處理訊息,如果訊息佇列裡面沒有訊息,迴圈就會阻塞進入休眠狀態,等有訊息時就會被喚醒
  3. 最後再new一個Handler,Handler構造方法會獲取到Looper和Looper的MessageQueue物件,然後通過sendMessage方法往訊息佇列中插入訊息。

最後,我們對訊息機制有一些瞭解了,先寫這麼多吧,如有錯誤歡迎指出。