1. 程式人生 > >Android訊息機制(Handler、MessageQueue、Looper)詳細介紹

Android訊息機制(Handler、MessageQueue、Looper)詳細介紹

Android的訊息機制其實在android的開發過程中指的也就是Handler的執行機制,這也就引出了android中常見的面試問題:

  1. 簡述Handler、Looper、MessageQueue的含義,以及它們之間的關係
  2. 簡述Handler的執行機制
  3. 說明Handler、Looper以及Message之間的關係

Handler機制為什麼這麼重要呢?

我們知道android裝置作為一臺移動裝置,不管是記憶體或者還是它的效能都會受到一定的限制:過多的使用記憶體會使記憶體溢位(OOM);另外一方面,大量的使用CPU的資 源,一般是指CPU做了大量耗時的操作,會導致手機變得很卡甚至會出現程式無法響應的情況,即出現ANR異常(Application Not Responding)。ANR異常對於我們來說的話其實並不陌生:在UI執行緒中如果5秒之內沒有相應的輸入事件或者是BroadcastReceiver中超過10秒沒有完成返回的話就會觸發ANR異常,這樣就要求我們必須要在寫程式碼的時候格外注意不能將大量耗時的操作放在UI執行緒中去執行,例如網路訪問、資料庫操作、讀取大容量的檔案資源、分析點陣圖資源等…

Handler機制的主要作用

Android系統的這些特性導致開發者在進行開發的時候必須要將比較耗時的操作遠離UI執行緒(ActivityThread),單獨的將這些操作放入到子執行緒中進行執行,在子執行緒完成程式碼執行之後將等到的結果資料返回到UI執行緒(android系統規定在子執行緒中不允許訪問UI),同時UI執行緒根據這些返回的資料更新介面UI,從而實現了人機之間友好的互動。

那麼對於以上的結論,我們會發現如下的問題:

  • 在子執行緒執行完畢的程式碼如何才能將資料返回到UI執行緒呢?
  • 在這個過程中,android系統到底都做了些什麼呢?

接下來我們就開始介紹Handler的具體的執行機制

Handler機制簡介

Handler執行機制是需要MessageQueue、Looper、Handler三者的相互協調工作,但是實際上這三者就是一個整體,只不過我們在開發的過程中只是接觸到了Handler一個而已。Handler的主要作用就是將一個任務切換到指定的執行緒中去執行。而這樣的特性正好可以用來解決在子執行緒中無法訪問UI的矛盾。
那麼接下來我們就來了解下MessageQueue、Looper、Handler的執行機制,以及它們最基本的執行原理。

Handler機制之MessageQueue介紹

MessageQueue通常翻譯為“訊息佇列”,它內部儲存了一組資料,以佇列的形式對外提供插入和刪除操作。雖然被稱之為訊息佇列,但是實際上它的資料結構卻是採用的單鏈表的結構來儲存訊息列表(單鏈表在插入和刪除操作上效率比較高)。
MessageQueue主要包含兩個操作:插入(enqueueMessage)和讀取(next)。

  • 插入:enqueueMessage()方法是往訊息佇列中插入一條資料
  • 讀取:next()方法用於訊息的讀取,讀取操作本身也會伴隨著訊息的刪除操作,即從訊息佇列中取出一條資料並將該資料從訊息佇列中刪除。

對於enqueueMessage和next方法的原始碼請讀者去自行檢視,因為這兩個方法佔得篇幅較大,在這裡就不貼出來了,本處就說明下這兩個方法的主要作用。需要指出的是,next方法是一個無限迴圈的方法,如果訊息佇列中沒有訊息,那麼next方法會一直堵塞,有新訊息的事後,next方法會返回這條訊息並從連結串列中刪除該訊息。

Handler機制之Looper介紹

Looper可以理解為訊息迴圈,因為MessageQueue只是一個儲存訊息佇列,不能去處理訊息,所以需要Looper無限迴圈的去檢視是否有新的訊息,如果有的話就處理訊息,否則就一直等待(阻塞)。 從巨集觀的角度來分析的話,每一個非同步執行緒,都維護著唯一的一個Looper,每一個Looper會初始化(維護)一個MessageQueue,之後進入一個無限迴圈一直在讀取MessageQueue中儲存的訊息,如果沒有訊息那就一直阻塞等待。

Looper中有2個比較重要的方法:

  1. Prepare();
  2. Loop();

Looper.prepare()簡介

Handler的工作需要Looper,沒有Looper的執行緒就會報錯,所以就必須要通過Looper.prepare()方法為當前執行緒建立一個Looper,之後用Looper.loop()方法來開啟訊息迴圈。但是對於我們開發者來說,當我們在UI執行緒中例項化handler的時候並沒有要求對Looper進行初始化,照樣可以是執行沒有任何問題的。其原因就是因為UI執行緒在被建立的時候就會初始化Looper,這樣也就明白了在UI執行緒中可以預設使用handler的原因了。

Looper.prepare()原始碼如下:

 /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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方法完成了Looper物件的初始化,對於原始碼中所說的sThreadLocal物件來說,它是ThreadLocal的例項化物件,對於ThreadLocal的詳細分析介紹請點選這裡
通過以上對於原始碼的理解,我們可以知道該方法主要做了如下工作:

  • 檢查是否例項化了ThreadLocal
  • 如果已經例項化ThreadLocal,則將所線上程中Looper物件儲存進去

另外,下面我們通過一個Demo來展示下Handle初始化是需要事先呼叫Looper.perpare()的必要性:

**
 * @author zwjian
 * @des init Looper testActivity
 */
public class InitLooperActivity extends Activity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        new Thread("Thread#1") {

            @Override
            public void run() {
                super.run();
                Handler handler = new Handler();

            }
        }.start();

        new Thread("Thread#2") {

            @Override
            public void run() {
                super.run();
                Looper.prepare();
                Handler handler = new Handler();
                Looper.loop();

            }
        }.start();
    }
}

上述程式碼很簡單,我們來看一下執行結果:
這裡寫圖片描述
我們可以發現該執行時異常指的就是在Thread#1中沒有初始化Looper導致handler例項化失敗(後面我們會講到這個異常是怎麼丟擲的)。

Looper.loop()簡介

該方法的主要功能就是不斷從MessageQueue中讀取訊息,交給訊息的target屬性的dispatchMessage去處理。

其原始碼展示如下:

 /**
     * 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
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            msg.target.dispatchMessage(msg);

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

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

在該方法中,我們可以看到通過之前儲存在threadLocal物件中的looper物件得到了該執行緒中對應looper物件中維護的MessageQueue訊息佇列,之後進入了一個無限迴圈,對訊息佇列中的資料進行讀取,並且會把訊息通過msg.target.dispatchMessage(msg);進行處理,對於msg.target來說,其實它就是與之繫結的handler例項,在下面我們會進行詳細分析。

以上就是對於Looper的詳細介紹了,對於Looper的總結可以如下:

  • 與當前的執行緒繫結,保證一個執行緒只有一個Looper例項,同時一個Looper例項只有一個MessageQueue
  • loop方法不斷的從MessageQueue中去取訊息,交給訊息的target屬性的dispatchMessage去處理

Handler機制之Handler介紹

handler的工作主要是訊息的傳送和接收過程。訊息的傳送可以通過post和send等一系列的方法來實現。但是需要說明的是,通過post的方法最後還是通過send來實現的。

通常我們在使用handler之前都會進行初始化,比如說比較常見的更新UI執行緒,我們會在宣告的時候直接初始化。那麼問題來了,handler是怎樣和Looper以及MessageQueue聯絡起來的呢?它在子執行緒中傳送的訊息怎麼傳送到了MessageQueue中呢?

下面我們通過檢視Handler的原始碼來進行這些疑點的解釋:

首先我們先來看下Handler的建構函式:

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

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

很顯然,我們可以發現在該建構函式中完成了該handler與looper的繫結,以及獲得該looper物件中的訊息佇列等操作。同時在對mLooper進行判空的時候我們就可以發現一句熟悉的異常:”Can’t create handler inside thread that has not called Looper.prepare()”,而這個異常就是在前文中我們在沒有呼叫Looper.prepare()方法而直接例項化handler時所報的異常資訊了。

接下來我們來看一下handler傳送訊息的過程:

一般我們使用handler傳送訊息的函式可以有好幾個,在這裡我們使用如下程式碼進行傳送:

handler.sendMessage(new Message());

接下來我們就去看看它的傳送過程是怎樣的:

    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) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最後呼叫的是handler的enqueueMessage方法,在該方法中我們發現了一條熟悉的語句:msg.target=this;這句話就是對msg.target賦值為handler物件本身,正好對應上前文在looper.loop()方法中msg.target.dispatchMessage(msg);

同時我們一路跟下來發現最後呼叫的就是queue.enqueueMessage方法,而這個方法就是我們前面介紹的訊息佇列中進行插入訊息的那個方法。這樣一來,我們就明確了handler傳送的訊息其實最後是進入了訊息佇列中,那插入訊息到訊息佇列之後的操作是什麼呢?我們接著往下看。

通過前面我們所說的Looper中的loop方法,Looper . Loop()方法中通過無限迴圈對訊息佇列進行讀取訊息,得到訊息之後通過訊息的target屬性中的disPatchMessage()方法回撥handlerMessage等方法。而handlerMessage方法就是我們在UI執行緒中例項化handler的過程中重寫的handlerMessage方法。

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

我們發現在這個函式中其實有兩種處理,一個是執行handleCallback(msg);另外一個就是執行handleMessage(msg);對應原始碼如下:

private static void handleCallback(Message message) {
        message.callback.run();
    }
/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

我們知道在我們例項化handler的時候,如果採用post方法,一般要new 一個Runnable物件,具體如下:

handler = new Handler();
        handler.post(new Runnable() {
            @Override
            public void run() {
               //to do change UI
            }
        });

或者是這樣的形式:

handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case xxx:
                        //to do  change UI
                        break;
                }

            }
        };

而所謂的disPatchMessage所要做的就是對這兩種操作進行回撥,之後執行開發者自行重寫的程式碼,這樣就實現了從一個執行緒中傳送訊息,在另外一個執行緒收到訊息的全過程。

下面我們照樣是通過一個簡單的Demo來說明下:

相應的xml佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.zwjian.myapplication.HandlerAct">

    <TextView
        android:id="@+id/test_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/toolbar"
        android:layout_margin="20dp"
        android:text="你是一個壞人" />
</RelativeLayout>

對應的Activity

/**
 * @author zwjian
 * @des show handler + message changes UI
 */
public class HandlerAct extends AppCompatActivity {
    private static final int HANDLER_TEST_VALUE = 0X10;
    private Handler handler;
    private TextView test_tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        test_tv = (TextView) findViewById(R.id.test_tv);
        //first method
        handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case HANDLER_TEST_VALUE:
                        test_tv.setText("我是第一個方法:誰說的?你才是一個壞人!");
                        break;
                }

            }
        };
        //another method
        handler = new Handler();
        handler.post(new Runnable() {
            @Override
            public void run() {
                test_tv.setText("我是第二個方法:誰說的?你才是一個壞人!");
            }
        });

        new Thread("Thread#2") {

            @Override
            public void run() {
                super.run();
//                handler.sendEmptyMessage(HANDLER_TEST_VALUE);
                handler.sendMessage(new Message());
            }
        }.start();
    }

}

以上,我們通過在thread#2中傳送了一條訊息,在UI執行緒中進行接收,當然這裡寫了2種接收方式,讀者可以自行去註釋掉某一種去進行演示,最後可以通過這個demo發現這兩種方式都是可以被回撥執行的,通過執行我們可以看到textView的顯示發生了相對應的變化,這樣,我們就完成了handler訊息的傳遞的全過程。

最後,我們來總結下handler機制的全過程:

  1. 首先Looper.prepare()會在當前執行緒儲存一個looper物件,並且會維護一個訊息佇列messageQueue,而且規定了messageQueue在每個執行緒中只會存在唯一的一個
  2. Looper.loop()方法會使執行緒進入一個無限迴圈,不斷地從訊息佇列中獲取訊息,之後回撥msg.target.disPatchMessage方法。
  3. 我們在例項化handler的過程中,會先得到當前所線上程的looper物件,之後得到與該looper物件相對應的訊息佇列。(程式碼見handler的構造方法)
  4. 當我們傳送訊息的時候,即handler.sendMessage或者handler.post,會將msg中的target賦值為handler自身,之後加入到訊息佇列中。
  5. 在第三步實現例項化handler的過程中,我們一般會重寫handlerMessage方法(使用post方法需要實現run方法),而這些方法將會在第二步中的msg.target.disPatchMessage方法中被回撥,從而實現了message從一個執行緒到另外一個執行緒的傳遞。

以上就是handler機制的所有分析,由於個人理解有限,有錯誤之處還請諸位讀者能批評指正。