掌握關於Handler的這些基本問題,讓你的面試事半功倍!

掌握關於Handler的這些基本問題,讓你的面試事半功倍!

前言

Handler是個老生常談的問題,我相信幾乎全部的Android開發者都會使用Handler,那關於Handler還有什麼好講的嗎?Handler若是僅僅是使用的話,確實沒什麼好講的,可是Handler倒是一個幾乎全部面試官都會問的問題,不一樣的要求問的深度也不同,今天我就帶你們學習一下關於Handler你所必需要掌握的知識。程序員

Handler消息機制

首先有四個對象咱們必需要了解一下HandlerLooperThreadLocal還有MessageQueue面試

Handler

首先咱們要明白Handler消息機制是用來幹嗎的?Handler是把其餘線程切換到Handler所在的線程,注意,這裏不是UI線程。雖然咱們的大多數使用Handler的場景,都是咱們在子線程作了一下耗時的操做(IO或者數據庫),當子線程執行完之後咱們可能須要更新UI,這個時候咱們用Handler來處理(sendMessage()或者post())就把子線程切換到了UI線程了。假如說,咱們如今有這麼一個需求,線程A發個信息給線程B(線程A、線程B都不是主線程),這個時候咱們用Handler依然能夠作,只須要在線程B中建立好Handler就能夠了(關於如何在子線程建立Handler我會在下面詳細說明)。因此,Handler並非把其餘線程切換到主線程(UI線程),而是切換到它所在的線程,這一點必定要弄清楚。數據庫

Looper

Handler消息機制裏面最核心的類,消息輪詢。Handler要想切換線程(或者說發送消息),必需要先得到當前線程的Looper,而後調用Looper.loop()方法把MessageQueue裏面的message輪詢出來"交"給Handler來處理,至於如何「交」給Handler的,下面我會經過源碼帶你們分析。緩存

ThreadLocal

ThreadLocal是Looper內部的一個,它雖然前面有個「Thread」,但其實它並非線程,它至關於一個線程內部的存儲類。剛纔在講Looper的時候咱們說到,「Handler要想切換線程(或者說發送消息),必需要先得到當前線程的Looper」,那如何得到當前線程的Looper呢?正是經過ThreadLocal,它能夠在不一樣線程之間互不干擾地存儲數據。ThreadLocal裏面有一個內部靜態類對象ThreadLocalMap,經過其set()和get()方法對數據對象進行保存和讀取。數據結構

MessageQueue

MessageQueue——消息隊列,雖然它叫消息隊列,但實際上的結構並非一個隊列,而是一種單鏈表的數據結構,只是使用列隊的形式對數據進場作添加或者移除。MessageQueue是在Looper被建立的時候由Looper生成的。同一線程下,Handler發送的全部message都會被加入到MessageQueue裏面,當Message被loop()之後,就會從消息隊列中移除。Message我沒有單獨拿出來,由於確實比較簡單,就是消息自己,它能夠攜帶兩個int型參數,若是要傳比較複雜的參數就用obj屬性,what屬性用來區分是哪一個Handler發送的信息。架構

小結一下

Handler是把其餘線程切換到它所在的線程,使用Handler發消息以前要先建立Looper對象,建立和讀取Looper都須要使用ThreadLocal,它能夠在不一樣線程之間互不干擾地保存數據。在建立Looper對象的同時也把MessageQueue建立好了,Handler所發的message會被添加到MessageQueue對象裏面,Looper.loop()方法之後會把MessageQueue上的Message輪詢出來。鏈接這幾個對象的還有一條很重要的線,那就是線程,記住,以上的過程都要保證Handler、Looper、MessageQueue在同一線程,這樣,才能保證Handler的消息機制被正確使用。ide

子線程建立Handler

注意:咱們這裏是爲了讓你們更好地學習Handler,因此要在子線程建立Handler,現實使用場景,不多會在子線程建立Handleroop

在主線程(UI線程)建立Handler,我相信全部人都會post

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mainHandler = new Handler();
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.e("qige_test", "thread_name=" + Thread.currentThread().getName());
            }
        });
    }

咱們先按照主線程的方式在子線程建立一個Handler試一下,看看會有什麼樣的結果學習

new Thread(){
            @Override
            public void run() {
                childHandler=new Handler();
                childHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

            }
        }.start();

掌握關於Handler的這些基本問題,讓你的面試事半功倍!

沒錯,如圖所示尚未建立Looper,那麼如何建立Looper呢?圖中有句話已經給出了答案——Looper.prepare(),同時爲了讓咱們發送的消息被輪詢到,還必需要調用Looper.loop(); 因此在完整的在子線程建立Handler的代碼以下:

new Thread(){
            @Override
            public void run() {
                //建立Looper,同時建立MessageQueue
                Looper.prepare();

                childHandler=new Handler();
                childHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

                //輪詢
                Looper.loop();

            }
        }.start();

這樣就能夠在子線程裏面建立Handler了,看到這裏,你可能會問,爲何我在主線程建立Handler的時候,沒有調用Looper.prepare()和Looper.loop()?這個問題咱們先放一下,咱們先從源碼角度把Handler消息機制分析一下。

從源碼角度分析

先看一下建立Looper和MessageQueue的方法Looper.prepare()

public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
         //ThreadLocal來了
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

這裏就出現了我剛纔說的ThreadLocal對象。Android規定每一個線程有且僅有一個Looper,因此爲了防止不一樣的線程之間的Looper相互影響,使用ThreadLocal對象來存儲Looper。

再看一眼new Looper()的源碼

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

MessageQueue也出來了,建立Looper的同時建立MessageQueue。

下面咱們看Handler.post()或者是Handler.sendMessage()他們本質是同樣的,把消息加入到消息隊列當中

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

post()或者sendMessagexxx()最終都會調用sendMessageAtTime(Message msg, long uptimeMillis)方法,咱們直接看這個方法的源碼

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

都很簡單,最後一步enqueueMessage(queue, msg, uptimeMillis);是把消息加入隊列,我們再點開看一下

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //把當前的Handler賦值給msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

關鍵要看的地方是 msg.target = this剛纔講Message的時候沒有提,Message的target屬性就是一個Handler對象,這裏直接把當前的Handler對象賦值過去了。最後一行:queue.enqueueMessage(msg, uptimeMillis)是把message加入到消息隊列裏面,具體的實現我抓不到了,知道這句話是把message加入到MessageQueue裏面就行。
下面分析* 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.");
        }
        **標註1**
        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.
       ........................
       ......................
       **標註2**
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ....................................
            ...................................
            try {
               **標註3**
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           ........................................
           ........................................
             **標註4**
            msg.recycleUnchecked();
        }
    }

看源碼的時候注意看一下標註,一個個地來說;

  • 標註1:保證了Handler、Looper和MessageQueue在同一線程
  • 標註2:沒有看錯,就是一個無線死循環,Looper會不停地對MessageQueue進行輪詢。這時候,你可能會問,這種無限死循環會不會很浪費資源?其實並不會,由於當沒有message被添加到隊列的時候,程序會進入阻塞狀態,對資源的消耗是很小的。
  • 標註3:還記得剛纔講地msg.target吧,這裏 msg.target.dispatchMessage(msg);就是handler.dispatchMessage(msg),直接去看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);
        }
    }

這裏面msg.callback是個Runnable對象,也就是當咱們使用handler.post(Runnable r)的方法的話,這時候就直接去調用Runnable對象裏面的東西了,若是使用的是handler.sendMessagexxx(),就是去調用咱們重寫的handleMessage(msg)方法了。
若是調用post()方法發送消息

mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        //這裏會被調用
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

若是調用sendMessagexxx()

mainHandler.sendEmptyMessage(0);
        mainHandler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                //這裏會被調用
                Log.e("qige_test","what="+msg.what);
            }
        };

咱們再回到上一級源碼

  • 標註4, msg.recycleUnchecked();其實這裏就是msg完成了它的使命,被釋放了之後又放回緩存池裏面去了。

以上基本就是一個完整的Handler消息機制的過程,我再帶你們好好回顧一下:

1.Looper.prepare();//這一步建立了Looper和MessageQueue
2.handler.sendMessagexxxx(); // 把message添加到MessageQueue上
3.Looper.loop();//輪詢消息
4.handler.dispatchMessage(msg);//處理消息

關於在主線程建立Handler的疑問

好了,到了該解決歷史遺留問題的時候了,爲何咱們在主線程建立handler不須要調用Looper.prepare()Looper.loop()呢?
這是由於,主線程已經給咱們建立好了,在哪裏建立好的呢?
在Java裏面,有一個程序的主入口,就是靜態main方法

public class Test {
    //程序入口
     public static void main(String... args){

     }

在Android裏面呢,一樣有這麼一個main方法入口,它在ActivityThread類中。在咱們App啓動過程當中,會調用到ActivityTread的main()方法(因爲篇幅問題,該過程沒有辦法詳細講,後期會推文章單獨說一下),下面咱們直接看ActivityTread的main()方法的源碼
爲了方便理解,仍是隻看關鍵部分代碼

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

        //建立Looper和MessageQueue對象,用於處理主線程的消息
        Looper.prepareMainLooper();

        //建立ActivityThread對象
        ActivityThread thread = new ActivityThread(); 

        //創建Binder通道 (建立新線程)
        thread.attach(false);

        //消息輪詢
        Looper.loop();

    }

看到這裏,我想你應該明白了爲啥在主線程裏建立Handler不須要調用Looper.prepare()和Looper.loop()方法了吧,由於在App啓動的時候,ActivityThread已經給咱們建立好了。不過,須要注意的是,我剛纔在講Looper.loop()源碼的時候說過這裏面會有一個無限死循環,那麼程序運行到這裏豈不是要永遠卡在這了呢,那咱們的Activity、Service都是咋執行的呢? 看關鍵的源碼這一句thread.attach(false),註釋上其實解釋的很清楚,經過Binder建立了新的線程,在這個新的線程裏面運行了Activity、Service等組件,而之因此Android爲何採用這種方式,讓咱們留在之後再說,你只須要知道,Looper.loop()不會形成卡頓,主線程已經給咱們建立好了Looper和MessageQueue對象就能夠了。

爲何寫這篇文章

開篇我就說過,Handler幾乎全部人都會用,但僅僅會用是不夠,要知其然更知其因此然。不少面試官願意問Handler相關的問題,好好閱讀這篇文章,它會讓你在面試的時候事半功倍。

有些東西你不只要懂,並且要可以很好地表達出來,可以讓面試官承認你的理解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它只活在面試當中,實際工做當中你壓根不會用到它,可是你要知道它是什麼東西。

最後文末我爲你們準備了一套精品Android架構師教程,保證你學了之後保證薪資上升一個臺階。(如下是一小部分,獲取更多其餘精講進階架構視頻資料能夠加我wx:X1524478394 免費獲取

一下是今天給你們分享的一些獨家乾貨:

①Android開發核心知識點筆記

掌握關於Handler的這些基本問題,讓你的面試事半功倍!

②對標「阿里 P7」 40W+年薪企業資深架構師成長學習路線圖

掌握關於Handler的這些基本問題,讓你的面試事半功倍!

③面試精品集錦彙總

掌握關於Handler的這些基本問題,讓你的面試事半功倍!

④全套體系化高級架構視頻

Android精講視頻領取學習後更加是如虎添翼!進軍BATJ大廠等(備戰)!如今都說互聯網寒冬,其實無非就是你上錯了車,且穿的少(技能),要是你上對車,自身技術能力夠強,公司換掉的代價大,怎麼可能會被裁掉,都是淘汰末端的業務Curd而已!現現在市場上初級程序員氾濫,這套教程針對Android開發工程師1-6年的人員、正處於瓶頸期,想要年後突破本身漲薪的,進階Android中高級、架構師對你更是如魚得水,趕快領取吧!

上述【高清技術腦圖】以及【配套的架構技術PDF】能夠 加我wx:X1524478394 免費獲取

相關文章
相關標籤/搜索