Android學(xué)習(xí)之Handler

Handler內(nèi)存泄露

sendMessage方法內(nèi)存泄露

有這么一個需求,延遲執(zhí)行一段邏輯,先看第一種方式,直接讓線程sleep:

 private val handler2 = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            startActivity(Intent(this@HandlerActivity, XXXActivity::class.java))
        }
    }
    
     private fun test() {
        thread {
            val message = Message()

            SystemClock.sleep(3000) //1

            message.apply {
                what = 3
                obj = "hahaha"
            }
            handler2.sendMessage(message)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("XX", "onDestroy")
        handler2.removeMessages(3) //2
    }

從上面的代碼里看到在代碼1處讓程序休眠3秒,然后點擊返回按鈕銷毀此Activity,那么即使在onDestroy方法里移除掉這個message,也是沒有效果的。原因在于此時這個message還沒有添加到MessageQueue里,所以移除的是null。
那么該如何解決呢,使用以下代碼:

 private fun test() {
        thread {
            val message = Message()
            message.apply {
                what = 3
                obj = "hahaha"
            }
            handler2.sendMessageDelayed(message,3000)
        }
    }

使用sendMessageDelayed方法執(zhí)行延遲操作,即可以避免帶來的內(nèi)存泄露。

為什么不能在子線程new Handler

我們在單獨的線程里初始化一個Handler

private fun test() {
        thread {
           Handler()
        }
    }

然后在onCreate方法里調(diào)用,發(fā)現(xiàn)會閃退,報了一個錯誤,如下:

java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-4,5,main] that has not called Looper.prepare()

那么為什么我們在子線程中new Handler會報這個異常呢,原因是我們的Handler需要初始化一個loop對象,而我們沒有做。那么為什么在Android的主線程中我們可以直接使用Handler而不報錯呢,是因為在應(yīng)用啟動的時候已經(jīng)幫我們調(diào)用了初始化loop。在ActivityThread類里main方法中已經(jīng)幫我們初始化好了loop,這個loop綁定的是主線程。

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

        Looper.prepareMainLooper();

        ......
 }

Looper.prepareMainLooper();就是這行代碼完成了loop的初始化工作。那么我們再進入到這個方法中看看:

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方法,然后初始化sMainLooper對象,并且可以看出sMainLooper只能被初始化一次,否則被拋出異常。
再進入到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));
    }
    
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

從代碼里可以看出,prepare方法初始化了looper對象,并且在looper中綁定了當前線程和new除了一個MessageQueue。并且把這個looper對象放入了sThreadLocal中。然后通過調(diào)用myLooper()方法從sThreadLocal中取得looper對象,至此完成looper的初始化工作。

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

那么,我們該如何在子線程中使用Handler呢,很簡單,在我們使用Handler之前調(diào)用一下Looper的prepare方法即可:

    private fun test() {
        thread {
            Looper.prepare();
            Handler()
        }
    }

這樣的話,這個Handler就是運行在我們new出來的子線程中了,當然這個Handler也不能去更改UI了。

更改UI只能在主線程中操作嗎

我們學(xué)習(xí)Android的時候就知道,不能在非UI線程中去更新UI,否則會報錯,那么真的是絕對的嗎,看下面的代碼:

    private fun test() {
        thread {
            btnTxt.text = "我哦喔喔"
        }
    }

我們在子線程中將一個Button控件的text值修改,并且成功運行沒有報錯,但是在某些手機或者某些系統(tǒng)上就會拋出異常。那么是為什么呢?原因是我們在調(diào)用setText的時候會調(diào)用requestLayout();方法,這個方法里又會調(diào)用ViewRootImpl的requestLayout方法:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

在這個方法中就會檢查線程是否正確,即調(diào)用checkThread方法:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

這里就會做線程的檢查,如果不是主線程,就會拋出異常,但是在執(zhí)行requestLayout方法時還會并行的執(zhí)行invalidate方法,所以,有可能頁面invalidate方法先執(zhí)行了,然后才觸發(fā)checkThread方法,那么就不會拋出異常。
我們可以修改一下上面的代碼驗證一下:

    private fun test() {
        thread {
            SystemClock.sleep(1000)
            btnTxt.text = "我哦喔喔"
        }
    }

我們讓修改的代碼延遲一秒執(zhí)行,可以發(fā)現(xiàn),程序就閃退了,并且拋出了上面的異常。


屏幕快照 2019-08-11 17.38.54.png

Handler的dispatchMessage方法分析

我們在通過Handler去發(fā)送消息,并執(zhí)行的時候可以有三種方式:

    //方式一
    private val handler = Handler(Handler.Callback {
        when (it.what) {
            3 -> {}
            else -> {}
        }
        false
    })
    
    //方式二
    private val handler2 = object : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            startActivity(Intent(this@HandlerActivity, AopActivity::class.java))
        }
    }
    
    //方式三
    handler.post(){
            btnTxt.text = "handler"
    }
    

那么這三種方式有什么區(qū)別呢,答案就在Handler的dispatchMessage方法源碼里:

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

注釋1處的代碼

讓我先看注釋1處的代碼,也就是dispatchMessage首先判斷了msg里的callback是否是null,如果不為null,那么就會調(diào)用handleCallback方法。那么這個callback是什么呢,就是我們調(diào)用Handler.post時傳進來的Runnable。

    public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

可以看到我們調(diào)用post方法時,會將我們傳進來的Runnable封裝到Message對象里并且返回,那么在dispatchMessage方法里這個最先被判斷的就不會為空,也就會執(zhí)行handleCallback方法:

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

這個handleCallback方法也就是我們傳進Runnable的run方法。

注釋2處的代碼

dispatchMessage方法里,如果callback為null了又進行判斷mCallback是否為null。那這個mCallback是什么呢,就是我們初始化Handler對象是在構(gòu)造方法中傳進來的Handler.Callback對象,它是一個被定義在Handler中的接口:

    public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

如果我們在初始化中傳進來這個CallBack,那么將執(zhí)行它里面的handleMessage方法。

注釋3處的代碼

那么,如果以上這兩個對象都為null的話,將調(diào)用Handler內(nèi)部的handleMessage方法,這個方法是個空實現(xiàn),也就是我們自己實現(xiàn)的handleMessage方法。

Handler的發(fā)送消息和執(zhí)行消息過程

Handler的發(fā)送消息

當我們調(diào)用了Handler的sentXXX方法時,到最終都會調(diào)用enqueueMessage方法,這個方法代碼如下:

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

從上面的代碼中可以看出,這個方法做了兩件事

  1. 將當前的Handler對象賦值給msg.target對象
  2. 調(diào)用MessageQueue中的enqueueMessage方法
    那么,我們再到enqueueMessage方法中看一下邏輯:
boolean enqueueMessage(Message msg, long when) {
        ......
     
        synchronized (this) {

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

            ......
        }
        return true;
    }

代碼很長,其中最重要的一句就是mMessages = msg;即將傳進來的msg賦值給全局的mMessages。這個過程就是Handler的發(fā)送消息的過程。

Handler的執(zhí)行消息

那么我們在發(fā)送消息的時候最終將msg賦值到了MessageQueue中的全局對象mMessages中,是如何將它取出執(zhí)行的呢。
首先是通過Looper.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;
            }

            ......
            
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
          ......
    }

這段代碼很長,我截取了比較關(guān)鍵的部分,可以看出loop方法先是取出looper對象,然后從looper對象中取出MessageQueue對象,接著在一個死循環(huán)中取出queue中的msg,如果為null,就返回。否則就調(diào)用msg.target的dispatchMessage方法,那么這里的msg.target就是剛才發(fā)送消息時綁定的Handler對象,所以最終會通過Handler的dispatchMessage方法調(diào)用我們的回調(diào)方法。
至此,Handler的發(fā)送消息和執(zhí)行消息就分析完了。

最后編輯于
?著作權(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)容