kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- title: 'Android Handler 消息機制' disqus: kyleAlien --- Android Handler 消息機制 === ## OverView of Content 如有引用參考請詳註出處,感謝 :smile: Android 是以消息推動系統的機制,所以我們要好好了解 Handler 機制,以下會以使用 Handler 分析源碼 來下手;這篇是分析 Java 層的消息機制 [TOC] ## Android Handler 概述 **由於 Android 是 ++消息驅動++ 的系統**,所以要先來介紹 Handler,Handler 機制主要有分為 4 個組成 | 類 | 說明功能 | | -------- | -------- | | [**Handler**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/Handler.java) | 使用者接收、傳送事件的句秉 | | [**Looper**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/Looper.java) | 循環檢查是否有要傳輸的事件,從 MessageQueue 中獲取事件,並交給 Handler 處理(假如列隊為空就進入休眠) | | [**MessageQueue**](https://android.googlesource.com/platform/frameworks/base/+/0e2d281/core/java/android/os/MessageQueue.java) | 儲存需要做的事情,**一般來說止允許保存相同類型的 Object**、Message 除存的數據結構為 [**佇列**](https://hackmd.io/@AlienHackMd/rkkpuEhzU) | | [**Message**](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/Message.java) | 需要做的事件、要傳遞的訊息 | * 其實一開始會很難發現 Handler & Thread 之間的關係,我們可以先看看這幾個結論在看看程式如何實現它 1. 一個 Thread 對應一個 Looper 2. 一個 Looper 對應一個 MessageQueue 3. 一個 MessageQueue 內有多個 Message 4. **每個 Message 中最多對應一個 Handler 處理事件** > **==Thread & Handler 是一對多的關係==** ### [Handler](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/Handler.java) * 應用開發時常使用到 Handler 類,它主要有兩個做用 1. 接收 **處理** Message 2. 將 Message **存入** MessageQueue 中 > ![](https://i.imgur.com/mxVI5lh.png) ```java= // Handler.java public class Handler { ... final Looper mLooper; final MessageQueue mQueue; final Callback mCallback; ... public interface Callback { boolean handleMessage(@NonNull Message msg); } // 通常用來給使用者 Override 處理事件 public void handleMessage(@NonNull Message msg) { } // 分發事件,有三種方式 public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } } ``` * 在上面分發事件有三種方法,並且 **這三種分發方式有 ^1^ 先後順序**,只要前面被處理,就不會使用後面的方式處理 1. Message 本身的 callback 2. 實現 Callback 接口 3. Override `Handler#handleMessage` Function 這種方式就會變成了一種循環 > ![](https://i.imgur.com/fDza2tW.png) * 透過 Handler 把訊息壓到 MessageQueue,**方式有 Post & Send 兩種系列,並且可以 ^2^ 控制發送的時間**,以下寫幾個常用的 1. Post 系列:把零散訊息轉為 Message 再用 Send 傳出 | 函數 | 功能 | | -------- | -------- | | boolean post(Runnable r) | 直接發出訊息 | | boolean postAtTime(Runnable, long updatetimeMillis) | 在固定(規定)時間再發出該條訊息 | 2. Send 系列:參數直接是 Message | 函數 | 功能 | | -------- | -------- | | boolean sendEmptyMessage(int what) | 直接發送訊息 | | boolean sendMessageArFrontOfQueue(Message msg) | 把消息發送到訊息隊列最前面 | | boolean sendMessageAtTime(int what, long updatetimeMillis) | 在固定(規定)時間再發出該條訊息 | | boolean sendMessageDelayed(Message msg, long delayMillis) | 延遲幾毫秒後發送該訊息 | * Post 實現方式如下 ```java= // Handler.java public class Handler { public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } // 包裝 callback 再傳出 private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); // 取得 Message 資源,避免不斷創建,可以復用資源 m.callback = r; // 直接設定 Runnable 回調函數 return m; } // 最後透過計算 當前時間 + 延長實現,之後就呼叫 sendMessageAtTime 函數 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } // 在規定時間發送消息 public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { // 若是該 Thread 並沒有設定 MessageQueue 則會拋出錯誤 RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } } ``` ### 發送訊息方法 | 名稱 | 參數 : 回傳 | 解釋 | | -------- | -------- | -------- | | post | Runnable : boolean | 接收一個 Runnable 對象作為 Message | | postAtTime | Runnable, long : boolean | 接收一個 Runnable 對象作為 Message,並在等待的規定時間發送 Message | | sendEmptyMessage | int : boolean | 發送一個使用者規定的 int 訊息,在由使用者自己處理 | | sendMessageAtTime | Messge, int : boolean | 發送一個使用者規定的 int 訊息,在由使用者自己處理 | | sendMessageDelayed | Messge, long : boolean | 在等待的規定時間後 (延遲) 發送 Message | ### Post & Send * 從上面可以看出上面主要有 Post & Send * 而他們的差別在 **Send 內直接發送 Message 訊息** 在設定進 Message 內的屬性 what,**Post 則是包裝過後在調用 Send 的方法**,**其 ++本質是一樣的++** ```java= // Send ... public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } // Post ... public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } // 最終都會發至 sendMessageAtTime public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { // 1. RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } ``` 1. 一般來說每個 Thread 都要有一個 MessageQueue 才可以處理 Message ### 處理 Message * 可透過重載 Handler 中的 `dispatchMessage`、`handleMessage`這兩個函數作為 Handler 處理 Message > ![](https://i.imgur.com/JtqkS9L.png) * 從 dispatchMessage 可看出**它預設處理 Message 的順序** 1. msg.callback (Runnable) 2. mCallback (interface) 3. handleMessage (method) ```java= public void handleMessage(Message msg) { // 使用者覆寫 } /** * Handle system messages here. */ public void dispatchMessage(Message msg) { // callback 優先及最高 if (msg.callback != null) { // msg.callback is Runnable handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } ``` ### runWithScissors 傳同步訊息 * 舉例:以下是 WMS 在初始化時就使用 ([**DisplayThread**](https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/DisplayThread.java)) Handler#runWithScissors 這個函數來達到先執行 Runnable 任務的功能 (如果不先創建 WMS 則後面使用則會出問題) ```java= // DisplayThread.java public final class DisplayThread extends ServiceThread { // ServiceThread 會初始化 Loop private static DisplayThread sInstance; private static Handler sHandler; ... 省略部分 private static void ensureThreadLocked() { if (sInstance == null) { sInstance = new DisplayThread(); sInstance.start(); sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER); // 創建 Handler 對象 (其中的 Looper 是父類創建並初始化) sHandler = new Handler(sInstance.getLooper()); } } public static Handler getHandler() { synchronized (DisplayThread.class) { ensureThreadLocked(); return sHandler; } } } // ---------------------------------------------------------- // WindowManagerService.java private static WindowManagerService sInstance; static WindowManagerService getInstance() { return sInstance; } @VisibleForTesting public static WindowManagerService main(final Context context, final InputManagerService im, final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy, ActivityTaskManagerService atm, DisplayWindowSettingsProvider displayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory, Supplier<Surface> surfaceFactory, Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) { // getHandler 就是 Handler 對象 DisplayThread.getHandler().runWithScissors(() -> // 創建 WindowManagerService 對象 sInstance = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy, atm, displayWindowSettingsProvider, transactionFactory, surfaceFactory, surfaceControlFactory), 0); return sInstance; } ``` * 這裡的重點是 Handler 中的 **runWithScissors 函數:** ^1^ 比對 Looper 若是就是 Handler 中的 Thread,則直接執行,^2^ 創建 BlockingRunnable 對象 (主要分析 BlockingRunnable) ```java= // Handler.java public class Handler { ... 省略部分 final Looper mLooper; public final boolean runWithScissors(@NonNull Runnable r, long timeout) { // 判空 if (r == null) { throw new IllegalArgumentException("runnable must not be null"); } // 時間檢查 if (timeout < 0) { throw new IllegalArgumentException("timeout must be non-negative"); } // 若當前 Handler 使用的 Looper (mLooper) 就是 Looper#myLooper // 也就是比對 Thread (因為一個 Thread 只有一個 Looper) if (Looper.myLooper() == mLooper) { // 使用到當前 Thread r.run(); // 直接執行 return true; } // 創建堵塞 Runnable 對象 BlockingRunnable br = new BlockingRunnable(r); // 分析 postAndWait 方法 return br.postAndWait(this, timeout); } } ``` * BlockingRunnable 是 Handler 中的內部類 ```java= // Handler.java public class Handler { private static final class BlockingRunnable implements Runnable { private final Runnable mTask; private boolean mDone; public BlockingRunnable(Runnable task) { mTask = task; } @Override public void run() { try { mTask.run(); // 跑使用者的任務 } finally { synchronized (this) { mDone = true; notifyAll(); // 喚醒當前對象所有的鎖 } } } public boolean postAndWait(Handler handler, long timeout) { // 先丟入任務 Queue,判斷是否成功 if (!handler.post(this)) { return false; } synchronized (this) { if (timeout > 0) { // 計算到期時間 final long expirationTime = SystemClock.uptimeMillis() + timeout; while (!mDone) { long delay = expirationTime - SystemClock.uptimeMillis(); if (delay <= 0) { return false; // timeout } try { wait(delay); // wait 指定 delay 的時間後喚醒當前對象 } catch (InterruptedException ex) { } } } else { while (!mDone) { try { wait(); // wait 當前對象 } catch (InterruptedException ex) { } } } } return true; } } } ``` ## MessageQueue & Message * 如其名,它是一個**數據結構佇列 FIFO**,它透過 ==**本地方法 (Native) 內存指針創建 Queue**== ```java= private native static long nativeInit(); MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); } ``` ### Queue 方法 | 名稱 | 參數 : 回傳 | 解釋 | | -------- | -------- | -------- | | enqueueMessage | Message, long : boolean | 在指定時間內壓入佇列中 | | next | void : Message | 元素拉出佇列 | | removeMessages | Handler, int, Object : void | 移除特定元素 | | removeMessages | Handler, Runnable, Object : void | 移除特定元素 | ### [MessageQueue](https://android.googlesource.com/platform/frameworks/base/+/0e2d281/core/java/android/os/MessageQueue.java) * MessageQueue 因為是 Queue(數據結構) 所以有所謂的 FIFO,MessageQueue 有以下功能 1. 新建列隊 由建構函數 中的 **nativeInit** 方法組成 ```java= // /android/os/MessageQueue.java MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); } private native static int nativeInit(); ``` 2. 元素入隊列 **enqueueMessage** ```java= // /android/os/MessageQueue.java boolean enqueueMessage(Message msg, long when) { ... } ``` 3. 元素出隊列 **next** ```java= // /android/os/MessageQueue.java Message next() { ... } ``` 4. 刪除元素 ```java= // /android/os/MessageQueue.java void removeMessages(Handler h, int what, Object object) { ... } void removeMessages(Handler h, Runnable r, Object object) { ... } ``` 5. 銷毀列隊,透過本地函數 nativeDestory 銷毀一個 MessageQueue ```java= private native static void nativeDestroy(int ptr); ``` ### MessageQueue 創建 > 連接於 Looper 的建構函數,**==當 Looper 創建時 MessageQueue 就一同創建==** ```java= private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } ``` ### Looper 透過 MessageQueue 取得 Message * Looper 透過 MessageQueue#next 函數,就可以取得下一條要發送的訊息 :::warning * next 函數是一個無限循環的函數,所以取不到訊息可能會 Block ::: ```java= // MessageQueue.java Message next() { // 若已經被 depose mPtr 就會被設定為 0 final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration // 下一個訊息的時間 int nextPollTimeoutMillis = 0; // 進入無限循環,也就是說 next 會導致 block for (;;) { ... 省略部分 synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; // 上一個訊息 Message msg = mMessages; // 當前訊息 if (msg != null && msg.target == null) { // target 就是 Handler // 找到一個可發送的 Message do { prevMsg = msg; msg = msg.next; // 切換到下一個 Message } while (msg != null && !msg.isAsynchronous()); } // 再次判斷 Message if (msg != null) { // 判斷時間 if (now < msg.when) { // 該訊息的時間還沒到 // Next message is not ready. Set a timeout to wake up when it is ready. // 計算剩餘時間 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 時間到,並且有 Message mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; // 上一個 Message 就是當前 Message } else { mMessages = msg.next; //切換到下一個要用的 Message } // 斷開連結 !! 好讓 GC 偵測到後回收 !!! (可達性分析) msg.next = null; ... debug 訊息 msg.markInUse(); // 標記該 Message 已經被使用過 return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { // 檢查是否退出 dispose(); return null; } ... 省略 IdleHandler (下一小節會說明) } ... 省略部分 } ``` ### IdleHandler - 偵測閒置 * IdleHandler 是 MessageQueue 內的一個 interface,它可以在 MessageQueue 沒有消息時讓使用者知道,並決定如何處理 ```java= // MessageQueue.java public static interface IdleHandler { // 返回 true,會讓你設定的接口繼續存在 // 返回 false 則會移除你設定的接口 boolean queueIdle(); } ``` * 同樣分析 next 方法,可以看到 **一個 MessageQueue 最多設定 4 個 IdleHandler** ```java= // MessageQueue.java Message next() { // 若已經被 depose mPtr 就會被設定為 0 final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration // 下一個訊息的時間 int nextPollTimeoutMillis = 0; // 進入無限循環,也就是說 next 會導致 block for (;;) { ... 省略部分 synchronized (this) { ... 省略部分 // 檢查是否有設置 IdleHandler if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } // 最多設置 4 個 Idle Handler if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // 循環 for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { // 當沒有訊息時通知接口 keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } // 返回 false 就移除 if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } ... 省略部分 } } ``` ## Looper * 它是組成消息的重要關鍵 (Handler、Message、MessageQueue),**它的角色是==驅動==** | 名稱 | 參數 : 回傳 | 解釋 | | -------- | -------- | -------- | | myLooper | void : Looper | 以當前線程為主,取得當前 Thread 的 Looper | | getMainLooper | void : Looper | 取得主線程的 Looper | ### [Looper](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/os/Looper.java) - [ThreadLocal](https://android.googlesource.com/platform/libcore/+/1f07ea2/luni/src/main/java/java/lang/ThreadLocal.java) * 最後 Looper 則是讓整個 Hanler 機制循的動力,有 Looper 的推動 Handler 才能正常接收、發送資訊 > ![](https://i.imgur.com/WAQAwIa.png) * Looper 中包含了一個 MessageQueue(在建構函數中建構的) ```java= // ava/android/os/Looper.java public final class Looper { // 私有函數 private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } } ``` * 當你每創建一個線程就必須為這個線程準備 Looper,否則在 Handler 發送消息時就會 crush (上面有說到會檢查),創建主要有以下步驟 1. Looper 準備工作(prepare 函數) 2. 創建 Handler 發送 & 接收消息 3. Looper 開始運作(loop 函數) ```java= class LooperThread extends Thread { private Handler handler; public void run() { // 1 Looper.prepare(); // 2. handler = new Handler() { @Override public void handleMessage(Message msg) { //... 處理 Message } } // 3. Looper.loop(); } } ``` :::success * 不需要準備 Thread ? 其實 Thread 是也是有使用,不過它由 ThreadLocal 做管理,細節可以參考另外一篇 [**文章**](https://hackmd.io/7fBX6uEtQt6AzCpuBWTHMQ?view) ::: * ThreadLocal 這裡大概提及,ThreadLocal 也就是線程隔離,使用線程 Thread 作為 Key 儲存,而 Value 就是你要拷貝到每個線程的數據(每個子線程都持有一個數據) ```java= // /android/os/Looper.java public final class Looper { // 拷貝線程的數據是 Looper static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) // 一個線程只能 prepare 一次 throw new RuntimeException("Only one Looper may be created per thread"); } // 為當前 Thread 設定 Looper sThreadLocal.set(new Looper(quitAllowed)); } } ``` * 這裡要注意 ^1^ ThreadLocal 是泛型,^2^ 在 Looper 裡面就是一個靜態變數 * 當你建立一個 Handler 時,在 Handler construct 就會透過當前的線程取得 Looper ```java= // Handler public Handler(@Nullable 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; } // ----------------------------------------------------------------- // Looper public static @Nullable Looper myLooper() { // sThreadLocal 是靜態的,會透過當前 Thread 作為 Key 取得 Looper return sThreadLocal.get(); } ``` ### Looper & WorkThread > 當一個普通線程要使用 Handler 時 **必須要有 Looper 的預前準備工作** ```java= public class MyThread extends Thread { private Handler handler; @SuppressLint("HandlerLeak") @Override public void run() { Looper.prepare(); //"1. " handler = new Handler() { //"2. " @Override public void handleMessage(@NonNull Message msg) { } }; Looper.loop(); //"3. " } } ``` 1. Looper 預前準備 ```java= //"a. " static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { // 只要內部有就不會創建 Loop if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //"b. " sThreadLocal.set(new Looper(quitAllowed)); } //Looper Constract... private Looper(boolean quitAllowed) { //"c. " mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } ``` > **a.** 它有一個很重要的屬性 **static final ThreadLocal**,它是一個 **靜態變量**,意味著每個線程共享此屬性,但 **[ThreadLocal](https://hackmd.io/7Ru0TE45Tnm1LEUqx4qe-A#ThreadLocal-%E7%B7%9A%E7%A8%8B%E9%9A%94%E9%9B%A2) 是特殊的變量,==讓每一個 Thread 都有一個 Looper 對象==** > **b.** 可看出 **prepare 時它內部會自己創建一個 Looper 對象**,並且 Looper 的建構函數也是私有的 > **c.** Looper 被創建時就會**自己準備一個 MessageQueue** 2. 創建處理消息的 Handler 3. Looper 開始循環運作 ### Looper & Handler Looper 跟 Handler 的關係可以從 **new Handler() 這個==建構函數==** 中看出,它是**透過 myLooper 方法,透過目前的線程在 ThreadLocal 中 ++取得該當前線程所擁有的 Looper++** > 這也就是上面在準備時為何要調用 Looper.prepare() 這個方法,如果沒準備就不一定會有 Looper ```java= // Handler construct public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { ... 忽略 // 取得當前線程的 Loop 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; } //Looper... public static @Nullable Looper myLooper() { return sThreadLocal.get(); } ``` ### Looper & UI Thread * Android 中 Activity 的主線程是 ActivityThread's main() (frameworks/base/core/java/android/app/ActivityThread.java) ```java= public static void main(String[] args) { ... Looper.prepareMainLooper(); //"1. " ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { //"2. " sMainThreadHandler = thread.getHandler(); } ... // End of event ActivityThreadMain. ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } ``` 1. 主線程的 Looper 是不可以關閉的,並且使用類同步 ```java= public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } // 屬性... private static Looper sMainLooper; // guarded by Looper.class ``` > sMainLooper = myLooper(); **把目前的 Thread 作為內部靜態類維護,並可以透過,getMainLooper ==隨時==取得** > ```java= public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } } ``` * **這樣主線程可以隨時訪問其他 Looper (透過 myLooper() 方法)** 2. 一般線程必須自己建造 Handler 處理訊息,不過其實 **ActivityThread 內部已經有自己的 Handler 實現** (並且不能 Override) ```java= // ActivityThread.java final Handler getHandler() { return mH; } // 屬性... final H mH = new H(); // Handler 實現 private class H extends Handler { ... } ``` 3. Looper#loop,首先取得目前線程所擁有的 Looper,再取出 Looper 內部有的 MessageQueue,最後無限循環整個消息對列 ```java= // Looper.java public static void loop() { final Looper me = myLooper(); ... final MessageQueue queue = me.mQueue; ... for (;;) { // Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } ... try { // 解決是誰再呼叫 dispatchMessage,target 為 MessageQueue 中的 Handler msg.target.dispatchMessage(msg); end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis(); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } ... msg.recycleUnchecked(); } } ``` ## ThreadLocal * **ThreadLocal 的功能主要是做到線程隔離 (用線程隔離數據)**,**簡單來說可以把 ThreadLocal 當作一個 Map (但實際來說它並不是 Map),它的 Key 是 `Thread`,Value 是一個`泛型`** * 詳細 ThreadLocal 分析有寫在 [**Java 多線程-進階**](https://hackmd.io/7Ru0TE45Tnm1LEUqx4qe-A?view#ThreadLocal-%E7%B7%9A%E7%A8%8B%E9%9A%94%E9%9B%A2) ```java= // 前面 Looper.prepare() 內部就有使用到 set() 方法 public void set(T value) { Thread t = Thread.currentThread(); //"1. " ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } // 前面 Handler() 建構函數內部就有使用到 get() 方法 public T get() { Thread t = Thread.currentThread(); //"2. " ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } ``` 1. 它使用目前呼叫它的 Thread.currentThread 作為 Key 存入,Value 是泛型 2. 依照當前線程 (currentThread),取的 Value * **消息機制就是通過每個 Thread 儲存不同的 Looper 在 ThreadLocal 中**,當有需要時就已 Thread 作為 key 來取得 Looper ## 結論 Handler & Thread * 從上面可以看出 Handler & Thread 的關係是建立在,==**ThreadLocal 取得 Looper,而 Handler 建構函數又取得當前 Thread**== * **一個 Thread 對應一個 Looper,一個 Looper 對應一個 MessageQueue** ## Appendix & FAQ :::info ::: ###### tags: `Android 進階`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully