1. 程式人生 > >Handler訊息傳遞機制(一)理解到底為什麼?

Handler訊息傳遞機制(一)理解到底為什麼?

1.為什麼UI更新要在主執行緒中,主執行緒有什麼原則?

我們都知道,要在非UI執行緒更新UI需要使用到Handler或者AsyncTask(內部也是Handler實現),那麼為什麼就不能直接在子執行緒中更新UI,而要在UI執行緒中更新。這就扯到一個單執行緒和多執行緒問題,要知道UI執行緒中只有一個MainThread主執行緒,也就是單執行緒,而非UI執行緒的話就可能有多執行緒,關於一個執行緒安全問題。在多執行緒情況下如果去更新UI,所有執行緒同時在對一個UI進行訪問,當前UI的狀態就會無法獲取到,導致執行緒安全問題,可以使用synchronized鎖定當前執行緒,防止其他執行緒訪問。這裡也看到其他人的一些說法,就是跟顯示卡晶片

有關,想想還是有點道理。所有的併發顯示UI都是要排隊的,只是給你的假象是在同時更新,如果晶片很差,就會出現繪製延遲和混亂,如果顯示卡晶片效能很高很高,在子執行緒更新UI也沒什麼問題,而目前在Google 新推出的Sky語言號稱能達到120fps ,並且不會阻塞UI。要知道現在的Android手機很多連60fps都達不到。
另外當所有更新UI操作放到主執行緒時,如果存在一些比較耗時的工作比如訪問網路或者資料庫查詢,都會堵塞UI執行緒,導致事件停止分發,也就是耗時操作要放在子執行緒。對於使用者來說,應用看起來像是卡住了,更壞的情況是,如果UI執行緒blocked的時間太長(大約超過5秒),使用者就會看到ANR(application not responding)的對話方塊。所以Android的單執行緒模型有兩條原則

①不要阻塞UI執行緒,耗時操作放在子執行緒

②不要再UI執行緒之外訪問UI元件

2.UI中子執行緒更新UI的3種方法

  • runOnUiThread(Runnable runnable)
     /**
     * Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is
not the UI thread, the action is posted to the event queue of the UI thread.

     *@param action the action to run on the UI thread
     */
public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }

原始碼中這樣解釋。先判斷當前執行緒是否是主執行緒,如果是主執行緒,則直接執行Runable介面的run()方法,如果不是主執行緒,則通過Handler,post(Runnable runnable)到主執行緒中去。這裡需要說明的是,runOnUiThread方法是屬於Activity的,也就是說我們能拿到Activity才能使用該方法。比如

new Thread(new Runnable){
    @Override
    public void run(){
        MainActivity.this.runOnUiThread(new Runnable(){

            @Override
            public void run(){
                //done
            }
        });
    }
}).start();

很明顯,這裡的執行緒並不是主執行緒,獲取當前執行緒是通過Looper裡面Thread.currentThread()獲得,而MainThread主執行緒裡面已經自動建立了Looper,詳細會在之後介紹。該方法會返回當前的執行執行緒。這裡明顯是執行了mHandler.post(action)方法。我們目前不去研究handler.post方法,因為一會你就知道為什麼了。再來看看第二種解決問題的辦法

  • view.post(Runnable runnable)
/**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @see #postDelayed
     * @see #removeCallbacks
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

這裡AttachInfo表示View繫結到父Window的一組資訊。當View繫結到父視窗時就獲取當前視窗的Handler,再呼叫post方法。無論我們是選擇第一種方法還是第二種方法去解決這個崩潰問題,都是殊途同歸的,最後經過層層封裝,都走到了handler.post方法中。

  • handler.post(Runnable runnable)
 /**
     * Causes the Runnable r to be added to the message queue.
     * The runnable will be run on the thread to which this handler is 
     * attached. 
     *  
     * @param r The Runnable that will be executed.
     * 
     * @return Returns true if the Runnable was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     */
    public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

可以看到,通過handler.post(Runnable runnable)會把Runnable介面新增到當前的訊息佇列中,這個Runnable的執行時的執行緒就是與Handler繫結的執行緒.而Handler的執行緒則是通過Looper來獲取。也就是說,當我們建立Handler的時候,例如我們經常new的一個Handler

    Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };  
        ...
        //我們new了一個很普通的Handler,我們點進 new Handler()的構造方法一層一層點進
    public Handler() {
        this(null, false);
    }
        ...
    //再點
    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;
    }

可以看到,在new Handler()中,裡面獲取了Looper,而Looper裡面通過Thread.currentThread()獲取到了當前的執行緒,跟前面一樣,詳細的會下介紹Looper的時候說。
回到handler.post上,呼叫的是sendMessageDelayed(getPostMessage(r), 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);
    }

可以看到,方法的呼叫順序為post–sendMessageDelayed–sendMessageAtTime–enqueueMessage
詳細的下一篇文章再引出來,一次性引出太多也消化不了,反而變亂。本篇文章主要跟大家講解Handler的使用情況,進而引出一系列問題出來,比如Handler內部操作,Looper又是什麼,他們之間是如何協調工作的。

3.小結

* 主執行緒更新UI操作,考慮到執行緒安全問題

* 耗時任務要放在子執行緒,避免阻塞主執行緒

* 子執行緒更新Ui的三種方法

1.runOnUiThread(Runnable runable),必須持有activity的引用才可以呼叫
2.view.post(Runnable runnable) , View通過繫結父視窗,獲取父視窗的Handler,再呼叫Handler.post(Runnable runnable)方法
3.handler.post(Runnable runnable) , 將Runnable打包成Message,放到Handler的訊息佇列中去。Handler通過Looper綁定當前執行緒

4.個人體會

Handler相信網上肯定有很多的資料解釋,但那些資料也無一沒有脫離原始碼,所以要解決問題,還是要多看原始碼,自己多動手做,堅持才是硬道理。下篇文章將會通過原始碼詳細介紹Handler,MessageQueue,Looper