帶你深入理解Android Handler機(jī)制

說到消息機(jī)制,我們一定會(huì)想到Handler,由于Android系統(tǒng)規(guī)定主線程不能阻塞超過5s,否則會(huì)出現(xiàn)"Application Not Responding"。也就是說,你不能在主線程中進(jìn)行耗時(shí)操作(網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫操作等),只能在子線程中進(jìn)行。下面先來看一下在子線程中訪問UI會(huì)出現(xiàn)什么情況。

  public void click(View v){
    new Thread(new Runnable() {
        @Override
        public void run() {
            mTextView.setText("2");
        }
    }) .start();
}

結(jié)果不出意外的報(bào)錯(cuò):

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.###

在ViewRootImpl的checkThread()檢查是否是主線程,如果不是拋異常。


那么這個(gè)時(shí)候怎么解決才能在更新UI呢?其實(shí)就是用Handler機(jī)制啦!

Handler##

先來看一下如何改進(jìn)代碼,然后詳細(xì)分析Handler機(jī)制。

   public void click(View v){
    new Thread(new Runnable() {
        @Override
        public void run() {
            //拿到Message對(duì)象
            Message msg = mHandler.obtainMessage();
            msg.arg1 = 2;
            mHandler.sendMessage(msg);
        }
    }) .start();
}

然后在handleMessage中更新UI

   private Handler mHandler =  new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        mTextView.setText(msg.arg1+"");
    }
};

這樣就成功了。

僅僅知道怎么用那肯定是不夠的,我們還需要知道其背后到底干了什么。

我們就從 mHandler.sendMessage(msg)開始說起吧。當(dāng)我們調(diào)用sendMessage時(shí)候,其實(shí)最終調(diào)用的是sendMessageAtTime(msg,long)。此方法源碼如下:

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

它會(huì)調(diào)用enqueueMessage()將Message送到MessageQueue中去。那么MessageQueue是什么呢?顧名思義是消息隊(duì)列,其實(shí)在我們創(chuàng)建Handler的時(shí)候,它需要與Looper作關(guān)聯(lián),Looper類有一個(gè)成員變量
MessageQueue mQueue,它就是消息隊(duì)列。用來接收Handler發(fā)送Message。MessageQueue內(nèi)部并不是用數(shù)組存儲(chǔ)的,而是用鏈表的數(shù)據(jù)結(jié)構(gòu),方便添加和刪除。

下面來看一下Looper.looper()源碼,這個(gè)方法就是將Message交給Handler.handleMessage去完成的。

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

第3,4行:判斷Looper對(duì)象是否為空,如果是空,拋出"No Looper; Looper.prepare() wasn't called on this thread."異常。換句話說,如果我們?cè)谧泳€程中創(chuàng)建Handler,并調(diào)用sendMessage()時(shí)候,由于沒有Looper對(duì)象,就會(huì)拋此異常信息。我們可以通過Looper.prepare()將當(dāng)前線程轉(zhuǎn)為Looper線程。該源碼會(huì)在下面分析。

主要看 for (;;)那段代碼,它是個(gè)死循環(huán),不斷地執(zhí)行next()方法,如果有新消息,就交給 msg.target.dispatchMessage(msg);這里msg.target其實(shí)就是Handler對(duì)象。那么下面我們看一下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);
    }
}

其實(shí)看到這里就明白了,它調(diào)用的就是handleMessage(),所以我們就可以輕松的更新UI界面了!

那么mCallback.handleMessage(msg)是什么呢?

接下來我們看一下這個(gè)代碼:

    private Handler mHandler =  new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        Toast.makeText(getApplicationContext(),"1",Toast.LENGTH_SHORT).show();
        return false;
    }
}){
    @Override
    public void handleMessage(Message msg) {
        Toast.makeText(getApplicationContext(),"2",Toast.LENGTH_SHORT).show();
    }
};

注意:在第5行我return false,結(jié)果吐司展現(xiàn)1,展現(xiàn)完之后再展示2.
當(dāng)return true時(shí),結(jié)果吐司只展現(xiàn)1。這樣我們就可以知道,這其實(shí)是用來攔截處理消息的。

剛剛提到Looper.prepare()可以將當(dāng)前線程轉(zhuǎn)為Looper線程。那看一下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));
}

通過拋出的那個(gè)異常我們可以發(fā)現(xiàn)一個(gè)Handler只能有一個(gè)Looper.而一個(gè)Looper內(nèi)部維護(hù)者M(jìn)essageQueue,當(dāng)有消息時(shí)Looper從MessageQueue中取出消息交給Handler處理。這樣它們之間就建立起關(guān)系了。

看一下源碼中的這行代碼 sThreadLocal.set(new Looper(quitAllowed)); 關(guān)于ThreadLocal可以看一下我的這篇文章

http://www.itdecent.cn/writer#/notebooks/4409171/notes/5075332

要想到Looper不斷的從MessageQueue中取消息,就必須調(diào)用Looper.loop()來不斷取消息.

在子線程中發(fā)送消息的完整代碼如下:

    public void click(View v){
    final Handler handler =  new Handler();
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            handler.sendMessage();
            Looper.loop();
        }
    }) .start();
}

注意 Looper.prepare(); handler.sendMessage();這二個(gè)方法順序不能變,我們可以看一下Handler構(gòu)造方法

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

注意這段代碼 mLooper = Looper.myLooper();如果為空,拋異常,該異常意思是必須調(diào)用Looper.prepare().這就是說為什么順序不能改變!

那么讀者可能會(huì)問,我們?cè)谥骶€程創(chuàng)建Handler對(duì)象,并沒有調(diào)用Looper.prepare()也出什么問題啊,的確是這樣的,因?yàn)锳ctivityThread 的main()函數(shù)里面 Looper.prepareMainLooper();已經(jīng)自動(dòng)幫我們創(chuàng)建好了Looper對(duì)象了??匆幌略创a:

 public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

注意sMainLooper = myLooper();看一下myLooper()方法:

 public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

講了這么多,估計(jì)對(duì)于小白來說有點(diǎn)暈了。哈哈。那么接下來我盜用一張hyman老師的一張圖分析Handler,MessageQueue,Looper之間的關(guān)系吧。。。

0160904160222603)

一天,老板正在和員工開會(huì),員工想上廁所,出于禮貌,對(duì)老板說我要去上廁所(sendMessage 到 MessageQueue中) 老板思考了一會(huì)兒,回復(fù)到"你去吧"(Looper.loop()) , 最后員工去WC了(handleMessage) ,從詢問到最后WC都是員工做的事,這就是它們之間的關(guān)系了。。。哈哈哈。

其實(shí)Handler發(fā)送消息有多種方式

  • msg.sendToTarget();
  • mHandler.sendMessageAtFrontOfQueue();
  • mHandler.sendEmptyMessage()
  • mHandler.sendEmptyMessageDelayed()
  • mHandler.post()
  • mHandler.postDelayed()

雖然有多種方法,但本質(zhì)都是通過Handler.handleMessage()實(shí)現(xiàn)的。

還有幾個(gè)這里就不一一列舉了,有興趣的讀者可以去Android官網(wǎng)吧。。。

聲明##

感謝《Android開發(fā)藝術(shù)探索》,感謝慕課網(wǎng)視頻,感謝郭神。
我來附一下鏈接吧,感興趣的話去看一下吧。。。

視頻:

http://www.imooc.com/learn/267

郭神博客:

http://blog.csdn.net/guolin_blog/article/details/9991569

最后:這是筆者第一次寫分析源碼的文章,(估計(jì)也是最后一次了吧。。。哈哈哈。。),寫的不好,比較雜亂,還請(qǐng)讀者多多包涵。寫著寫著就過了24點(diǎn)了,即將迎來大三,希望用這篇文章來為準(zhǔn)大三生活開個(gè)好頭吧!祝大家生活愉快!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容