Handler相關面試題你答對多少?怎樣清晰表達拿下面試官?

yilian發表於2020-03-05

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。還可以看看Looperg構造方法的原始碼

  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學習PDF大全、Android進階之光、高階Android開發強化實戰、深入探索Android熱修復技術原理,還有演算法題的朋友,可以直接私信我【核心】

這些都是我閒暇還會反覆翻閱的精品資料!

Android學習PDF大全

這份Android學習PDF大全真的包含了方方面面了,內含Java基礎知識點、Android基礎、Android進階延伸、演算法合集等等

Android進階之光

1章  Android新特性

.第 2章  Material Design

3章  View體系與自定義 View

4章 多執行緒程式設計

5章 網路程式設計與網路框架

6章 設計模式

7章 事件匯流排

8章 函式響應式程式設計

9章 註解與依賴注入框架

10章 應用架構設計

11章 系統架構與 MediaPlayer框架

高階Android開發強化實戰

1.進階基礎

2高階控制元件

3.專案架構

4.晌應式程式設計

5.炫酷功能

6.精美動畫

7.Katlin SVG

8.測試與最佳化

深入探索Android熱修復技術原理

介紹了 Android 熱修復的核 技術原理 結合 ophix 熱修復開發實踐過程,

從程式碼修復、資源修復、 so 庫修復 大方向進行了詳細的技術剖析與解讀,業內少有的深度講解 Android 系統熱修復技術的書籍,對於原理、程式碼講解得非常清晰和深入,值得我們 Android工程師研讀。

我的這份學習合集,可以有效的幫助大家掌握知識點。

總之也是在這裡幫助大家學習提升進階,也節省大家在網上搜尋資料的時間來學習,也可以分享給身邊好友一起學習

獲取方式:轉發+關注,私信我【核心】即可或者直接 

面試:如果不準備充分的面試,完全是浪費時間,更是對自己的不負責!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2678752/,如需轉載,請註明出處,否則將追究法律責任。

相關文章