【Android 异步操作】手写 Handler ( 总结 | Message | MessageQueue | Looper | Handler ) ★
文章目錄
- 一、Message 消息
- 二、Handler 消息處理者
- 三、MessageQueue 消息隊列
- 四、Looper 循環者
- 五、關于 Looper 線程本地變量的說明
一、Message 消息
模仿 Android 中的 Message 基本功能 , 提供 what 與 obj 變量 , 提供一個回收方法 ;
此外 , 還要指明下一個消息 , 以及是哪個 Handler 發送的該消息 ;
package kim.hsl.handler;public class Message {/*** 消息識別碼*/int what;/*** 消息對象*/Object obj;/*** 指向下一個消息*/Message next;/*** 該 Message 使用哪個 Handler 進行發送的*/Handler target;/*** 回收方法*/public void recyle(){obj = null;next = null;target = null;} }二、Handler 消息處理者
Handler 有兩個功能 :
功能一 : 發送消息到 Looper 中的 消息隊列 MessageQueue 中 ;
/*** 發送消息* @param msg*/public void sendMessage(Message msg){// 為消息設置發送的 Handlermsg.target = this;// 向消息隊列中放入要執行的消息mQueue.enqueueMessage(msg);}功能二 : 接收 Looper 中的 loop 方法傳來的 Message , 并 執行該 Message 代表的任務 ;
Handler 執行 Message 任務 , 具體的執行邏輯需要 用戶實現 ; 用戶創建 Handler 時 , 需要覆蓋 handleMessage 方法 , 在重寫的方法中處理不同的 Message 任務 ;
/*** 執行消息對應的任務* @param next*/public void handleMessage(Message next) {}Handler 初始化 :
Handler 的功能一 發送消息 , 就是向 消息隊列 MessageQueue 中發送消息 , 并將消息放到 MessageQueue 中的 Message 鏈表隊列的最后一個 ;
這就需要 Handler 持有 消息隊列 MessageQueue 的引用 ,
消息隊列封裝在 Looper 中 , 因此需要先拿到 線程本地變量 Looper , 然后從 Looper 中獲取對應的消息隊列 ;
這里就需要特別注意 , 在初始化 Handler 時 , 需要用到 Looper , 如果 Looper 為空 , Handler 初始化就會失敗 ;
因此在 創建 Handler 之前 , 必須先調用 Looper 的 prepare 方法 , 先將 Looper 進行初始化操作 ;
/*** 消息隊列* 該消息隊列封裝在 Looper 中* Looper 封裝在線程本地變量中*/MessageQueue mQueue;public Handler(){/*在 Handler 中需要拿到 Looper進而拿到 Looper 中的 MessageQueue 消息隊列Handler 的操作就是將 Message 放入 MessageQueue因此在 Handler 中需要持有 MessageQueue 消息隊列的引用獲取 Looper 時 , Looper 必須已經初始化完畢,也就是已經調用過 prepare 創建了 Looper 并將其放入了線程本地變量*/// 獲取當前線程中的 線程本地變量 LooperLooper looper = Looper.looper();// 獲取封裝在 Looper 中的 消息隊列 MessageQueuemQueue = looper.mQueue;}
完整 Handler 代碼 :
package kim.hsl.handler;public class Handler {/*** 消息隊列* 該消息隊列封裝在 Looper 中* Looper 封裝在線程本地變量中*/MessageQueue mQueue;public Handler(){/*在 Handler 中需要拿到 Looper進而拿到 Looper 中的 MessageQueue 消息隊列Handler 的操作就是將 Message 放入 MessageQueue因此在 Handler 中需要持有 MessageQueue 消息隊列的引用獲取 Looper 時 , Looper 必須已經初始化完畢,也就是已經調用過 prepare 創建了 Looper 并將其放入了線程本地變量*/// 獲取當前線程中的 線程本地變量 LooperLooper looper = Looper.looper();// 獲取封裝在 Looper 中的 消息隊列 MessageQueuemQueue = looper.mQueue;}/*** 發送消息* @param msg*/public void sendMessage(Message msg){// 為消息設置發送的 Handlermsg.target = this;// 向消息隊列中放入要執行的消息mQueue.enqueueMessage(msg);}/*** 執行消息對應的任務* @param next*/public void handleMessage(Message next) {} }三、MessageQueue 消息隊列
Message 鏈表 : 消息隊列 MessageQueue , 內部維護了一個 Message 鏈表 , 存儲的時候只存儲第一個 Message 即可 ;
鏈表插入元素 : 當 Handler 在其它線程調用 sendMessage 方法 , 將 消息 Message 放入 Looper 中的 MessageQueue 時 , 針對該鏈表的操作就是 , 循環獲取鏈表的下一個元素 , 最終 獲取到最后一個元素 , 最后一個元素的 next 為空 ; 將 最后一個元素的 next 設置為本次要插入的 Message , 即可完成消息存儲到消息隊列的操作 ;
鏈表元素同步 : 鏈表為空時 , 取出鏈表的操作會阻塞 , 調用的是 wait 方法 , 此時有消息加入鏈表后 , 需要 調用 notify 喚醒阻塞 ;
消息入隊的部分代碼 :
/*** 該隊列是一個鏈表 , 因此這里只給出第一個 Message 即可*/Message mMessage;/*** 將 Message 消息加入到 Message 鏈表中* @param msg*/public void enqueueMessage( Message msg ){// 因為 該消息隊列 可能會有多個線程 通過 Handler 向消息隊列中添加消息// 因此 需要使用同步代碼塊包裹以下邏輯synchronized (this){if( mMessage == null ){mMessage = msg;}else{/*如果鏈表不為空這里需要循環查找消息隊列的最后一個消息將本次傳入的 Message msg 參數加入到鏈表尾部*/Message pointer = mMessage;Message previous = pointer;for(;;){// 記錄上一條消息, 每次遍歷都將本次遍歷的記錄下來previous = pointer;// 將 pointer 指向下一條消息pointer = pointer.next;// 此時如果某個 Message 的 下一個元素為空// 說明該 Message 是消息隊列最后一個元素if(pointer == null){break;}}// 將本次參數傳入的 Message 放到鏈表最后previous.next = msg;}notify();}}Looper 調用 loop 方法后 , 會一直循環 , 不斷地從 消息隊列 MessageQueue 中取出 Message 消息 , 然后 將 Message 消息發送給對應的 Handler 執行對應的操作 ;
從 消息隊列 MessageQueue 中取出消息 , 也是 取出鏈表表頭 的操作 , 取出該鏈表的表頭 , 然后 將表頭設置成鏈表的第二個元素 ;
消息同步 : 如果當前鏈表為空 , 此時會 調用 wait 方法阻塞 , 直到消息入隊時 , 鏈表中有了元素 , 會調用 notify 解除該阻塞 ;
/*** 從消息隊列中獲取消息* @return*/public Message next(){synchronized (this){// 本次要獲取的消息, 最后要返回到 Looper 中 loop 方法中Message result;for (;;){// 嘗試和獲取 消息隊列 鏈表中的第一個元素result = mMessage;if(result == null){// 如果當前的 Message 隊列為空 , 阻塞等待 , 直到新的消息到來try {wait();} catch (InterruptedException e) {e.printStackTrace();}}else{// 如果不為空 , 說明已經獲取到最終的消息 , 退出循環即可break;}}// 處理鏈表邏輯 , 將表頭指向下一個 MessagemMessage = mMessage.next;return result;}}消息隊列完整代碼 :
package kim.hsl.handler;public class MessageQueue {/*** 該隊列是一個鏈表 , 因此這里只給出第一個 Message 即可*/Message mMessage;/*** 將 Message 消息加入到 Message 鏈表中* @param msg*/public void enqueueMessage( Message msg ){// 因為 該消息隊列 可能會有多個線程 通過 Handler 向消息隊列中添加消息// 因此 需要使用同步代碼塊包裹以下邏輯synchronized (this){if( mMessage == null ){mMessage = msg;}else{/*如果鏈表不為空這里需要循環查找消息隊列的最后一個消息將本次傳入的 Message msg 參數加入到鏈表尾部*/Message pointer = mMessage;Message previous = pointer;for(;;){// 記錄上一條消息, 每次遍歷都將本次遍歷的記錄下來previous = pointer;// 將 pointer 指向下一條消息pointer = pointer.next;// 此時如果某個 Message 的 下一個元素為空// 說明該 Message 是消息隊列最后一個元素if(pointer == null){break;}}// 將本次參數傳入的 Message 放到鏈表最后previous.next = msg;}notify();}}/*** 從消息隊列中獲取消息* @return*/public Message next(){synchronized (this){// 本次要獲取的消息, 最后要返回到 Looper 中 loop 方法中Message result;for (;;){// 嘗試和獲取 消息隊列 鏈表中的第一個元素result = mMessage;if(result == null){// 如果當前的 Message 隊列為空 , 阻塞等待 , 直到新的消息到來try {wait();} catch (InterruptedException e) {e.printStackTrace();}}else{// 如果不為空 , 說明已經獲取到最終的消息 , 退出循環即可break;}}// 處理鏈表邏輯 , 將表頭指向下一個 MessagemMessage = mMessage.next;return result;}} }四、Looper 循環者
Looper 是 線程本地變量 , 在每個線程中 , 可以通過線程調用 ThreadLocal 變量的 get 方法獲取該線程對應的對象副本 , 調用 ThreadLocal 變量的 set 方法 , 設置該線程對應類型的對象副本 ;
Looper 調用 prepare 方法進行初始化 , 在該方法中處理 線程本地變量的先關初始化與設置 ,
如果之前已經初始化過 , 本次調用 prepare 方法是第二次調用 , 則會 拋出異常 ,
如果之前沒有初始化過 , 那么創建一個 Looper , 然后調用線程本地變量 ThreadLocal 的 set 方法 , 將該 Looper 對象設置成線程本地變量 ;
/*** 一個線程只能有一個 Looper* 使用 ThreadLocal 來保存該 Looper* 是線程內部存儲類 , 只能本線程才可以得到存儲的數據 ;*/static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();/*** 準備 Looper 方法*/public static void prepare(){System.out.println("prepare 創建 Looper ");// 先進行判斷 , 如果當前線程已經有了 Looper , 那就拋出異常if(sThreadLocal.get() != null){throw new RuntimeException("當前線程已存在 Looper");}// 如果不存在 Looper , 就創建一個 LoopersThreadLocal.set(new Looper());}在 Looper 線程中 , 最后一句代碼肯定是 Looper.loop() , 執行該方法后 , 就開啟了一個無限循環 ,
不斷從 消息隊列 MessageQueue 中獲取消息 , 然后發送給該 消息 Message 對應的 Handler ,
哪個 Handler 發送的消息 , 就將消息在送回給哪個 Handler ;
消息同步 : 當 消息隊列 MessageQueue 為空時 , 無法從消息隊列中獲取數據 , 此時線程會 阻塞 , 直到有新的消息到來后 , 解除阻塞 ;
Looper 循環遍歷消息隊列部分代碼 :
/*** 不斷從 消息隊列 MessageQueue 中取出 Message 消息執行*/public static void loop(){System.out.println("開始無限循環獲取 Message");// 獲取當前線程的 LooperLooper looper = Looper.looper();// 從當前線程的 Looper 獲取 消息隊列 MessageQueueMessageQueue messageQueue = looper.mQueue;// 不斷從 消息隊列中獲取 消息 , 分發到發送消息的 Handler 中執行for(;;){// 獲取消息隊列中的第一個消息Message next = messageQueue.next();// 分發到發送該消息的 Handler 中執行next.target.handleMessage(next);}}完整 Looper 代碼 :
package kim.hsl.handler;public class Looper {/*** 一個線程只能有一個 Looper* 使用 ThreadLocal 來保存該 Looper* 是線程內部存儲類 , 只能本線程才可以得到存儲的數據 ;*/static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();/*** 消息隊列*/public MessageQueue mQueue;/*** Looper 構造函數*/private Looper(){mQueue = new MessageQueue();}/*** 獲取當前線程對應的 Looper* @return*/public static Looper looper(){return sThreadLocal.get();}/*** 準備 Looper 方法*/public static void prepare(){System.out.println("prepare 創建 Looper ");// 先進行判斷 , 如果當前線程已經有了 Looper , 那就拋出異常if(sThreadLocal.get() != null){throw new RuntimeException("當前線程已存在 Looper");}// 如果不存在 Looper , 就創建一個 LoopersThreadLocal.set(new Looper());}/*** 不斷從 消息隊列 MessageQueue 中取出 Message 消息執行*/public static void loop(){System.out.println("開始無限循環獲取 Message");// 獲取當前線程的 LooperLooper looper = Looper.looper();// 從當前線程的 Looper 獲取 消息隊列 MessageQueueMessageQueue messageQueue = looper.mQueue;// 不斷從 消息隊列中獲取 消息 , 分發到發送消息的 Handler 中執行for(;;){// 獲取消息隊列中的第一個消息Message next = messageQueue.next();// 分發到發送該消息的 Handler 中執行next.target.handleMessage(next);}}}五、關于 Looper 線程本地變量的說明
ThreadLocal 作用是 保存線程私有變量 ;
使用 ThreadLocal 維護一個變量時 , 每個使用該 ThreadLocal 線程本地變量 的線程 , 都會 被分配一個獨立的變量副本 ,
每個線程 只 可以 改變本線程內的 變量副本 , 即 ThreadLocal 線程本地變量 ;
1 . ThreadLocal 定義 :
/*** 一個線程只能有一個 Looper* 使用 ThreadLocal 來保存該 Looper* 該變量是線程內部存儲類 , 只能本線程才可以得到存儲的數據 ;*/static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();2 . ThreadLocal 變量獲取 : 調用 ThreadLocal 變量的 get() 方法 , 可以獲取該 ThreadLocal 線程本地變量 ;
在 ThreadLocalMap map = getMap(t) 中 , 獲取的 ThreadLocalMap 與 Java 中的 Map 集合沒有任何關聯 , 該類就是為了保存 線程本地變量而在 ThreadLocal 中設置的內部類 ; 在該 ThreadLocalMap 內部類中 , 通過 key 鍵 , 獲取對應 value 值 ;
public class ThreadLocal<T> {/*** 返回 該線程本地變量的 當前線程的變量副本.* 如果 該線程中對應的 變量沒有值, 應該首先初始化該變量值** @return 返回當前線程的線程本地變量值 */public T get() {// 首先通過 Thread 拿到當前的線程 Thread t = Thread.currentThread();// 通過當前線程 , 獲取當前線程的 ThreadLocalMap ThreadLocalMap map = getMap(t);if (map != null) {// 通過 key 獲取指定的 valueThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();} }3 . ThreadLocal 變量設置 : 調用 ThreadLocal 的 put() 方法 , 可以設置 線程本地變量 ;
4 . Looper 中關于 線程本地變量 的設置 : 在 Looper 中涉及到了 線程本地變量 的設置 ,
Looper 要求每個線程只能保持一個 , 并且各個線程之間的 Looper 相互獨立 , 沒有任何關聯 ;
這就需要 將 Looper 定義成線程本地變量 ;
public class Looper {/*** 一個線程只能有一個 Looper* 使用 ThreadLocal 來保存該 Looper* 是線程內部存儲類 , 只能本線程才可以得到存儲的數據 ;*/static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();/*** 準備 Looper 方法* 其中* 使用了 sThreadLocal.get() 獲取線程本地變量 * 使用了 sThreadLocal.set(new Looper()) 設置線程本地變量 */public static void prepare(){// 先進行判斷 , 如果當前線程已經有了 Looper , 那就拋出異常if(sThreadLocal.get() != null){throw new RuntimeException("當前線程已存在 Looper");}// 如果不存在 Looper , 就創建一個 LoopersThreadLocal.set(new Looper());}總結
以上是生活随笔為你收集整理的【Android 异步操作】手写 Handler ( 总结 | Message | MessageQueue | Looper | Handler ) ★的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 异步操作】手写 Han
- 下一篇: 【Android 异步操作】Handle