Android Handler相關面試題你能答對多少?子線程和主線程是如何切換的?

Handler機制是面試官非常喜歡問的知識點,源碼我看完幾遍,還是會覺得不清晰,裏面的代碼非常繞。後來我決定放棄探究細節,先把相關的類和調用的方法畫一個草圖,然後理清互相調用的關係,再結合關於Handler的高頻面試題,去尋找答案,這樣一輪下來,會對Handler有更深的認識。現在把這個過程,面試題尋找的答案以及相關的解釋整理成這篇文章,如有不對,歡迎大家予以指正。

高頻面試題

1.獲取Message實例的方式有哪些?哪一種更好?

獲取Message實例的方法主要有兩種,一種是直接創建,Message msg = new Message。另一種是通過Message.obtain()或者Handler.obtatinMessage()來得到一個Message對象。更推薦使用後一種方式,這種方式得到的對象是從對象回收池中得到,複用已經處理完的Message對象,而不是重新生成一個新對象。Message.obtain()Handler.obtatinMessage()最終都是調用了Message類的obtain()方法,查看方法內容,就知道是從對象回收池裏得到Message。

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

2.當Activity有多個Handler的時候,Message消息是否會混亂?怎麼樣區分當前消息由哪個Handler處理?

不會混亂,哪個Handler發送的消息,到時候也是這個handler處理。在發送消息的時候,會綁定target,這個target就是Handler本身,當需要handler調用dispatchMessage(msg)處理消息的時候,這個Handler就是發送消息時綁定的handler。
無論用哪一種方法發送消息,最終都會調用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)來發送消息

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

這裏的this,就是當前的handler。在來看需要Handler處理消息的時候,取的是哪一個handler,下面貼出主要源碼。

 public static void loop() {
  ......
        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
         ......
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

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

            msg.recycleUnchecked();
        }
    }

這是循環消息時的部分代碼,處理消息代碼是msg.target.dispatchMessage(msg);,這裏的target就是當時發送消息的handler。

3.在子線程發送消息,卻能夠在主線程接收消息,主線程和子線程是怎麼樣切換的?

子線程用handler發送消息,發送的消息被送到與主線程相關聯的MessageQueue,也是主線程相關聯的Looper在循環消息,handler所關聯的是主線程的Looper和MessageQueue,所以最後消息的處理邏輯也是在主線程。只有發送消息是在子線程,其它都是在主線程,Handler與哪個線程的Looper相關聯,消息處理邏輯就在與之相關的線程中執行,相應的消息的走向也就在相關聯的MessageQueue中。所以子線程切換到主線程是很自然的過程,並沒有想象中的複雜。

4.能不能在子線程中創建Handler?

可以,但是在創建前先調用prepare()方法創建Looper。Handler創建的時候,會去檢查是否有創建Looper,如果沒有創建就會拋出異常。相關源碼如下:

 public Handler(Callback callback, boolean async) {
     ......
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

所以我們在子線程需要先調用prepare()方法創建Looper。這裏還多提一點,在主線程創建就不需要自己創建Looper,因爲在ActivityTread類裏面,已經爲我們創建好了,相關源碼如下:

 public static void main(String[] args) {
     ......
        Looper.prepareMainLooper();// 爲主線程創建looper

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

Looper.prepareMainLooper();這句代碼就是爲主線程創建Looper。 所以在主線程中直接創建一個Handler,就直接可以循環消息,因爲安卓的主線程已經爲我們準備好了Looper。

5.一個線程可以有幾個Handler?幾個Looper?

一個線程可以有多個Handler,但是隻有一個Looper。創建Handler之前,需要創建Looper,否則會報錯。源碼裏面已經做了說明。

 public Handler(Callback callback, boolean async) {
   ......
        mLooper = Looper.myLooper();
        if (mLooper == null) {//判斷Looper是否被創建
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

再來看Looper的創建,是在prepare()方法裏。

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 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是否存在,存在就會拋出Only one Looper may be created per thread異常,這是在告訴我們一個線程只能有一個Looper。而TreadLocal的作用就是線程間隔離,確保一個線程對應一個Looper。還可以看看Looper構造方法的源碼

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

MessageQueue的初始化是在Looper的構造方法裏。不管一個線程有多少個Handler,相關聯的都是同一個Looper和MessageQueue。

關於Handler,可以問的問題有很多,以上只是抽出一些我認爲比較重要的問題。在尋找答案以後,我將Handler機制的整個過程在腦海中過了一遍,並且畫了個草圖。

Handler機制中重要類的相互關聯圖

Handler機制原理涉及幾個重要的類:Handler、Message、MessageQueue、Looper。
就用子線程向主線程發送消息來說明整個過程。
首先在主線程創建一個Handler,在Handler類裏面會創建Looper以及MessageQueue的對象,並且在Handler構造方法裏面賦值

 final Looper mLooper;
 final MessageQueue mQueue;
 public Handler(Callback callback, boolean async) {
 ......
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

mLooper = Looper.myLooper();得到的Looper是主線程的Looper
mQueue = mLooper.mQueue;得到的MessageQueue就是在Looper構造方法裏面創建的MessageQueue。
創建好了Handler實例,我們就會在子線程調用handler.sendMessage(msg);發送消息,將message放到MessageQueue裏面。在enqueueMessage()裏面就給每個message設置target,這個target就是當前的handler。

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

再然後調用Looper.loop();(在ActivityThread的main()裏面已經幫我們寫好)開始循環消息,拿到消息以後就會用handler取出消息進行處理,重點代碼是msg.target.dispatchMessage(msg);,這裏的handler就和一開始我們爲message設置的Handler對應。

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

最後我們在Handler的handleMessage(msg)裏面去處理我們的消息。
這就是子線程給主線程發送消息的整個過程,源碼很多,我只是截取了部分重點。這裏多提一點就是線程處理消息。 在線程中處理消息需要做三件事情

1. 先創建一個Looper (Looper.prepare()) 2. 再創建Handler,默認是和當前線程的Looper關聯起來 3. 循環消息(Looper.loop()

這三個步驟的順序不能調換。因爲主線程已經幫我們創建了Looper,所以我們不需要寫,如果是在子線程創建Looper就需要了。

Handler機制的理解要靠自己去琢磨,不斷的看源碼,去理解,理清它們之間的互相調用。只有比較深入的理解了Handler,才能在面試中回答面試官的問題,靠死記硬背是不可取的。多花時間去看,多動腦,不偷懶,總能把Handler拿下。

最後

文章到這裏就結束了,有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的理解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它只活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。

最後這裏是關於我自己的Android 學習,面試文檔,視頻收集大整理,有興趣的夥伴們可以看看~

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