<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                [TOC] ## 什么是 Handler? Handler 是 Android 的一種消息處理機制,與 Looper,MessageQueue 綁定,可以用來進行線程的切換。常用于接收子線程發送的數據并在主線程中更新 UI ## Handler線程通信的原理 > 你剛說 Handler 可以切換線程,它是怎么實現的? “切換線程”其實是“線程通信”的一種。為了保證主線程不被阻塞,我們常常需要在子線程執行一些耗時任務,執行完畢后通知主線程作出相應的反應,這個過程就是線程間通信。 Linux 里有一種進程間通信的方式叫消息隊列,簡單來說當兩個進程想要通信時,一個進程將消息放入隊列中,另一個進程從這個隊列中讀取消息,從而實現兩個進程的通信。 ![](https://img.kancloud.cn/3e/74/3e74c814cf7f372c13e568036bd90bf7_463x346.png) Handler 就是基于這一設計而實現的。在 Android 的多線程中,每個線程都有一個自己的消息隊列,線程可以開啟一個死循環不斷地從隊列中讀取消息。 當 B 線程要和 A 線程通信時,只需要往 A 的消息隊列中發送消息,A 的事件循環就會讀取這一消息從而實現線程間通信 ![](https://img.kancloud.cn/2e/62/2e62c2579d6aaf9dfd52c5054af7aaee_628x448.png) ### 事件循環和消息隊列的實現(Looper MessageQueue) Android 的事件循環和消息隊列是通過 Looper 類來實現的 Looper.prepare() 是一個靜態方法。它會構建出一個 Looper,同時創建一個 MessageQueue 作為 Looper 的成員變量。MessageQueue 是存放消息的隊列 當調用 Looper.loop() 方法時,會在線程內部開啟一個死循環,不斷地從 MessageQueue 中讀取消息,這就是事件循環 每個 Handler 都與一個 Looper 綁定,Looper 包含 MessageQueue ![](https://img.kancloud.cn/ee/46/ee46cdf633f39c882e4cbf16d6d74963_328x283.png) ### Looper 被存放在ThreadLocal Looper 是存放在線程中的。但如何把 Looper 存放在線程中就引入了 Android 消息機制的另一個重點 --- **ThreadLocal** 前面我們提到。Looper.prepare() 方法會創建出一個 Looper,它其實還做了一件事,就是將 Looper 放入線程的局部變量 ThreadLocal 中。 ~~~ // Looper.java#private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // sThreadLocal是一個靜態對象,類型是ThreadLocal<Looper> sThreadLocal.set(new Looper(quitAllowed)); } ~~~ ### 什么是 ThreadLocal ThreadLocal 又稱線程的局部變量。它最大的神奇之處在于,**一個 ThreadLocal 實例在不同的線程中調用 get 方法可以取出不同的值。** 用一個例子來表示這種用法: ~~~ public void set(T value) { // ① 獲取當前線程對象 Thread t = Thread.currentThread(); // ② 獲取線程的成員屬性map ThreadLocalMap map = getMap(t); // ③ 將value放入map中,如果map為空則創建map if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private Entry[] table; ~~~ **ThreadLocal.set 可以將一個實例變成線程的成員變量** 因為 Looper 要放在線程中的,每個線程只需要一個事件循環,只需要一個 Looper。事件循環是個死循環,多余的事件循環毫無意義。ThreadLocal.set 可以將 Looper 設置為線程的成員變量 同時為了方便在不同線程中獲取到 Looper,Android 提供了一個靜態對象 Looper.sThreadLocal。這樣在線程內部調用 sThreadLocal.get 就可以獲取線程對應的 Looper 對象 綜上所述,使用 ThreadLocal 作為 Looper 的設置和獲取工具是十分方便合理噠 ## Looper 的這個死循環會一直“空轉” 當然不會!如果事件循環中沒有消息要處理但仍然執行循環,相當于無意義的浪費 CPU 資源!Android 是不允許這樣的 為了解決這個問題,在 MessageQueue 中,有兩個 native 方法,`nativePollOnce` 和 `nativeWake`。 nativePollOnce 表示進行一次輪詢,來查找是否有可以處理的消息,如果沒有就阻塞線程,讓出 CPU 資源 nativeWake 表示喚醒線程 所以這兩個方法的調用時機也就顯而易見了 ~~~ // MessageQueue.java boolean enqueueMessage(Message msg, long when) { ··· if (needWake) { nativeWake(mPtr); } ··· } ~~~ 在 MessageQueue 類中,`enqueueMessage` 方法用來將消息入隊,如果此時線程是阻塞的,調用 `nativeWake` 喚醒線程 ~~~ // MessageQueue.java Message next() { ··· nativePollOnce(ptr, nextPollTimeoutMillis); ··· } 復制代碼 ~~~ `next()` 方法用來取出消息。取之前調用 `nativePollOnce()` 查詢是否有可以處理的消息,如果沒有則阻塞線程。等待消息入隊時喚醒。 ### nativePollOnce 與 nativeWake 在linux新的內核中使用了epoll來替換它,相比于select,epoll最大的好處在于它不會隨著監聽文件描述符數目的增長而效率降低,select機制是采用輪詢來處理的,輪詢的fd數目越多,效率也就越低。epoll的接口非常簡單就只有三個函數: int epoll_create(int size);創建一個epoll句柄,當這個句柄創建完成之后,在/proc/進程id/fd中可以看到這個fd。 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);注冊事件函數。 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的發生,參數timeout是超時時間毫秒值,0會立即返回,-1將不確定,也就是說有可能永久阻塞。該函數返回需要處理的事件數目,如返回0表示已超時。 ### Looper 是個死循環,為什么不會導致 ANR 首先要明確一下概念。**ANR 是應用在特定時間內無法響應一個事件時拋出的異常。** 典型例子的是在主線程中執行耗時任務。當一個觸摸事件來臨時,主線程忙于處理耗時任務而無法在 5s 內響應觸摸事件,此時就會拋出 ANR。 但 Looper 死循環是事件循環的基石,本身就是 Android 用來處理一個個事件的。正常情況下,觸摸事件會加入到這個循環中被處理。但如果前一個事件太過耗時,下一個事件等待時間太長超出特定時間,這時才會產生 ANR。所以 Looper 死循環并不是產生 ANR 的原因。 ## 消息隊列中的消息是如何進行排序 這個就要看 MessageQueue 的 enqueueMessage 方法了 enqueueMessage 是消息的入隊方法。Handler 在進行線程間通信時,會調用 sendMessage 將消息發送到接收消息的線程的消息隊列中,消息隊列調用 enqueueMessage 將消息入隊。 ~~~ // MessageQueue.java boolean enqueueMessage(Message msg, long when) { synchronized (this) { // ① when是消息入隊的時間 msg.when = when; // ② mMessages是鏈表的頭指針,p是哨兵指針 Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; // ③ 遍歷鏈表,比較when找到插入位置 if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } // ④ 將msg插入到鏈表中 msg.next = p; prev.next = msg; } if (needWake) { nativeWake(mPtr); } } return true; } ~~~ 消息入隊分為 3 步: ① 將入隊的時間綁定在 when 屬性上 ② 遍歷鏈表,通過比較 when 找到插入位置 ③ 將 msg 插入到鏈表中 這就是消息的排序方式 ## 異步消息和同步屏障 在 Android 的消息機制中,消息分為**同步消息**、**異步消息**和**同步屏障**三種。(沒錯,同步屏障是 target 屬性為 null 的特殊消息)。通常我們調用 sendMessage 方法發送的是同步消息。異步消息需要和同步屏障配合使用,來提升消息的優先級。 同步屏障理解起來其實很簡單。剛才說同步屏障是一種特殊的消息,當事件循環檢測到同步屏障時,之后的行為不再像之前那樣根據 when 的值一個個取消息,而是遍歷整個消息隊列,查找到異步消息取出并執行。 這個特殊的消息在消息隊列中像一個標志,事件循環探測到它時就改變原來的行為,轉而去查找異步消息。表現上看起來像一個屏障一樣攔住了同步消息。所以形象地稱為同步屏障。 源碼實現非常非常簡單: ~~~ //MessageQueue.java Message next() { ··· // ① target為null表明是同步屏障 if (msg != null && msg.target == null) { // ② 取出異步消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } ··· } ~~~ ### 同步屏障,不移除,會發生什么事呢? 同步屏障是用來“攔住”同步消息,處理異步消息的。如果同步屏障不移除,消息隊列里的異步消息會一個一個被取出處理,直到異步消息被取完。如果此時隊列中沒有異步消息了,則線程會阻塞,隊列中的同步消息永遠不會執行。所以同步屏障要及時移除。 ### 那你知道同步屏障的應用場景 同步屏障的核心作用是提高消息優先級,保證 Message 被優先處理。Android 為了避免卡頓,應用在了 view 繪制中。具體可以看之前關于 view 繪制的總結~ ## Handler相關的內存泄漏問題 內存泄漏歸根到底其實是生命周期“錯位”導致的:**一個對象本來應該在一個短的生命周期中被回收,結果被一個長生命周期的對象引用,導致無法回收。** Handler 的內存泄漏其實是內部類持有外部類引用導致的。 形成方式有兩種: (1)匿名內部類持有外部類引用 ~~~ class Activity { var a = 10 fun postRunnable() { Handler(Looper.getMainLooper()).post(object : Runnable { override fun run() { this@Activity.a = 20 } }) } } ~~~ Handler 在發送消息時,message.target 屬性就是 handler 本身。message 被發送到消息隊列中,被線程持有,線程是一個無比“長”生命周期的對象,導致 activity 無法被及時回收從而引起內存泄漏。 解決辦法是在 activity destory 時及時移除 runnable (2)非靜態內部類持有外部類引用 ~~~ //非靜態內部類 protected class AppHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { } } } ~~~ 解決方案是用靜態內部類,并將外部引用改為弱引用 ~~~ private static class AppHandler extends Handler { //弱引用,在垃圾回收時,被回收 WeakReference<Activity> activity; AppHandler(Activity activity){ this.activity = new WeakReference<Activity>(activity); } public void handleMessage(Message message){ switch (message.what){ } } } ~~~ ## HandlerThread HandlerThread 顧名思義就是 Handler+Thread 的結合體,它本質上是一個 Thread。 我們知道,子線程是需要我們通過 Looper.prepare()和 Looper.loop()手動開啟事件循環的。HandlerThread 其實就幫我們做了這件事,它是一個實現了事件循環的線程。我們可以在這個線程中做一些 IO 耗時操作。 ## IdleHandler IdleHandler 雖然叫 Handler,其實和同步屏障一樣是一種特殊的”消息"。不同于 Message,它是一個接口 ~~~ public static interface IdleHandler{ boolean queueIdle(); } 復制代碼 ~~~ Idle 是空閑的意思。與同步屏障不同,同步屏障是提高異步消息的優先級使其優先執行,IdleHandler 是事件循環出現空閑的時候來執行。 這里的“空閑”主要指兩種情況 (1)消息隊列為空 (2)消息隊列不為空但全部是延時消息,也就是 msg.when > now 利用這一特性,我們可以將一些不重要的初始化操作放在 IdleHandler 中執行,以此加快 app 啟動速度;由于 View 的繪制是事件驅動的,我們也可以在主線程的事件循環中添加一個 IdleHandler 來作為 View 繪制完成的回調,等等。 但應該注意的是,如果主線程中一直有任務執行,IdleHandler 被執行的時機會無限延后,使用的時候要注意哦~ ## 參考資料 [【面試官爸爸】繼續給我講講Handler?](https://juejin.cn/post/6995341995015143432)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看