1. 程式人生 > >Android Handler 訊息機制分析(一)

Android Handler 訊息機制分析(一)

        Handler,Message,MessageQueue,Looper,android一種訊息處理機制,在android開發中經常會用到,當Handler建立後,會被繫結到它所在的執行緒上,處理訊息的成員及其功能如下:

        Handler:傳送一個訊息(Message)去做特定任務

        Message:代表一個要處理的任務;

        MessageQueueMessage的佇列;

        Looper:負責迴圈Message佇列,取出Message又傳給Handler處理。

它們之間關係如下:

        每個執行緒中只有一個Looper,由

ThreadLocal維護;

        每個Looper中只有一個MessageQueue,因此一個執行緒中只有一個MessageQueue

        每個MessageQueue中可以有多個Message

        每個執行緒中可以有多個Handler

四者關係如下圖:

接下來我們以開發中使用時的順序來分析它們之間的關係。

1.Looper

        Looper,就是不斷迴圈讀取訊息給Handler。在開發過程中,更多的是在主執行緒中對Handler進行例項化,然後就可以呼叫sendMessage()方法傳送訊息了,不需要對Looper做額外的處理,但是如果我們在子執行緒進行例項化

Handler時,則必須在例項化Handler之前對Looper做如下的操作:

class MyThread extends Thread{
    @Override
    public void run() {
        //為該執行緒建立一個Looper
        Looper.prepare();
        //例項化Handler
        mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        //不斷迴圈讀取Message
        Looper.loop();
    }
}
        至於為什麼在子執行緒中必須進行這樣的操作,而在主執行緒就不需要這樣的操作呢?這就先得從Handler說起了。

1.1.子執行緒中Looper的獲取

        Handler的構造方法如下:

public Handler() {
    this(null, 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
    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,則丟擲Runtime異常,注意了,這裡是當前執行緒,這是什麼意思?下面進行分析

        再來看看Looper中的兩個靜態方法幹了什麼,prepare()方法如下:

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物件,並且維護了一個ThreadLocal存放每個執行緒的Looper物件,通過ThreadLocal將執行緒和Looper進行了繫結,從而解決了多執行緒問題,保證了每個執行緒只有一個Looper物件。因此,如果在子執行緒中不顯示呼叫prepare()方法,則由於該執行緒中沒有Looper物件,從而報出異常:Can't create handler inside thread that has not called Looper.prepare().

        好了,再來看看Looper的構造方法:

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

        在構造方法中,首先例項化了MessageQueue物件,由於每個執行緒只有一個Looper,每個Looper又只會例項化一個MessageQueue,因此,每個執行緒也就只有一個MessageQueue當在子執行緒中呼叫Looper.prepare()後,該子執行緒中就有Looper了,因此,就可以完成Handler的例項化了,再來看看Looperloop()方法:

public static void loop() {
    final Looper me = myLooper();
................
    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);
            end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
        } finally {
        }
    //回收資源
        msg.recycleUnchecked();
    }
}

        在loop()方法中,設定了一個for死迴圈,不斷的從MessageQueue中讀取MessageQueue,如果Message為空,則return退出該方法。但是這不表示loop時如果沒有訊息,該方法就return了,mQueue.next()是個阻塞方法,也就是說,如果當前執行緒中還沒有訊息,該方法處於阻塞狀態,那麼什麼時候mQueue.next()會返回null呢?在分析MessageQueue的時候進行分析。

        那麼為何在主執行緒中,為何可以不使用Looper.prepare()直接獲取Handler呢?這是因為,主執行緒已經做了這些操作了。

1.2.主執行緒中Looper的獲取

        Android主執行緒,即UI執行緒,實際上就是ActivityThread,我們看看ActivityThreadmain()方法:

public static void main(String[] args) {
    .....................
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

        可以看到,和子執行緒獲取Looper有一個小差別,在這裡,Looper使用了prepareMainLooper()方法例項化。此外,如果Looper.loop()執行退出,則丟擲異常。
Looper的實現不是特別複雜,實現原理就是內部維護一個ThreadLocal保證每一個執行緒只有一個Looper物件,但卻是整個訊息處理機制的基石,每個執行緒只有獲取了Looper後才可以獲取Handler。其主要工作就是不停的迴圈遍歷訊息,然後對訊息進行派遣。派遣給誰呢?是msg.target,也就是Handler了。

2.Handler

        Handler作為訊息的發起者和訊息的最終處理者,也是這四者當中最常見的,它提供了許多的介面供我們開發時呼叫,下面開始分析Handler在訊息機制中的作用以及它的常用方法。

2.1.Handler構造方法

        Handler中核心成員如下:

public class Handler {
    final Looper mLooper;
    final MessageQueue mQueue;
    final Callback mCallback;
    final boolean mAsynchronous;
    IMessenger mMessenger;
}

        現在我們開始先從構造方法開始分析,Handler提供了七個構造方法,然而它們都的目的是一樣的,在構造方法中,都會初始化以上的核心成員變數,同時它們被final修飾,也就意味著一旦初始化就不能改變,mLooper是當前執行緒的Looper物件;mQueue代表當前執行緒的MessageQueue物件;CallbackHandler內部的一個介面,該介面和處理訊息有關,會在處理訊息時說明;mAsynchronous代表是否需要非同步執行,如果設定為true,Handler中的操作會非同步進行,預設為false。在分析Looper的時候已經分析了兩個構造方法了,這裡再列舉出我們常用來例項化Handler的構造方法,如在android系統服務中會常常使用一個Looper物件例項化Handler,從而給Handler提供一個Looper,不使用預設Looper

public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

        因此,不管是何種構造方法,其目的都是一樣的,就是初始化核心的成員屬性。

2.2.Handler傳送訊息方式的兩種方式

        當獲取了Handler物件後,就可以傳送訊息了,就像我們最常用的sendMessage()方法一樣,Handler同樣提供了多種傳送訊息的方法,它們之間的聯絡和呼叫順序如下圖所示:


    

        上圖中綠色代表HandlerAPI的開放介面,即我們可以呼叫的方法,紅色表示API中沒有開放的方法。從上圖中可以看出,傳送訊息的方法可以分為兩類,一類是sendXXX(Message)傳送訊息,另一種是postXXX(Runnable)傳送訊息,無論是哪種方式傳送訊息,最終都會走到enqueueMessage()方法中。在enqueueMessage()方法中,首先會將msg和當前Handler進行繫結,然後會將Message新增到訊息佇列中去,具體如何加入訊息對列,在MessageQueue中進行分析。該方法如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //當前Handler和當前msg進行繫結
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //呼叫到MessageQueue中加入訊息佇列
    return queue.enqueueMessage(msg, uptimeMillis);
}

        這兩種傳送訊息的方式有何區別呢?我們從原始碼中看起,先看一個sendXXXMessage()型別的方法:

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    //設定Message what屬性
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

        再來看看postXXX()型別的方法:

public final boolean postAtTime(Runnable r, long uptimeMillis)
{
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

        這裡呼叫了一個getPostMessage(r),那就再看看這個方法做了什麼:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    //設定Message callback屬性
    m.callback = r;
    return m;
}

        可以看到,在sendXXX()方式傳送的訊息中,給訊息(Message)設定了的what屬性,而在postXXX()方法傳送的訊息中,是給訊息(Message)設定了callback屬性,其他並無區別。現在清楚這兩種方式傳送的訊息的區別了,那麼這樣做的根本目的是什麼呢?這就要在處理訊息的方法中進行分析了。

2.3.Handler處理訊息

        當Handler傳送訊息後,會將其加入訊息佇列,我們在Looper中已經分析到,如果Looper在死迴圈中遍歷到Message,會進行如下操作:

msg.target.dispatchMessage(msg);

        Msg.target是和當前Message繫結的Handler,因此,Handler中傳送完畢訊息後,最終對訊息進行接受和處理的還是在Handler中。dispatchMessage()方法如下:

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

        在這個方法中,對訊息的處理方式進行決策:

1.判斷Messagecallback屬性是否為空,如果不為空,呼叫handlerCallback()方法處理訊息;

2.如果Messagecallback屬性為空,則判斷HandlermCallback物件是否為空,如果不為空,使用mCallback.handleMessage()進行處理;

3.如果Messagecallback屬性為空,同時HandlermCallback物件也為空,則直接使用HandlerhandlerMessage(msg)進行處理。

        Messagecallback是哪個呢?就是使用post(Runnable)方式傳送訊息傳入的RunnbaleHandlerCallback物件是什麼呢?是就是構造方法Handler(Callback callback)傳入的。

handlerCallback()方法如下:

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

        到這裡,終於明白了使用sendXXX(Message)postXXX(Runnable)兩種方式傳送訊息的目的了,如果是send方式傳送訊息,那麼在處理訊息時,會使用handleMessage(msg)進行處理,具體使用Handler的該方法還是Handler.Callback的該方法,要看Handler是通過何種構造方法例項化;如果是post(Runnable)方式傳送訊息,那麼會在Runnablerun()方法中進行處理。

2.3.Handler.post(Runnable r)是執行在主執行緒呢還是在子執行緒?

問題:Handler.post(Runnable r)是執行在主執行緒呢還是在子執行緒?

這個問題經常困擾到我,我們這裡寫個小demo測試一下,首先我們在主執行緒例項化一個Handler,並使用Handler.post(Runnable r)傳送訊息:

public class MainActivity extends AppCompatActivity {

    private MyHandler mMyHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mMyHandler = new MyHandler();
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMyHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("tag","Thread id:"+Thread.currentThread().getId()
                            +",Thread name:"+Thread.currentThread().getName());
                    }
                });
            }
        });
    }
    class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}

        執行列印Log如下:


        可以看到,儘管使用了Runnable,但它是執行在主執行緒的。

        接下來我們在子執行緒例項化Handler,並使用Handler.post(Runnable)傳送訊息:

public class MainActivity extends AppCompatActivity {

    private MyHandler mMyHandler;
    private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyThread().start();
        mButton = findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMyHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this,"Thread name:"
                                +Thread.currentThread().getName(),
                                 Toast.LENGTH_SHORT).show();
                        Log.d("tag","Thread id:"+Thread.currentThread().getId()
                                +",Thread name:"
                                +Thread.currentThread().getName());
                    }
                });
            }
        });
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            mMyHandler = new MyHandler();
            Looper.loop();
        }
    }
    class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}

        執行列印Log如下:


        因此,HandlerpostXXX(Runnable)方法接受訊息時的執行執行緒和Handler的執行緒有關,如果Handler來源於主執行緒,那麼在接受訊息時,Runnablerun()方法在主執行緒執行,如果Handler來源於子執行緒,則run()方法也在子執行緒執行。

3.Message

        比起LooperHandlerMessage就相對簡單了,Message,就是為Handler而封裝的一個實體類,其實現了Parcelable介面,因此可以進行序列化和反序列化,同時還能跨程序傳遞。對於Message,只需要瞭解它常用的屬性和方法即可,接下來我們分析其屬性和常用方法。

3.1.Message物件的獲取

        Message只提供了一個構造方法:

public Message() {
}

        但是,在開發中並不建議使用構造方法類獲取Message物件,因為Message提供了靜態方法obtain()用於獲取Message物件,他有許多的過載形式,在獲取Message物件的同時,可以給Message設定對應的屬性,如:

public static Message obtain() {
    synchronized (sPoolSync) {
        //如果存在已回收的Message,複用它即可,否則重新例項化一個Message
        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();
}

        在呼叫靜態方法obtain()時,首先會判斷是否存在回收的Message sPool,如果存在,則直接使用該物件,不再例項化新的Message物件,否則例項化Message,從而避免的不必要的物件佔用記憶體空間。

        還有其他過載的obtain()方法用來獲取Message,但是這些過載方法中都會首先呼叫obtain()方法,比如obtain(Handler,int)

public static Message obtain(Handler h, int what) {
    Message m = obtain();
    m.target = h;
    m.what = what;
    return m;
}

        可以通過obtain(Handler,int)直接給當前Message物件設定targetwhat屬性,如果是這種方式獲取的Message,就可通過這種方式來發送訊息:

//獲取Message物件
Message message = Message.obtain(mMyHandler,1);
//傳送訊息
message.sendToTarget();

        sendToTarget()方法內部則通過Messagetarget傳送訊息,原始碼如下:

public void sendToTarget() {
    target.sendMessage(this);
}

        除了以上obtain()方法之外,還有五中型別的過載方法,它們的原理相同,都是根據傳入的引數提前對Message設定了對應的屬性。因此,在實際開發中使用要獲取Message時,不建議使用new Message()獲取例項,而是通過Message.obtain()獲取。

3.2.Message常用屬性

        Message類中提供了一些屬性來攜帶資訊,有些對使用者開放,有些則沒有開放,接下來對這些屬性進行總結:

public int what:訊息碼
public int arg1:可以攜帶int值
public int agr2:可以攜帶int值
public int obj:可以攜帶一個物件,一般用於跨程序

/*package*/ int flags:標識是否需要非同步執行
/*package*/ long when:處理訊息時間點
/*package*/ Handler target:繫結的Handler
/*package*/ Runnable callback:Runnable物件,如果存在則在run()方法中處理訊息
        到這裡為止,Handler、Looper、Message分析完畢了,現在就剩餘MessageQueue,由於MessageQueue涉及到JNI層,內容較多,因此在另一篇文中分析。