終于明白了Handler的運行機制

前言

Handler是一個Android SDK 提供給開發(fā)者方便進行異步消息處理的類。

我們都知道在UI線程中不能進行耗時操作,例如數(shù)據(jù)讀寫、網(wǎng)絡(luò)請求。Android 4.0開始,在主線程中進行網(wǎng)絡(luò)請求甚至會拋出Android.os.NetworkOnMainThreadException。這個時候,我們就會開始依賴Handler。我們在子線程進行耗時操作后,將請求結(jié)果通過Handler的sendMessge**() 方法發(fā)送出去,在主線程中通過Handler的handleMessage 方法處理請求結(jié)果,進行UI的更新。

后來隨著AsyncTask、EventBus、Volley以及Retrofit 的出現(xiàn),Handler的作用似乎被弱化,逐漸被大家遺忘。其實不然,AsyncTask其實是基于Handler進行了非常巧妙的封裝,Handler的使用依然是其核心。Volley同樣也是使用到了Handler。因此,我們有必要了解一下Handler的實現(xiàn)機制。

神奇的Handler

記得很久之前的一天,我在閱讀別人的代碼時,看到了這樣一段:

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(mContext, "I'm new Handler !", Toast.LENGTH_SHORT).show();
            }
        }, 1000);

第一印象就是,這不是在子線程中進行UI操作嗎?這代碼有問題吧,于是乎立刻在自己電腦上寫了個demo試了一下,結(jié)果發(fā)現(xiàn)真的沒有問題。在一陣懵逼過后,我又寫出下面的代碼,測試一下子線程中到底能不能進行UI操作。

        new Thread(new Runnable() {
            @Override
            public void run() {
                
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();

            }
        }).start();

結(jié)果很明顯,程序一啟動立刻就奔潰了。并拋出異常java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
于是乎我又在try block 之前添加了Looper.prepare()這行代碼。再次運行程序雖然沒有奔潰,但也沒有任何反應(yīng),Toast也沒顯示。

那么Handler到底是什么呢?他怎么就這么神奇。

實現(xiàn)機制解析

首先,我們從整體上了解一下,在整個Handler機制中所有使用到的類,主要包括Message,MessageQueue,Looper以及Handler。

Handler

好了,為了方便后面的敘述,我們就首先了解一下這個類圖中使用到幾個類,及其關(guān)鍵方法。

Message

首先看一下Message這個類的定義(截取部分)

 public final class Message implements Parcelable {

    public int what;
    public int arg1; 
    public int arg2;
    public Object obj;
    /*package*/ Handler target;
    /*package*/ Runnable callback;
    
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            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();
    }
    
    /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
    */
    public Message() {
    }
}

看到這個類的前四個屬性,大家應(yīng)該很熟悉,就是我們使用Handler時經(jīng)常用到的那幾個屬性。用來在傳遞我們特定的信息。其次我們還可以總結(jié)出以下信息:

  • Message 實現(xiàn)了Parcelable 接口,也就是說實現(xiàn)了序列化,這就說明Message可以在不同進程之間傳遞。
  • 包含一個名為target的Handler 對象
  • 包含一個名為callback的Runnable 對象
  • 使用obtain 方法可以從消息池中獲取Message的實例,也是推薦大家使用的方法,而不是直接調(diào)用構(gòu)造方法。

MessageQueue

MessageQueue顧名思義,就是上面所說的Message所組成的queue。

首先看一下構(gòu)造方法:

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

接收一個參數(shù),決定當前隊列是否允許被終止。同時調(diào)用 一個native方法,初始化了一個long類型的變量mPtr。

同時,在這個類當中,還定義了一個next 方法,用于返回一個Message 。

Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
    ……
    }

由于這個方法中有一些native調(diào)用,未能完全理解,只知道會返回一個Message對象。

這個next方法相當于是隊列出棧,有出棧必然有進棧,enqueueMessage 方法就是完成這個操作;這個我們后面再說。

Looper

上面說到了MessageQueue,那么這個Queue又是由誰創(chuàng)建的呢?其實就是Looper。關(guān)于Looper有兩個關(guān)鍵方法:

prepare()loop()

Looper-prepare()

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

可以看到,對于每一個線程只能有一個Looper。也就是說執(zhí)行prepare方法時,必然執(zhí)行最后一行代碼
sThreadLocal.set(new Looper(quitAllowed));

我們再看Looper(quitAllowed)方法:

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

這樣,MessageQueue 就被創(chuàng)建了。這里也可以看到,默認情況下,一個MessageQueue的quiteAllow=true。

這里使用到的sThreadLocal 是一個ThreadLocal對象。簡單來說,使用它可以用來解決多線程程序的并發(fā)問題。使用set方法,將此線程局部變量的當前線程副本中的值設(shè)置為指定值;使用get方法,返回此線程局部變量的當前線程副本中的值。

Looper-loop()

再看一下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;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            msg.target.dispatchMessage(msg);
            msg.recycleUnchecked();
        }
    }

首先看第一句代碼執(zhí)行的方法:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

很明顯,這樣返回的Looper就是剛才prepare時set進去的那個,因為都是在同一線程。再明確一下,一個線程對應(yīng)一個Looper。

這樣就確保我們可以在不同的線程中創(chuàng)建各自的Handler,進行各自的通信而不會互相干擾

回到代碼,后面邏輯就很簡單了,在一個死循環(huán)中,通過隊列出棧的形式,不斷從MessageQueue 中取出新的Message,然后執(zhí)行msg.target.dispatchMessage(msg) 方法,還記的前面Message類的定義嗎,這個target屬性其實就是一個Handler 對象,因此在這里就會不斷去執(zhí)行Handler 的dispatchMessage 方法。如果取出的Message對象為null,就會跳出死循環(huán),一次Handler的工作整個就結(jié)束了。

Handler

上面說了這么多終于輪到Handler,那么就看看在Handler中到底發(fā)生了什么。回到我們一開始的代碼。

        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                String currentName=Thread.currentThread().getName();
                Toast.makeText(mContext, "I'm new Thread "+currentName, Toast.LENGTH_SHORT).show();
            }
        }, 4000);

這里我們用Toast彈出了當前線程的name,結(jié)果發(fā)現(xiàn)這個線程的名字居然是main,這也是必然結(jié)果

讓我們一步一步看看,神奇的Handler到底是怎樣工作的。就從這個代碼開始解讀。首先看一下Handler的構(gòu)造方法。

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

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

這里做的事情很簡單,就是完成了一些初始化的工作,調(diào)用Looper.myLooper()賦值給當前mLooper,關(guān)聯(lián)MessageQueue;這里由于代碼中調(diào)用的是不帶任何參數(shù)的構(gòu)造函數(shù),因此會創(chuàng)建一個mCallback=null且非異步執(zhí)行的Handler 。

接下看postDelayed 方法。

public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

這里通過getPostMessage(Runnable r) 方法,把我們在Activity里寫的Runnable 這個線程賦給了Message 的callback這個屬性。

平時大家使用Handler也發(fā)現(xiàn)了,他為我們提供了很多方法

handler

因此,上面的postDelayed經(jīng)過了各種輾轉(zhuǎn)反側(cè),最終來到了這里:

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

經(jīng)過之前的構(gòu)造方法,mQueue顯然不為null,繼續(xù)往下看

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

注意,注意,注意 這里進行了一次賦值:

msg.target = this;

前面提到,這個target就是一個Handler對象,因此這里Message就和當前Handler關(guān)聯(lián)起來了。enqueueMessage,哈哈,這就是我們之前在MessageQueue中提到的進棧操作的方法,我們看一下:

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            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 {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

這個方法就是典型的隊列入隊操作,只不過會根據(jù)Message這個對象特有的一些屬性,以及當前的狀態(tài)是否inUse,是否已經(jīng)被quit等進行一些額外的判斷。

這樣,我們就完成消息入隊的操作。還記得我們在Looper中說過,在loop方法中,會從MessageQueue中取出Message 并執(zhí)行他的dispatchMessage 方法。

**dispatchMessage **

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

到這里,就很明確了,在之前的postDelayed 方法中,已經(jīng)通過getPostMessage,實現(xiàn)了 m.callback = r;這樣這里就會執(zhí)行第一個if語句:

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

這樣,就會執(zhí)行我們在Activity 的Runnable 中的run 方法了,也就是顯示Toast。

到了這里,我們終于明白了,使用Handler 的postDelay 方法時,其Runnable中的run方法并不是在子線程中執(zhí)行,而是把這個Runnable賦值給了一個Message對象的callback屬性,而這個Message會被傳遞到創(chuàng)建Handler所在的線程,也就是這里的主線程,所以這個Toast的顯示依舊是在主線程中。這也和postDelay API 中所聲明的內(nèi)容是一致的。

/**
* Causes the Runnable r to be added to the message queue, to be run
* after the specified amount of time elapses.
* The runnable will be run on the thread to which this handler
* is attached.
*/

到這里,一開始所說的第一個代碼塊所執(zhí)行的邏輯已經(jīng)理清楚了,但是還是有一點疑問,我們并沒有在Handler的構(gòu)造方法中看到Looper 的prepare()方法和loop() 方法被執(zhí)行,那么他們到底是在哪里執(zhí)行的呢?這個問題我也是疑惑了很久,最終才明白是在
ActivityThread的main方法中執(zhí)行。簡單來說,ActivityThread是Java層面一個Android程序真正的入口。關(guān)于ActivityThread更多的內(nèi)容可以看看這篇文章。

ActivityThread-main方法(截取主要部分)

public static void main(String[] args) {
      
        
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

這個類藏的比較深,你可以在Android-SDK\sources\android-24\android\app 這個目錄中找到。

也就是說,在一個Android 程序啟動之初,系統(tǒng)會幫我們?yōu)檫@個主線程創(chuàng)建好Looper。只不過這個方法名字比較特殊,叫做prepareMainLooper。

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

注意這里調(diào)用prepare時傳遞的參數(shù)值為false,和我們之前創(chuàng)建普通Looper時是不同的,這也 可以理解,因為這是主線程,怎么可以被允許被外部代碼終止呢。

到這里,我們終于完整的理解了開頭我們提到的第一個代碼塊的內(nèi)容了。

至于第二種使用寫法出錯的原因也在明顯不過了,主線程會在程序啟動時在main方法中幫我們主動創(chuàng)建Looper,調(diào)用loop方法;而我們自己創(chuàng)建的線程就得我們主動去調(diào)用Looper.prepare(),這樣才能保證MessageQueue被創(chuàng)建,程序不會奔潰;但是我們所期望的Toast依然沒有顯示出來,這是為什么呢?因為,我們沒有調(diào)用loop方法。消息被加入隊列了,但是沒有辦法彈出。因此我們將代碼修改如下:

        new Thread(new Runnable() {
            @Override
            public void run() {

                Looper.prepare();

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Toast.makeText(mContext, "I'm new Thread !", Toast.LENGTH_SHORT).show();

                Looper.loop();

            }
        }).start();

這樣就沒問題了,Toast就可以顯示出來了。實際上,平時寫代碼肯定不會這么寫,這里只是為了說明問題。

handleMessage

回想一下,我們使用Handler最常見的場景:

handler = new MyHandler();

private class MyCallback implements Callback {

        @Override
        public void onFailure(Call call, IOException e) {
            Message msg = new Message();
            msg.what = 100;
            msg.obj = e;
            handler.sendMessage(msg);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Message msg = new Message();
            msg.what = 200;
            msg.obj = response.body().string();
            handler.sendMessage(msg);
        }
    }

上面的代碼是OKHttp的回調(diào)方法,由于其回調(diào)方法不處于UI 線程,因此需要我們通過Handler將結(jié)果發(fā)送到主線中取執(zhí)行。
那么這又是怎樣實現(xiàn)的呢?

前面我們截圖說過,Handler為我們提供許多sendMessage 相關(guān)的方法,因此這里我們在onResponse 中執(zhí)行的sendMessage 經(jīng)過層層傳遞,殊途同歸依然會回到MessageQueue的enqueueMessage方法,也就是說所有的sendMessageXXX方法完成的工作無非就是隊列入棧的工作,就是將包含特定信息的Message加入到MessageQueue中。而我們也知道,通過loop方法,會從MessageQueue中取出Message,執(zhí)行每一個Message 所對應(yīng)Handler的dispatchMessage方法,我們再看一次這個方法:



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

這一次,我們創(chuàng)建的Message很簡單,其callback屬性必然是空的,而且在實例化Handler時,調(diào)用的是其無參構(gòu)造函數(shù) ,因此這個時候,就會執(zhí)行最后一行代碼handleMessage(msg) ;

/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

空的 ! 沒錯,這個方法就是空的,因為這是需要我們在Handler的繼承類中自己實現(xiàn)的方法呀。比如下面這樣;

class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            loading.setVisibility(View.GONE);
            switch (msg.what) {
                case 100:
                    Object e = msg.obj;
                    Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
                    break;
                case 200:
                    String response = (String) msg.obj;
                    tv.setText(response);
                    break;
                default:
                    break;
            }
        }
    }

我們在handleMessage方法中,實現(xiàn)了自己的處理邏輯。

總結(jié)

好了,這就是Handler的實現(xiàn)機制,這里再做一次總結(jié)稱述。

  • 通過Looper的prepare方法創(chuàng)建MessageQueue
  • 通過loop方法找到和當前線程匹配的Looper對象me
  • 從me中取出消息隊列對象mQueue
  • 在一個死循環(huán)中,從mQueue中取出Message對象
  • 調(diào)用每個Message對象的msg.target.dispatchMesssge方法
  • 也就是Handler的dispatchMessage 方法
  • 在dispatchMessage 根據(jù)Message對象的特點執(zhí)行特定的方法。

至此,終于弄明白了Handler的運行機制。

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

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

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