Handler源碼解讀——handler使用時(shí)的注意事項(xiàng)

Handler源碼解讀——handler使用時(shí)的注意事項(xiàng)

工作中經(jīng)常會(huì)遇到從子線程發(fā)送消息給主線程,讓主線程更新UI的操作,常見的有handler.sendMessage(Message),和handler.post(runnable)和handler.postDelayed(runnable, milliseconds);一直在使用這些方法,卻不知道他們的原理,今天就來解釋一下他們的原理。

先來一個(gè)常見的handler.sendMessage(message)的例子吧。代碼如下:

定義一個(gè)handler的子類,并實(shí)現(xiàn)它的handleMessage()方法:這里的msg.what的值是你自己定義的,就不多解釋了。

private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 1000:
                int arg = msg.arg1;
                // TODO: 17/3/22
                break;
            case 1001:

                break;
            default:
                break;
        }
    }
};

接下來是在子線程中發(fā)送消息的部分:

//handler.sendMessage(Msg)
new Thread(new Runnable() {
    @Override
    public void run() {
        Message msg = Message.obtain();
        msg.what = 1000;
        msg.arg1 = 10;
        handler.sendMessage(msg);
    }
}).start();

可以看到,我在一個(gè)新開的子線程中使用剛才定義的handler發(fā)送了一個(gè)Message對(duì)象,這個(gè)Message對(duì)象可以攜帶數(shù)據(jù),這里簡(jiǎn)單起見,我只攜帶了一個(gè)int類型的值10.

至于為什么在子線程中使用handler發(fā)送的Message,可以在UI線程中執(zhí)行了呢?這是因?yàn)樵贏ndroid的主線程中有一個(gè)Loop循環(huán)器,它一直在輪詢這個(gè)Loop循環(huán)器所屬的MessageQueue消息隊(duì)列中的消息,如果MessageQueue里面有Message,就調(diào)用這個(gè)Message.target.dispatchMessage(Message)方法,這里其實(shí)是調(diào)用了handler對(duì)象的dispatchMessage()方法,這個(gè)方法里面最終(滿足一定條件)會(huì)調(diào)用handler的handleMessage(Message)方法,也就是在主線程中調(diào)用了我們剛才定義的handler的handleMessage(Message)方法。

好吧,說了這么多,先來個(gè)精簡(jiǎn)版的簡(jiǎn)圖,接下來再用源碼解釋原理:

[圖片上傳失敗...(image-9ea33-1562745635646)]

ps: 上圖所涉及的源碼,可在Android Studio中可直接查看

1、ActivityThread類的main方法中,最后調(diào)用了Loop.loop()方法,開啟了主線程。

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

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

2、Loop.loop()方法中:

源代碼注解:在這個(gè)線程中一直調(diào)用MessageQuene隊(duì)列,確保loop被quit之前一直調(diào)用。

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;

        // 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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

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

①獲取當(dāng)前線程的loop對(duì)象。

②獲取loop中的MessageQueue。

③使用for( ; ; )的開啟一個(gè)死循環(huán),開始輪詢MessageQueue中的消息。

④如果消息不為空,則調(diào)用Message對(duì)象的target的dispatchMessage(msg)方法

3、msg.target.dispatchMessage(msg)

Message類中:
    /*package*/ Handler target;

①target對(duì)象為Handler類型,表示發(fā)送這個(gè)消息的handler對(duì)象

②可以通過obtain的一系列重載方法給message設(shè)置target,通過無參數(shù)的obtain方法獲取到的message的handler為null;

③target對(duì)象有set和get方法

public void setTarget(Handler target) {
    this.target = target;
}

/**
 * Retrieve the a {@link android.os.Handler Handler} implementation that
 * will receive this message. The object must implement
 * {@link android.os.Handler#handleMessage(android.os.Message)
 * Handler.handleMessage()}. Each Handler has its own name-space for
 * message codes, so you do not need to
 * worry about yours conflicting with other handlers.
 */
public Handler getTarget() {
    return target;
}

④至于我們這個(gè)handler發(fā)送的message對(duì)象中的target是如何與handler勾搭上的,留待后邊說明。

4、Handler.dispatchMessage(message)方法:

/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

這段代碼表明,dispatchMessage方法中可能會(huì)調(diào)用handleMessage(msg)方法,不過需要滿足幾個(gè)條件。好吧,這個(gè)問題留待后邊解決。我們現(xiàn)在已經(jīng)簡(jiǎn)單的看完了handler在子線程發(fā)送消息,最終在主線程進(jìn)行處理的流程。接下來我們需要解決過程中遇到的幾個(gè)困惑。

困惑一:第3步時(shí),handler發(fā)送的message對(duì)象中的target是如何與這個(gè)handler勾搭上的。

困惑二:第4步時(shí),在dispatchMessage方法中,執(zhí)行handleMessage(msg)方法的條件如何解釋。

解答一:handler發(fā)送的message的target對(duì)象是如何與這個(gè)handler勾搭上的。
①我們得從handler發(fā)送消息說起,先來看我們?cè)谧泳€程中發(fā)送消息的代碼:來找找handler對(duì)象是何時(shí)設(shè)置給message.target的。

        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = Message.obtain();
                msg.what = 1000;
                msg.arg1 = 10;
                handler.sendMessage(msg);
            }
        }).start();

②點(diǎn)進(jìn)Message.obtain()方法:

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

里面的sPool是Message類型,是為了Message的復(fù)用,這里沒有設(shè)置target的數(shù)據(jù),再看最下邊的new Message();

/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}

好吧,這里是個(gè)空實(shí)現(xiàn),啥也沒有。

③既然在Message里面沒有找到,我們就在handler.sendMessage(msg)里面找找,點(diǎn)進(jìn)去:

public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

沒有,繼續(xù)找:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
     }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

繼續(xù):

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

這里拿到了當(dāng)前的消息隊(duì)列,不過依然沒有我們要找的,繼續(xù):

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

好了,找到了。enqueueMessage()方法的第一步就給msg.target設(shè)置了值,就是當(dāng)前的handler對(duì)象,也就是發(fā)送消息的那個(gè)handler。這個(gè)方法的最后一句將這個(gè)msg添加進(jìn)當(dāng)前的MessageQueue,這樣,進(jìn)入隊(duì)列的message就攜帶上Handler類型的target對(duì)象了。

到現(xiàn)在為止,我們已經(jīng)說明,在Loop.loop()方法里的那句msg.target.dispatchMessage(msg),調(diào)用的就是當(dāng)前hander對(duì)象的dispatchMessage(msg)方法了。

解答二:在dispatchMessage方法中,執(zhí)行handleMessage(msg)方法的條件是怎么回事
①先上dispatchMessage(msg)源碼:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

具體流程如圖所示:
[圖片上傳失敗...(image-f7260-1562745635646)]

疑惑又來了,msg.callback是什么鬼?handleCallback(msg)方法是干嘛的?mCallback是什么鬼?它后邊的mCallback.handleMessage(msg)方法又是干嘛的?

②msg.callback和handleCallback(msg)方法;

進(jìn)入Message類中,發(fā)現(xiàn)callback成員變量為Runnable類型,Runnable接口自不必多說,它里面就一個(gè)無參無返回值的run方法;

在Message中是搜索callback,發(fā)現(xiàn)可以給它賦值的位置只有靜態(tài)方法obtain()的兩個(gè)重載方法:

    public static Message obtain(Message orig) {
        Message m = obtain();
        m.what = orig.what;
        m.arg1 = orig.arg1;
        m.arg2 = orig.arg2;
        m.obj = orig.obj;
        m.replyTo = orig.replyTo;
        m.sendingUid = orig.sendingUid;
        if (orig.data != null) {
            m.data = new Bundle(orig.data);
        }
        m.target = orig.target;
        m.callback = orig.callback;

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

    return m;
}

再看一眼剛才的handleCallback(msg)方法:它就是調(diào)用message.callback.run方法。

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

也就是說,只有當(dāng)我們直接通過Message.obtain(Message)和Message.obtain(Handler,Runnable)方法來獲取Message對(duì)象的時(shí)候(或者設(shè)置message.callback的值不為空),才有可能讓msg.callback不為空,才會(huì)調(diào)用callback.run方法,此時(shí)就絕對(duì)不會(huì)調(diào)用我們的handleMessage(msg)方法。

③接下來看mCallback和mCallback.handleMessage(msg)是何方神圣;
mCallback是Handler里面定義的一個(gè)Callback接口類型,

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 *
 * @param msg A {@link android.os.Message Message} object
 * @return True if no further handling is desired
 */
public interface Callback {
    public boolean handleMessage(Message msg);
}

上方注釋的意思是:當(dāng)你實(shí)例化一個(gè)Handler的時(shí)候,可以通過這個(gè)可以接口避免自己寫一個(gè)Handler的實(shí)現(xiàn)類。

這句話是什么意思呢?且往下看。

在Handler類里面找給mCallback賦值的地方,找到三處,都是Handler的帶參構(gòu)造方法:

    public Handler(Callback callback) {
        this(callback, 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;
    }
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

看到這兒,Handler里面這個(gè)接口的注釋應(yīng)該很好理解了。如果你使用上邊這兩個(gè)Handler的構(gòu)造方法來實(shí)例化一個(gè)Handler對(duì)象,那么你需要傳入一個(gè)Callback接口類型的參數(shù),在callback的handleMessage(msg)方法里面,你就可以直接寫主線程處理Message的邏輯了;此時(shí)我們就不需要再寫Handler的實(shí)現(xiàn)類,重寫handler的handleMessage(msg)方法了;另外,這個(gè)Callback接口里的handleMessage(msg)方法有返回值,如果你返回為true,就不會(huì)執(zhí)行handler.handleMessage(msg)方法,如果返回為false,依舊會(huì)執(zhí)行handler.handleMessage(msg)方法。這就給我們使用handler提供了多種選擇。

這里插一句嘴,上方的兩個(gè)Handler的構(gòu)造方法中,都有一個(gè)boolean類型的async參數(shù),同學(xué)們可能不知道怎么調(diào)用,沒關(guān)系,看看Handler()這個(gè)空參構(gòu)造是怎么寫的:

public Handler() {
    this(null, false);
}

這個(gè)this(null,false)調(diào)用的就是Handler(Callback callback,boolean async)這個(gè)方法,也就是說,默認(rèn)的我們傳入的async這個(gè)參數(shù)為false就可以。至于這個(gè)參數(shù)何時(shí)為true,何時(shí)為false,這個(gè)問題留待后續(xù)研究,這次不做講解。

好,原理和流程解釋完了,來對(duì)關(guān)鍵流程做下總結(jié)。

[圖片上傳失敗...(image-ddd051-1562745635646)]

handler.dispatchMessage(msg)里面的處理,前面已經(jīng)解釋過了,這里不再重復(fù)。

使用總結(jié):

①在使用handler發(fā)送消息的時(shí)候,要記住,自己實(shí)現(xiàn)的handleMessage(msg)方法不一定會(huì)執(zhí)行。

②如果我們發(fā)送的Message的callback參數(shù)(Runnable類型)不為空,那么就不會(huì)執(zhí)行handler的handleMessage(msg)方法,只會(huì)執(zhí)行message.callback的run方法。這種情況主要發(fā)生在我們使用handler.post(runnable)和handler.postDelayed(runnable, milliseconds)方法發(fā)送消息的時(shí)候。另外,在Message對(duì)象的callback的參數(shù)不為空的情況下,如果我們?cè)趧?chuàng)建Handler對(duì)象的時(shí)候既傳入了Callback接口參數(shù),也重寫了里面的handleMessage(msg)方法,那么此時(shí)依舊只會(huì)執(zhí)行message.callback的run方法。

③在我們發(fā)送的Message的callback參數(shù)(Runnable類型)為空的情況下(默認(rèn)為空),在使用Handler的帶參構(gòu)造方法初始化Handler的時(shí)候,如果傳入了Callback類型的參數(shù),并且那么這個(gè)接口里面的方法肯定會(huì)被調(diào)用;如果同時(shí)我們也重寫了該handler對(duì)象的handleMessage(msg)方法,那么handler對(duì)象的handleMessage(msg)方法不一定執(zhí)行,取決于Callback接口中handleMessage(msg)方法的返回值,如果為true,那么handler對(duì)象的handleMessage(msg)方法不會(huì)執(zhí)行,為false時(shí),handler對(duì)象的handleMessage(msg)方法會(huì)在后邊執(zhí)行。

關(guān)于handler的總結(jié),希望對(duì)你有用。

?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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