【Android源碼】Handler 機制源碼分析

為什么要使用Handler

  1. 因為在Android中訪問UI只能在主線程中進行,如果在子線程中運行,則程序會拋出異常。

    // ViewRootImpl.java
    void checkThread() {
       if (mThread != Thread.currentThread()) {
           throw new CalledFromWrongThreadException(
                   "Only the original thread that created a view hierarchy can touch its views.");
       }
    }
    
  2. 為什么不允許在子線程中訪問UI?

    因為Android的UI并不是線程安全的,如果在多線程中執行UI的操作,那么UI的狀態是不可控的,這個時候就會出現各種問題。
    那么最好的辦法就是只能在一個線程中執行UI的操作。
    而Android主線程中又不能執行耗時操作,因為那樣就會導致程序的ANR。
    所以Android提供了Handler這樣的機制,用來在子線程中執行耗時操作之后,發送消息給主線程,主線程再執行UI的更新操作。

Handler機制原理分析

這里我們以Android UI線程的Handler來分析。

Handler的創建

Android的應用入口是ActivityThread.main()函數,UI線程的Handler就是在這個函數中創建的。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    thread.attach(false);
    ActivityThread thread = new ActivityThread();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
}

當main函數執行之后,應用就啟動了,從這個時候開始,Looper就會一直從消息隊列中取出消息并處理。而應用會通過Handler來不斷的添加消息給消息隊列。通過不斷的添加消息和取出消息,整個消息機制就被運轉起來了。

Looper

從上面可以看到在Handler創建前后,通過Looper的prepareloop方法,創建了Looper對象和開啟消息循環。

  1. 構造函數

    private Looper(boolean quitAllowed) {
       mQueue = new MessageQueue(quitAllowed);
       mThread = Thread.currentThread();
    }
    
    1. 創建了MessageQueue對象,這個對象是消息隊列,主要用來存放我們的消息,會在下面具體分析。
    2. 獲取當前的線程并保存起來
  2. 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));
    }
    
    1. 判斷ThreadLocal中在當前線程是否已經存在了Looper對象,如果存在,則拋出異常,所以可以得出在同一個線程中只能存在一個Looper對象。
    2. 在ThreadLocal存入當前線程的Looper對象。
    3. ThreadLocal對象是一個線程內部的數據存儲類,通過它可以在指定的線程存儲數據,當存儲數據之后,就只能在指定的線程中獲取到存儲的數據,其他線程是不能獲取到數據的。
  3. loop

    public static void loop() {
       final Looper me = myLooper();
       // 獲取消息隊列
       final MessageQueue queue = me.mQueue;
        // 死循環來輪詢的從消息隊列中獲取消息
       for (;;) {
           Message msg = queue.next(); // might block
           if (msg == null) {
               return;
           }
           
           try {
               msg.target.dispatchMessage(msg);
           } finally {
               if (traceTag != 0) {
                   Trace.traceEnd(traceTag);
               }
           }
    
           msg.recycleUnchecked();
       }
    }
    
    1. loop函數會不斷的從消息隊列中取出消息,當queue.next()為空的時候就直接阻塞住
    2. msg不為空,調用msg.target.dispatchMessage(msg)處理消息,而msg.target其實就是Handler對象,取出消息之后就交給Handler來處理發送的消息了。
  4. quit

    public void quit() {
       mQueue.quit(false);
    }
    public void quitSafely() {
        mQueue.quit(true);
    }
    

    調用MessageQueue的quit方法來退出Looper。其中上面的是直接退出,下面的是安全的退出,也就是只標記一下,當消息隊列中的消息全部執行完成之后安全的退出。

    當我們在子線程中創建Looper和Handler的時候,如果我們不調用quit方法的話,子線程就會一直處于等待狀態,所以我們需要調用quit方法退出Looper。

MessageQueue

在Looper中大量的使用到了MessageQueue,而MessageQueue主要就是兩個操作插入消息和讀取消息并刪除消息,我們來一起分析下:

  1. 構造函數

    MessageQueue(boolean quitAllowed) {
       mQuitAllowed = quitAllowed;
       mPtr = nativeInit();
    }
    

    在構造函數中,調用了nativeInit方法在Native層創建了NativeMessageQueue和Looper,并將Looper設置給當前的線程。這個時候Java層和Native層都有了MessageQueue和Looper。
    在Native層的Looper中,創建了一個管道,本質上就是一個文件,一個管道中有讀和寫兩個文件操作符,通過讀和寫來將消息寫入和讀取。

  2. enqueueMessage 插入消息

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
           msg.markInUse();
           msg.when = when;
           Message p = mMessages;
           boolean needWake;
           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 {
               needWake = mBlocked && p.target == null && msg.isAsynchronous();
               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;
           }
    
           // We can assume mPtr != 0 because mQuitting is false.
           if (needWake) {
               nativeWake(mPtr);
           }
       }
       return true;
    }
    

    enqueueMessage其實就是構建好msg,并插入到單鏈表中。

  3. next 讀取消息并刪除消息

    Message next() {
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
        }
    }
    

    next方法是一個無限循環方法,當消息隊列中沒有消息的時候,next方法會被阻塞在這里,當有新的消息的時候,next方法會最終返回這條消息并從單鏈表中移除。

Handler

  1. 構造函數

    public Handler(Callback callback, boolean async) {
    
       mLooper = Looper.myLooper();
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread that has not called Looper.prepare()");
       }
       mQueue = mLooper.mQueue;
       mCallback = callback;
       mAsynchronous = async;
    }
    
    1. 首先獲取Looper對象,如果Looper對象為空,則拋出異常,所以當我們在子線程中創建Handler的時候,首先要通過Looper.prepare()來創建子線程的Looper對象。
    2. 通過Looper獲取到MessageQueue
  2. 發送消息

    Handler的發送消息主要是post的方法和send的方法,而他們最終調用的都是:

    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);
    }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    Handler主要就是向消息隊列中插入了一條消息,這個時候MessageQueue就會調用插入的方法來插入消息。

  3. 讀取消息

    當Handler發送消息之后,MessageQueue就會通過next方法讀取出這個消息,那么Looper的loop就不會被阻塞住,取出消息之后就調用了msg.target.dispatchMessage(msg)方法,最終交給Handler的dispatchMessage來執行。

    /**
    * 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);
       }
    }
    
    1. 檢查Message的callback是否為空,不為空則通過handleCallback來處理,而callback就是通過post傳遞過來的Runnable對象。直接調用run方法執行。

      private static void handleCallback(Message message) {
          message.callback.run();
      }
      
    2. 檢查mCallback是否為空,不為空則調用mCallback.handleMessage方法處理消息。而mCallback就是我們在創建Handler的時候傳遞過去的callback。

      public interface Callback {
          public boolean handleMessage(Message msg);
      }
      public Handler(Callback callback) {
          this(callback, false);
      }
      
    3. 調用handleMessage(msg)處理消息。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容