安卓中的訊息迴圈機制Handler及Looper詳解

來源:互聯網
上載者:User

標籤:

我們知道安卓中的UI線程不是安全執行緒的,我們不能在UI線程中進行耗時操作,通常我們的做法是開啟一個子線程在子線程中處理耗時操作,但是安卓規定不允許在子線程中進行UI的更新操作,通常我們會通過Handler機制來完成該功能,即當子線程中耗時操作完成後,在子線程中通過Handler向主線程發送訊息,在主線程中的Handler的handleMessage方法中處理接受到的訊息。這就是安卓中的訊息機制,安卓中的訊息機制主要是指Handler的運行機制,但是Handler的運行需要底層的MessageQueue和Looper機制的支撐。

下面我們來詳解講解安卓中的訊息迴圈機制,即Handler,MessageQueue,Looper機制。注意本部落格屬於安卓進階內容,所以一些基礎性的東西如果看官不懂情請自行百度解決。

首先我們來看一個標準的使用Handler機制的代碼示範例子:

class LooperThread extends Thread {      public Handler mHandler;      public void run() {          Looper.prepare();          mHandler = new Handler() {              public void handleMessage(Message msg) {                   // handle messages here              }          };          Looper.loop();      }  }



這段代碼大家肯定都不陌生,因為這是安卓中使用Handler機制的一個最標準的代碼示範,也許看官可能會說我在使用Handler的時候沒使用Looper.prepare()與Looper.loop()

語句,那時因為我們使用Handler通常是在UI線程中,而UI線程已經預設為我們做好了這些準備工作,至於這個待會後面會講到。之所以選擇在一個子線程中建立Handler的例子來講解,是因為這樣能讓我們更加清楚的明白安卓訊息迴圈中Handler,MessageQueue,Looper這三者之間的關係,因為在主線程中很多細節都被掩藏起來了。

首先從這段代碼可以看到這裡涉及兩個概念(事實上安卓中的訊息迴圈機制涉及三個重要概念),這裡先簡單介紹下

Looper:訊息迴圈,用於從MessageQueue訊息佇列中取出訊息進行處理。

Handler:訊息發送與處理,Handler通過sendMessage向訊息佇列MessageQueue中發送一條訊息,通過handlerMessage來處理訊息

MessageQueue:訊息佇列,用來存放Handler發送的訊息,它僅僅用來存放訊息


首先講解一下安卓中的訊息迴圈的整體過程,然後從上述的示範代碼上進行詳細的講解。

安卓中的訊息迴圈是使用Looper這個類來實現的,而Looper是基於線程的,即一個Looper對象與建立它的線程相關聯,當使用 Looper.prepare()語句時它會建立一個Looper對象和一個MessageQueue對象,然後在建立Handler時會先擷取到Handler所在的線程的Looper對象(如果調用的是無參建構函式時),通過這個Looper對象同時可以獲得其MessageQueue對象,正因為如此相當於Handler與Looper相關聯。這樣通過Handler發送的訊息才知道應該通過哪個Looper去進行處理。這是理解安卓整個訊息迴圈機制核心,即MeaageQueue與Looper相關聯,Handler與Looper相關聯,從這裡也可以看出Looper是安卓訊息迴圈機制的核心。


下面以上述示範代碼為例進行詳細講解。

首先我們來看一下 Looper.prepare()語句,即看一下prepare的源碼:

public static final void prepare() {    if (sThreadLocal.get() != null) {        throw new RuntimeException("Only one Looper may be created per thread");    }    sThreadLocal.set(new Looper());}
從這裡可以看到prepare()函數作了兩件事:建立一個looper對象,然後通過 sThreadLocal.set(new Looper());語句將其加入到 sThreadLocal中,這裡就不得不提一下安卓訊息迴圈機制中一個重要的概念:ThreadLocal,因為這涉及到Looper對象與線程相關聯的功能的實現。ThreadLocal它的作用是在不同的線程中訪問同一個ThreadLocal對象時通過ThreadLocal擷取到的值是不一樣的,即ThreadLocal中儲存的值是基於線程的。我們來看一下ThreadLocal的set方法:

 public void set(T value) {        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values == null) {            values = initializeValues(currentThread);        }        values.put(this, value);    }

可以看到set函數首先擷取當前的線程,然後通過當前線程產生一個Valuse對象,然後通過values的put方法將value(即Looper對象)與this(即ThreadLocal對象)相關聯,通過這段代碼我們應該知道Looper是與線程相關的,因為set方法會通過線程產生Valuse對象,然後通過Valuse對象put方法,將Looper儲存起來。


接下來我們來看Handler handler=new Handler();這個語句,我們來看一下Handler()這個無參建構函式。

public Handler() {    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());        }    }    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 = null;}
從這裡我們可以看到首先調用了 Looper.myLooper();方法,該方法是返回當前線程所關聯的Looper,且可以看到如果擷取到的mLooper為null則會拋出異常,這也說明建立Handler之前必選先建立Handler對象,獲得了Looper對象之後,我們就可以擷取到與Looper相關聯的MessageQueue對象,即 mQueue = mLooper.mQueue;前面講過建立Looper時會建立訊息佇列MessageQueue。這段代碼我們重點來看一下Looper.myLooper();這個方法,通過這個函數擷取到了與當前線程相關聯的Looper對象,正因為如此,Handler對象發送的訊息才知道應該被那個Looper處理。我們來看一下其代碼:

 public static Looper myLooper() {        return sThreadLocal.get();    }
可以看到他是通過sThreadLocal.get()方法來取得Looper對象,這個get與我們上面講述的ThreadLocal的set方法是相對應的,一個用來儲存資料,一個用來取出資料。

我們來看一下sThreadLocal.get()方法:

public T get() {        // Optimized for the fast path.        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values != null) {            Object[] table = values.table;            int index = hash & values.mask;            if (this.reference == table[index]) {                return (T) table[index + 1];            }        } else {            values = initializeValues(currentThread);        }        return (T) values.getAfterMiss(this);    }
可以看到sThreadLocal.get()方法先擷取當前線程,然後通過當前線程構造Values對象,然後通過valuse返回其儲存的資料(當然該資料為Looper對象),也正因為如此在Handler中擷取到的Looper對象與我們在當前線程中建立的Looper對象是同一個對象,這是保證Handler對象發送的資訊被正確的Looper所處理的關鍵。


最後看一下Looper.loop();語句,這個語句的作用是開啟Looper迴圈,我們來看一下其原始碼:

public static final void loop() {    Looper me = myLooper();    MessageQueue queue = me.mQueue;    while (true) {        Message msg = queue.next(); // might block        if (msg != null) {            if (msg.target == null) {                return;            }            if (me.mLogging!= null) me.mLogging.println(                    ">>>>> Dispatching to " + msg.target + " "                    + msg.callback + ": " + msg.what                    );            msg.target.dispatchMessage(msg);            if (me.mLogging!= null) me.mLogging.println(                    "<<<<< Finished to    " + msg.target + " "                    + msg.callback);            msg.recycle();        }    }}
可以看到loop方法首先擷取當前Looper對象,然後擷取該Looper對象的MessageQueue對象,然後在一個while死迴圈中不斷的通過 queue.next();從訊息佇列中取出一個訊息,然後通過訊息msg.target這個屬性來調用dispatchMessage(msg);來指派訊息,msg.target這個屬性本質上是發送訊息給這個MessageQueue的Handler對象,即通過此語句就將Handler發送的訊息交給它的dispatchMessage(msg);方法來處理,這樣就將代碼邏輯切換到建立該Handler的線程中去執行了。

我們來看一下Handler的dispatchMessage(msg);方法

public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}
可以看到在該方法中調用了 handleMessage(msg);,而這個正是我們在Handler中處理訊息的回調方法。

最後就是我們使用sendMessage來發送訊息的過程,Handler提供了多個發送訊息的函數,最終都會調用sendMessageAtTime()方法,我們來看一下其源碼:

public boolean sendMessageAtTime(Message msg, long uptimeMillis){    boolean sent = false;    MessageQueue queue = mQueue;    if (queue != null) {        msg.target = this;        sent = queue.enqueueMessage(msg, uptimeMillis);    }    else {        RuntimeException e = new RuntimeException(            this + " sendMessageAtTime() called with no mQueue");        Log.w("Looper", e.getMessage(), e);    }    return sent;}
可以看到在該方法中最終調用了MessageQueue的enqueueMessage(msg, uptimeMillis);方法,顧名思義,其作用是將一個訊息入隊,它的源碼不重要,我們只需知道它是通過將一個訊息插入到一個單向鏈表中的方式來完成的入隊操作即可。


在前面我們說過在主線程中我們是不需要建立一個Looper的,這是因為這些工作安卓系統幾經幫我們做好了,安卓中的主線程即ActivityThread,主線程的入口函數為main,我們來看一下其原始碼:

public static void main(String[] args) {    SamplingProfilerIntegration.start();    CloseGuard.setEnabled(false);    Environment.initForCurrentUser();    EventLogger.setReporter(new EventLoggingReporter());    Process.setArgV0("<pre-initialized>");    Looper.prepareMainLooper();    ActivityThread thread = new ActivityThread();    thread.attach(false);    if (sMainThreadHandler == null) {        sMainThreadHandler = thread.getHandler();    }    AsyncTask.init();    if (false) {        Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));    }    Looper.loop();    throw new RuntimeException("Main thread loop unexpectedly exited");}
可以看到在該方法中調用了Looper.prepareMainLooper()方法,而Looper.prepareMainLooper()方法會調用Looper.prepare()方法。所以在主線程中我們不需自己建立一個Looper對象。


好了,通過上述的講解相信看官對安卓中的訊息機制已經非常的清楚了,下面總結一下:

1Looper對象是安卓訊息迴圈的核心,Looper對象是基於線程的,我們在建立一個Looper對象時會通過ThreadLocal對象將我們建立的Looper與其所在的線程相關聯起來,具體來說是通過 sThreadLocal.set(new Looper());語句將其儲存起來,另外在建立一個Looper對象時其內部會幫我們自動建立了一個訊息佇列MessageQueue對象。

2Looper的作用是通過一個while死迴圈來不斷的查看訊息佇列MessageQueue中是否存在訊息Message,若存在則會通過 msg.target.dispatchMessage(msg);將訊息交給發送訊息的Handler對象來進行處理。

3我們在建立一個Handler對象時,其內部首先會獲得當前線程所關聯的Looper對象,即調用 Looper.myLooper();方法,具體來說是通過sThreadLocal.get()方法來完成的,

這樣就保證了在Handler中擷取到的Looper對象與我們在當前線程中建立的Looper對象是同一個對象,從而保證Handler對象發送的資訊被正確的Looper所處理。

4另外在建立一個Handler對象時,其內部會通過擷取到的Looper對象來擷取與Looper對象相關聯的MessageQueue對象,這樣通過sendMessage發送的訊息會發送到這個MessageQueue對象中,這也是保證Handler對象發送的資訊被正確的MessageQueue所處理的關鍵(其本質是通過3來完成的,因為MessageQueue的建立是在Looper內部完成的,即MessageQueue是與Looper相關聯的)。

5MessageQueue僅僅用來儲存訊息而已,訊息的指派是通過Looper來完成的,在Looper的loop迴圈中會通過一個while死迴圈來不斷的查看訊息佇列MessageQueue中是否存在訊息Message,若存在則會通過 msg.target.dispatchMessage(msg);將訊息交給發送訊息的Handler對象來進行處理,這樣就將代碼的邏輯切換到Handler所在的線程中進行處理。


用圖示表示如下:


好了上述就是本人理解的關於安卓訊息迴圈機制的全部內容,看官如果覺得不錯請點個贊哦,也請看官多多支援本人的其它部落格文章。

安卓中的訊息迴圈機制Handler及Looper詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.