Handler:你真的瞭解我嗎?

提到handler,大家都想到些什麼呢,切換線程?延時操作?
那麼你是否瞭解IdleHandler,同步屏障,死循環的設計原理?以及由Handler機制衍生的IntentService,BlockCanary
這次我們說下Android中最常見的Handler,通過解析面試點或者知識點,帶你領略Handler內部的神奇之處。

先說下基本的用法和工作流程

用法很簡單,定義一個handler,重寫handleMessage方法處理消息,用send方法通知handler
但是主線程和其他線程用法卻有點不一樣!其實新線程裏面的用法纔是表達出完整流程的。

Handler handler = new Handler() {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
};
handler.sendEmptyMessage(0);
handler.sendEmptyMessageDelayed(0, 1000);


new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler() {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
            }
        };
        Looper.loop();
    }
}).start();

Looper.prepare()方法就是創建looper對象,並且綁定到該線程。
然後定義一個handler,loop()方法中主要是looper對象會不斷從MessageQueue中獲取message並且發送給對應的hander。
所以looper相當於一個工具人,handler說要發送消息啦,工具人looper就把消息放到消息隊列裏面進行管理
同時他會不停的loop,從消息隊列裏面拿出隊頭消息給到當初的那個對應的handler,
最後handler通過handlemessage進行消息處理

所以呢,handler內部其實就是三部分

  • looper,關聯線程並且負責從消息隊列拿出消息分發給handler
  • MessageQueue,消息隊列,負責消息存儲管理
  • Message,消息對象
  • handler,負責發送和處理消息

用法和流程就這麼多,下面開始知識點講解,具體剖析Handler內部奧祕

知識點,面試點

知識點一:主線程的looper呢??

看下源碼,這裏需要涉及到的一個類android.app.ActivityThread,這個類中的main方法是整個app的最開始執行的方法,是app的入口,看下main方法源碼

Looper.prepareMainLooper();
// ***
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
}
if (false) {
    Looper.myLooper().setMessageLogging(new
    LogPrinter(Log.DEBUG, "ActivityThread"));
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
Looper.prepareMainLooper();
Looper.loop();

其中最重要的就是這兩句,調用了prepareMainLooper方法創建了主線程的Looper,然後調用loop方法開始***死循環***

ok,Looper是找到了。那Looper爲什麼可以一直取消息呢?看看源碼

//Looper
public static void loop() {
    //...
    for (; ; ) {
        // 不斷從 MessageQueue 獲取 消息
        Message msg = queue.next();
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //...
    }
}

找到原因了。在loop方法裏面有一個死循環,通過不斷的從MessageQueue獲取消息然後傳給Handler,所以Looper可以一直執行工具人的工作

知識點2:爲什麼有死循環呢?這種寫法科學嗎?不會oom嗎??

說白了,其實死循環也是有意爲之,線程在可執行代碼執行完後,就會終止,而主線程肯定需要一直運行,所以死循環就能保證這一點。
死循環之外怎麼處理事務?
既然主線程是死循環,那麼如果有其他事務該怎麼處理呢?創建新線程唄,在主線程創建之前會創建一些其他的binder線程,比如ApplicationThraed
死循環是不是會浪費cpu資源
主線程的messageQueue在沒有消息的時候,會阻塞在loop的queue.next方法中,此時主線程會釋放CPU資源,進入休眠狀態,直到下個消息來到,所以不會一直消耗CPU資源。
而activity的生命週期是怎麼實現在死循環體外正常執行的呢?
其實就是通過這個handler,比如onPause方法,當主線程Looper在loop的時候,收到暫停的消息,就會把消息分發給主線程的handleMessage處理,然後最後會調用到activity的onPause方法。
那主線程的消息又是哪裏來的呢?剛纔說到主線程之外還會創建一些binder線程,比如app線程,系統線程,一般是系統線程比如ApplicationThreadProxy傳消息給APP線程ApplicationThread,然後再傳給主線程,也就是ActivityThread所在的線程。

知識點3:內存泄漏??

首先爲什麼會發送內存泄漏?
handler作爲內部類會持有外部類的引用,當發送延遲消息時,就有可能發生處理消息的時候,外部類比如activity已經銷燬了,從而導致內存泄漏

怎麼解決?
定義靜態內部類,並且在onDestory裏面移除所有消息

直接onDestory方法裏移除不就行了?爲啥還需要靜態內部類?
onDestory方法不一定執行哦。如果你的Activity不在棧頂,然後app被後臺強殺,那麼onDestory就不會被執行了。所以這種辦法更加科學有效。

上代碼

private static class MemoryLeakSafeHandler extends Handler {

    private WeakReference<HandlerInfoActivity> ref;

    public MemoryLeakSafeHandler(HandlerInfoActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerInfoActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}

MemoryLeakSafeHandler handler;

public void handleMessage(Message msg) {

}

@Override
protected void onDestroy() {
    handler.removeCallbacksAndMessages(null);
    super.onDestroy();
}

知識點4:IdleHandler是什麼,有什麼用呢?

IdleHandler是在Hanlder空閒時,也就是沒有可處理的消息時候,用來處理空閒任務的一種機制。
有什麼作用呢?主要是用於提升性能,可以在消息隊列空閒時做一些事情,從而不影響到主線程的任務處理。(卑微小弟,你們重要的大哥們先用,我最後再用)。

用法如下:

Looper.myQueue().addIdleHandler(new IdleHandler() {  
    @Override  
    public boolean queueIdle() {  
        //do something
        return false;    
    }  
});

這裏queueIdle方法的返回參數是bool類型,true代表執行一次後不刪除,下次進入空閒時還會調用該回掉方法。false代表執行一次後該IdleHandler就會被刪除。
源碼在MessageQueue類的next方法,其實就是在消息隊列裏面沒有消息的時候會去查詢mIdleHandlers隊列,mIdleHandlers隊列有數據,也就是有IdleHandler就會去處理執行。
還是簡單放下源碼吧:

    Message next() {
        for (;;) {
            synchronized (this) {
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

        }
    }

有人可能要問了,這玩意真的有用嗎?確實有用,只是你沒用到而已。下面舉例兩個場景

  • 比如我要提升頁面的啓動速度,就可以把onCreate,onResume裏面的一些操作放到IdleHandler裏面執行,減少啓動時間。
  • Leakcanary等三方庫也用到了這個類,用來幹嘛呢?監聽主線程的UI操作已完成。既然都執行到我這裏來了,就說明UI操作都完成了是吧。

知識點5:同步屏障機制是什麼,有什麼用呢?

還是看這個next獲取消息的方法:

Message next() {
        for (; ; ) {
            synchronized (this) {
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        return msg;
                    }
                } 
            }
        }
    }

可以看到一開始就會判斷當前消息的兩個條件:

  • msg不爲空
  • msg的target爲空(target其實就是對應的Handler)

如果符合條件那麼這種消息就是屬於同步屏障消息,如果遇到這種消息,就會進入一個dowhile循環,找出消息隊列中的異步消息並返回。
所以這個同步屏障機制就是爲了讓handler能夠先執行異步消息,再去執行同步消息,直到屏障被移除。
慢着,我之前咋沒聽過還有異步消息?哈哈。確實是有的,Message有個setAsynchronous方法,如果傳入true,就代表這個消息是個異步消息,在同步屏障發生後就可以先執行。目的是爲了插入一些比較重要的消息需要先行處理。

同步屏障的具體使用方法就是:

  • postSyncBarrier方法加入屏障
  • removeSyncBarrier移除屏障

但是這兩個方法都已經標記爲hide了,要使用的話必須使用反射。

ok,瞭解完之後又該有人問了,有什麼用呢?這個同步屏障。如果你看過view繪製的源碼,你就會發現ViewRootImpl類中就用到了這個,由於view繪製的優先級比較高,所以開啓同步屏障後,就可以選擇讓某些操作先執行,提高了優先級,比如這個view的繪製處理操作。

咦,這個機制感覺跟之前的IdleHandler是對應關係啊,一個是卑微小弟?一個是在線大哥?

知識點6:Handler機制還還還有什麼其他的用處或者實際應用嗎?

當然有啦,舉🌰:

BlockCanary

一個用來檢測應用卡頓耗時的三方庫。它的原理就在於Handler相關的Looper類裏面,上面說過,Activity的生命週期都是依靠主線程的Looper.loop(),所以主線程的操作基本都是在handler處理中發生的。那有沒有什麼辦法可以查看handler處理的耗時時間呢?如果知道了耗時時間不就知道哪裏卡頓了?上源碼:

public static void loop() {
    ...

    for (;;) {
        ...

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

        ...
    }
}

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

在loop方法內,有個Printer類,用來打印日誌。我們可以看到在dispatchMessage方法前後分別打印了一次日誌,而dispatchMessage方法就是Handler用來處理消息的地方。那麼,我們如果能獲取到這兩次打印日誌的時間差,不就可以得到Handler處理消息的耗時時間了?所以我們直接替換這個Printer就可以達到我們的目的了:

Looper.getMainLooper().setMessageLogging(mainLooperPrinter);

這麼簡單有效的方法,讓我不得不給BlockCanary作者點個👍

IntentService

IntentService 是一個繼承自Service,自帶工作線程,並且線程任務結束後自動銷燬的一個類。Service是啥?可以統一管理後臺任務,運行在前臺,所以可以獲取到上下文。
IntentService同樣具有這些特性,並且可以在新線程管理任務,工作執行完自動銷燬。就線程池來說區別就在與IntentService擁有Service的特性,所以在需要用到上下文的時候就可以選擇IntentService。而IntentService內部其實就是用到了HandlerThread,也就是帶有Handler機制的線程。還是來點源碼:

    @Override
    public void onCreate() {
        super.onCreate();
        //創建新線程並start
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        mServiceLooper = thread.getLooper();
        //創建新線程對應的handler
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
       //service啓動後發送消息給handler
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
    
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
                //handler收到消息後調用onHandleIntent方法
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

至此,Handler的大概已經瞭解的差不多了,是不是覺得Handler太神奇了,你也忍不住想去好好看看它的源碼了呢?也許還有一些功能沒被利用起來,等着你去發現🚀🚀🚀

有說的不對的地方望指正,謝謝。🙏


參考鏈接:
知乎中關於爲什麼不會死循環卡死的回答
BlockCanary作者寫的介紹

你的一個👍,就是我分享的動力❤️。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章