Handler非同步訊息處理機制,從原始碼看起

來源:互聯網
上載者:User

標籤:

轉載註明出處:http://blog.csdn.net/xiaohanluo/article/details/51994401
簡書連結:http://www.jianshu.com/p/e72aba99012a

簡介

在Android開發中經常會遇見非同步訊息處理的情況,特別是網路請求成功或者失敗之後需要更新UI,但是更新UI只能在UI線程裡面進行,否則就會報錯,這個時候就需要通知UI線程去更新UI,於是Handler出現了。

Handler的使用

Handler的使用非常簡單,直接看例子。

    private static final int KEY_REQUEST_SUCCESS = 0x01;    private Handler mHandler = new Handler() {        @Override        public void handleMessage(Message msg) {              //這裡是在UI線程了            if(msg.what == KEY_REQUEST_SUCCESS) {                //更改UI            }        }    };    ... ...//一些其他動作    private void changeUI() {        Message message = mHandler.obtainMessage();        message.what = KEY_REQUEST_SUCCESS;        mHandler.sendMessage(message);    }

聲明了一個Handler成員變數mHandler,並複寫了其中的handleMessage(Message msg)方法,然後使用mHandler發送一條訊息,最終會調用到handleMessage方法,然後進行UI更新操作。

看見這麼好用的非同步訊息處理方法,肯定會去看看內部是怎麼實現訊息的調度的。

Handler內部資訊調度訊息進入隊列

所有的訊息都是使用sendMessage方法發送出去(還有其他類似方法),就從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) {            //異常處理        }        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發送訊息最終都會調用到sendMessageAtTime方法,在這個方法裡面就做了一件事,將訊息放入一個訊息佇列中。記住msg.target這個賦值操作,然後我們去看看在放入隊列時候都做了些什麼。

    boolean enqueueMessage(Message msg, long when) {        //異常處理        synchronized (this) {            //異常處理            msg.markInUse();            msg.when = when;            Message p = mMessages;            boolean needWake;            if (p == null || when == 0 || when < p.when) {                // New head, wake up the event queue if blocked.                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                needWake = mBlocked && p.target == null && msg.isAsynchronous();                Message prev;                for (;;) {                    prev = p;                    p = p.next;                    if (p == null || when < p.when) {                        break;                    }                    if (needWake && p.isAsynchronous()) {                        needWake = false;                    }                }                msg.next = p;                prev.next = msg;            }            if (needWake) {                nativeWake(mPtr);            }        }        return true;    }

從代碼中很清晰的可以看見。

  • 如果新訊息首次排入佇列,或者新訊息延遲時間要小於隊列首個訊息延遲時間,就將新訊息放在隊列首部。
  • 如果隊列不為空白,新訊息延遲時間並不是最短,就將新訊息插入到隊列的某個位置。

總之,新訊息插入隊列後,訊息佇列是按延遲時間長短排序的一個有序隊列,延遲時間越短,越靠近隊列首部。這樣,就可以猜測,系統一次拿去隊列中的訊息,然後進行分發處理。

訊息調度分發

我們都知道了訊息被加入了一個按延遲長短排序的有序隊列,訊息僅僅加入了隊列還需要有地方處理才行,那我們就看看哪裡處理了這個隊列。
首先我們來查看一下Handler的成員變數中的MessageQueue是在哪裡初始化或者賦值的,從源頭開始尋找,順藤摸瓜。

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

原來是直接用Looper賦值的,那會不會對這個訊息佇列的處理就在Lopper當中呢?趕緊去看看。Looper的代碼不多,很快就看到了loop()這個方法。

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

就是在這裡!!在loop()方法中有一個迴圈一直在取出訊息佇列中的訊息然後進行分發工作。我們注意這一行代碼msg.target.dispatchMessage(msg),調用了msg.tartget中的一個分發訊息的方法,還記得在上面訊息進入訊息佇列時候,注意的msg.target的賦值操作嗎?msg.target其實就是我們發送訊息的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);        }    }

很容易理解如果Message和Handler的CallBack成員變數都為空白的話,直接調用Handler本身的handlerMessage(Message msg)方法,這個方法就是我們在執行個體化Handler時候覆寫的一個方法,最終訊息的具體處理過程就回到了我們手中。

調度圖


Handler發送Message訊息,訊息進入訊息佇列,Looper不斷的從訊息佇列中取出Message,然後交給Handler處理,處理的代碼交給使用者處理。這樣我們就可以在非UI線程中發送Message訊息,但是最終的處理回到了UI線程,進行一些UI操作。

其他用法

雖然Handler是用於處理非同步訊息的,但是由於它本身有一個延遲的功能,所以Handler也可以做一個定時任務處理器,類似廣告結束倒計時等等都可以用到Handler。

Tips

Handler雖然看起來使用簡單,但是如果仔細的話,會發現,這一套機制對Message的管理是非常嚴格的。將訊息從訊息佇列中刪除或者訊息處理完畢之後,並不是直接將Message捨棄,而是調用了Message本身的recycleUnchecked()方法。

    void recycleUnchecked() {        // Mark the message as in use while it remains in the recycled object pool.        // Clear out all other details.        flags = FLAG_IN_USE;        what = 0;        arg1 = 0;        arg2 = 0;        obj = null;        replyTo = null;        sendingUid = -1;        when = 0;        target = null;        callback = null;        data = null;        synchronized (sPoolSync) {            if (sPoolSize < MAX_POOL_SIZE) {                next = sPool;                sPool = this;                sPoolSize++;            }        }    }

首先將Message裡面一些成員變數進行了重設操作,在最後面我們發現了MAX_POOL_SIZE這個屬性!!這說明在這一套機制中,Message數量是有上限的。心細的人就會發現,如果每次使用Message時候都使用new執行個體化一個的話,總會有一個時間點,Message數量會超出最大限制數。
所以我們在擷取Message的時候,一定要使用Handler.obtainMessage()方法或者Message.obtain(Handler h)方法。

如果使用new去執行個體化Message的話,這裡面有坑,謹慎。

Q&A

Looper類的loop()方法處理完隊列之後就會停止迴圈,如果有新的訊息加入,系統是怎麼調用到Looper的loop()的呢?

在將資訊加入到訊息佇列後,會運行nativeWake(mPtr)這一行代碼,nativeWake()是一個本地的方法,是通過這個方法在系統的底層去調用Looper.loop()方法。

由Handler發送出一個延遲任務訊息,這個訊息還未被處理,怎麼撤回?

Handler撤回訊息有五種方法。

  • removeCallbacks(Runnable r)
  • removeCallbacks(Runnable r, Object object)
  • removeMessages(int what)
  • removeMessages(int what, Object object)
  • removeCallbacksAndMessages(Object object)

但是實際上是在MessageQueue隊列中刪除了訊息,MessageQueue提供了三種刪除訊息方法

  • removeMessages(Handler h, int what, Object object)
  • removeMessages(Handler h, Runnable r, Object object)
  • removeCallBackAndMessage(Handle h, Runnable r, Object object)

參數都是Message中的成員變數(Handler參數會在Handler調用MessageQueue刪除訊息方法傳入,也就是Handler本身),當傳入參數與Message中屬性相等時候會將訊息從訊息佇列中刪除。參數object是補充條件,object傳入null,則前麵條件滿足即可;object不為空白,所有條件都需要滿足。

    void removeCallbacksAndMessages(Handler h, Object object) {        if (h == null) {            return;        }        synchronized (this) {            Message p = mMessages;            // Remove all messages at front.            while (p != null && p.target == h                    && (object == null || p.obj == object)) {                Message n = p.next;                mMessages = n;                p.recycleUnchecked();                p = n;            }            // Remove all messages after front.            while (p != null) {                Message n = p.next;                if (n != null) {                    if (n.target == h && (object == null || n.obj == object)) {                        Message nn = n.next;                        n.recycleUnchecked();                        p.next = nn;                        continue;                    }                }                p = n;            }        }    }

從代碼中很容易看出來,如果調用MessageQueue的removeCallbacksAndMessage方法時候,object傳入是null,會將訊息佇列中所有訊息都給清空。所以這樣調用mHandler.removeCallbacksAndMessage(null)時候,會清空訊息佇列中所有資訊。

待完善…

Handler非同步訊息處理機制,從原始碼看起

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.