Android Handler 消息機制

常用場景:
子線程發送Message?
主線程處理Message

子線程發送消息

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {Message msg = Message.obtain();msg.what = what;return sendMessageDelayed(msg, delayMillis);}public final boolean sendEmptyMessage(int what){return sendEmptyMessageDelayed(what, 0);}

不管那種方式發送小時最終都走到 sendMessageAtTime

public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

通過enqueueMessage將消息按照時間先手插入到MessageQueue中

主線程處理消息

應用啟動:

frameworks\base\core\java\android\app\ActivityThread.java

main方法中關注Looper.prepareMainLooper()和Looper.loop().

Looper.prepareMainLooper()

 public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {//Looper的唯一性throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}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));}private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();//綁定當前線程}MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();}void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}
......
}

1.?prepare創建 Looper 并設置給了sThreadLocal 后面loop中要獲取

2.prepare創建不可退出的MessageQueue(因為在主線程)

Looper.loop()

 public static void loop() {final Looper me = myLooper();......me.mInLoop = true;final MessageQueue queue = me.mQueue;for (;;) { //死循環Message msg = queue.next(); // might block......try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}......msg.recycleUnchecked();}}public static @Nullable Looper myLooper() {return sThreadLocal.get();}

1.Looper持有了當前線程的MessageQueue

2.通過queue.next() 獲取當前時間下一次要執行的Message

3.處理消息 msg.target.dispatchMessage(msg) 分發到內部類中處理(msg.target就是當前消息綁定的Handler)

4. 消息回收(消息復用) msg.recycleUnchecked()

流程:應用啟動-->ActivityThread main 啟動?--> 準備Looper? --> Looper死循環? 一直取隊列中的消息??-->處理消息?--> 消息回收處理

PS: 應用異常(Runtime)時壓棧最底下ActivityThread.main()? ?倒數第二行Looper.loop()? ? 這也印證了啟動的順序

Handler創建

常見創建:

Handler handler=new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}
};public Handler() {this(null, false);}public Handler(@Nullable Callback callback, boolean async) {mLooper = Looper.myLooper();//獲取Looperif (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;}public interface Callback {boolean handleMessage(@NonNull Message msg);}

消息創建

new Message

   /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).*/public Message() {}

Google 沒有詳細說明new? Message 當通過我們實際直接new 就完事了無需多言,不過Google推薦使用Message.obtain() 去創建

方法一:
Message msg = Message.obtain()
--------------------------------源碼-------------------------------public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}
方法二:
Handler handler = new Handler();
Message msg = handler.obtainMessage();//綁定了handler
--------------------------------源碼-------------------------------
public final Message obtainMessage(){return Message.obtain(this);// this==handler}public static Message obtain(Handler h) {Message m = obtain();m.target = h;return m;}

不管那種方式創建最終都是走無參的obtain方法
1.sPool等于空時通過new 創建Message
2.sPool 是在Looper dispatch 出去后通過recycleUnchecked清空后到Message

3.sPool最前面的空Messsage返回回去,sPool指針后移隊列到下一個保證下一個obtain可以正確獲獲取到sPool中的空Message

Message和handler的綁定
1.可以在創建Message是綁定,參考Message創建的方法二
2.消息發送是綁定
回到發送Message

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //綁定動作
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

總結:

?

handler機制就是一個傳送帶
1MessageQueue就是堆放貨物履帶。
2)ActivityThread電機給履帶滾動的動力
3)loop中的死循環就像開關,死循環開啟履帶滾動 輪詢messageQueue按照時間分發出去
4)Message就是要傳輸得貨物

同步問題

Handler是如何保證自己是線程安全?
從總結圖可以看出整個過程只要保證MessageQueue 中msg的入隊和出隊即可
enqueueMessage方法中 通過synchronized (this) {} 鎖住了關鍵的代碼塊
1,synchronized 是內置鎖? ?鎖的lock 和unlock 是系統(JVM)執行
2.? 鎖的是this,就相當于鎖的是?MessageQueue? 相當于 調用同一個MessageQueue的對象是互斥的
3. 一個線程持有一個Looper,一個Looper持有一個MessageQueue
所以:主線程只有一個MessageQueue子線程 發送消息的時候,子線程一次只能處理一個消息,其他的消息需要等鎖,這樣就保證了不管有多少子線程發送消息 主線程的MessageQueue時刻始終只處理一個消息的入隊? ??

next() 方法中 同樣通過synchronized (this) {} 鎖住了按照時間節點返回消息的關鍵代碼

既然這里都是一直要放回當前隊列的時間最靠前的msg(頭消息),加鎖的意義在哪里?

這里就是 鎖this的魅力 鎖住了MessgaeQueue,鎖的范圍是所有 this正在訪問的代碼塊都會有保護作用,即代表next方法和enqueueMessage方法能夠實現互斥統一時間MessageQueue只能入隊或者出隊這樣就保證了MessageQueue的有序性。??

HandlerThread

首先我們看下下面這段代碼創建Handler?

Thread thread = new Thread(new Runnable() {Looper looper;
@Override
public void run() {Looper.prepare();looper =Looper.myLooper();Looper.loop();
}
public Looper getLooper() {return looper;
}
});thread.start();Handler handler = new Handler(thread.getLooper());

子線程去獲取Looper對象然后通過子線程拿到Looper,這段代碼看似么有問題其實有雷運行時可能是出現new Hanlder 中參數Looper 為空

1.可能在執行new Handler對象時子線程沒有走完導致looper沒有賦值完成
解決:thread.start 后延時去new Handler 從而保證looper不為空,但此時線程依舊是不安全的

看看Goolge是如何解決子線程獲取Looper 且線程安全?

public class HandlerThread extends Thread {Looper mLooper;@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}public Looper getLooper() {if (!isAlive()) {return null;}// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {}}}return mLooper;}

1.HandlerThread 是Thread的子類 new HandlerThread? start 執行其run方法

?prepare 后通過獲取Looper關鍵代碼
?synchronized (this) {
? ? ? ? ? ? mLooper = Looper.myLooper();
? ? ? ? ? ? notifyAll();
? ? ? ? }

getLooper關鍵代碼
synchronized (this) {
? ? ? ? ? ? while (isAlive() && mLooper == null) {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? wait();
? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
1.通過synchronized (this)? 實現了run和getLooper的互斥
2.兩種情況?
2.1 :run 先拿到鎖

????????synchronized鎖住的代碼先執行,完完全全拿到Looper后通過notifyAll()通知所有?HandlerThread 對象 結束等鎖,準備拿鎖 ,然后釋放鎖?
2.2:getLooper??先拿到鎖
此時當線程活著時mLooper肯定為空線程執行wait() 等待且釋放鎖,getLooper??釋放鎖的同事run方法就會持有鎖,因為HandlerThread 就這兩個方法會獲取鎖。
如果是sleep()? 線程會阻塞 不會釋放鎖。

此類寫法就保證了不管什么樣的情況下當你通過HandlerThread 去getLooper是一定能獲取到線程的唯一Looper.此時線程是安全的。? ? ? ? ? ? ?

消息機制之同步屏障
揭秘 Android 消息機制之同步屏障:target==null ?我們知道,Android的消息機制就是Handle - 掘金


?



?

?

??

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/914525.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/914525.shtml
英文地址,請注明出處:http://en.pswp.cn/news/914525.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

day9 串口通信

1串口通信串口通信是嵌入式系統和電子設備中最常用的 異步串行通信 方式,核心是通過 TX(發送) 和 R(接收) 兩根線實現全雙工數據傳輸。2通信協議分類2.1同步/異步通信同步和異步是串行通信中兩種根本不同的數據傳輸方式…

面向對象的設計模式

一、設計模式簡介1、什么是設計模式針對反復出現的問題所總結歸納出的通用解決方設計模式是指在軟件開發過程中案。這些方案是眾多軟件開發人員經過大量實踐總結出來的,具有高效性、可維護性和可擴展性等優點。使用設計模式可以幫助開發者更高效地構建軟件系統&…

每日釘釘API探索:chooseDepartments專注于部門維度的選擇接口

在企業級應用開發過程中,針對組織架構中的部門進行操作是非常常見的需求。今天我們要介紹的是釘釘的chooseDepartments API,它允許用戶以部門為單位進行選擇,并返回所選部門的相關信息。📌 功能概述chooseDepartments API主要用于…

生產環境CI/CD流水線構建與優化實踐指南

生產環境CI/CD流水線構建與優化實踐指南 目錄 業務場景描述技術選型過程實現方案詳解 流水線結構設計并行構建與緩存策略部署策略:滾動、藍綠、金絲雀回滾與告警自動化 踩過的坑與解決方案總結與最佳實踐 業務場景描述 某大型電商平臺,為了保證代碼持續交…

騰訊云和火山云優劣勢對比

從問題本身看,用戶沒有限定具體場景,說明可能需要一個全面的橫向對比。不過云計算服務涉及面太廣,我最好先搭建一個框架性的分析結構,再填充具體細節。 首先想到從幾個核心維度切入:基礎能力(計算存儲網絡&…

Augment AI 0.502.0版本深度解析:Task、Guidelines、Memory三大核心功能實戰指南

Augment AI 0.502.0版本深度解析:Task、Guidelines、Memory三大核心功能實戰指南 augment最新版輔助功能全解析續杯免費額度再用滿教程|memory|userguidlines|tasksaugment最新插件功能教程前言 在AI輔助編程領域,Augment AI作為一款強大的VS Code插件&…

docker搭建、小皮面板搭建、bp使用、msf

docker搭建Vulhub靶場 docker安裝 apt-get install docker.io docker-compose#設置docker代理:創建文件夾以及對應的文件 mkdir /etc/systemd/system/docker.service.d#在該文件中配置自己的代理ip以及代理端口 vim /etc/systemd/system/docker.service.d/http-p…

AI優化器美國VPS集成:智能算力部署與性能調優指南

在當今數字化浪潮中,AI優化器與高性能VPS的融合正成為企業技術架構的核心競爭力。本文將深入解析美國VPS服務器如何通過AI驅動的智能優化技術實現算力突破,從資源配置算法到實時流量調度,全面揭示這種創新組合在跨境電商、大數據分析等場景中…

【保姆級圖文詳解】Spring AI 中的工具調用原理解析,工具開發:文件操作、聯網搜索、網頁抓取、資源下載、PDF生成、工具集中注冊

目錄前言一、Spring AI 中的工具調用(Tool Calling)1.1、概念1.2、工作原理1.3、技術選型1.4、原理解析1.4.1、實現接口1.4.2、工具調用二、工具調用(Tool Calling)開發2.1、文件操作2.1.1、概念描述2.1.2、概念描述2.2、聯網搜索…

Redis客戶端使用(Client、Java、SpringBoot)

上篇文章: Redis數據類型之zsethttps://blog.csdn.net/sniper_fandc/article/details/149139955?fromshareblogdetail&sharetypeblogdetail&sharerId149139955&sharereferPC&sharesourcesniper_fandc&sharefromfrom_link 目錄 1 Redis客戶端…

Modbus 開發工具實戰:ModScan32 與 Wireshark 抓包分析(一

引言 ** 在工業自動化領域,Modbus 協議猶如一座橋梁,連接著各種電子設備,實現它們之間高效的數據交互。從可編程邏輯控制器(PLC)到人機界面(HMI),再到各類智能傳感器,M…

Oracle SQL - 使用行轉列PIVOT減少表重復掃描(實例)

[13/JUL/2025, Yusuf Leo, Oracle SQL Performance Tuning Series]我們經常會遇到從同一表中按不同維度取出不同區間的數據,再以相同的屬性將這些數據分別匯總到一起的需求。這類需求往往迫使我們對同一個表反復去掃描,當原始數據量太大的時候&#xff0…

HTTP 請求方法詳解:GET、POST、PUT、DELETE 等

在 HTTP 協議中,請求方法(也稱為 HTTP 動詞)定義了客戶端希望對指定資源執行的操作類型。這些方法是 HTTP 報文的核心組成部分,決定了請求的目的和行為。 主要 HTTP 請求方法 1. GET 用途:獲取資源 特點&#xff1a…

Android 代碼熱度統計(概述)

1. 前言 代碼熱度統計,在測試中一般也叫做代碼覆蓋率。一般得到代碼覆蓋率后就能了解整體樣本在線上的代碼使用情況,為無用代碼下線提供依據。 做了一下調研,在Android中一般比較常用的是:JaCoCO覆蓋率統計工具,它采…

RAG優化

RAG搭建本地AI知識庫,在使用過程中遇到的三大痛點,以及相應的進階方案。1. RAG知識庫的三大痛點-- 內容理解不足:AI難以全面理解導入資料的內容,比如在向量編碼時候,生硬的截斷等導致分析結果不理想。eg: 知識庫分割器…

Ubuntu 24.04 啟用 root 圖形登錄

關鍵詞:Ubuntu 24.04、root 登錄、GDM、SSH、nano、配置文件一、前言 Ubuntu 默認禁用 root 賬戶 的圖形與 SSH 登錄,這是為了安全。但在某些場景(如測試、救援、自動化腳本)你可能需要 直接用 root 登錄 GNOME 桌面。本文以 Ubun…

Jekyll + Chirpy + GitHub Pages 搭建博客

Chirpy 是適用于技術寫作的簡約、響應迅速且功能豐富的 Jekyll 主題,文檔地址:https://chirpy.cotes.page/ ,Github 地址:jekyll-theme-chirpy 。 1.開始 打開 chirpy-starter 倉庫,點擊按鈕 Use this template -->…

學習 Flutter (一)

學習 Flutter (一) 1. 引言 什么是 Flutter? Flutter 是 Google 開發的一套開源 UI 框架,主要用于構建高性能、高保真、跨平臺的應用程序。使用一套 Dart 編寫的代碼,開發者可以同時構建適用于: Android iOS Web Windows、mac…

Spring Boot 實現圖片防盜鏈:Referer 校驗與 Token 簽名校驗完整指南

Spring Boot 實現圖片防盜鏈教程(Referer 校驗 Token 簽名校驗)本文將詳細講解兩種防盜鏈實現方案,并提供完整代碼示例。方案一:Referer 校驗通過檢查 HTTP 請求頭中的 Referer 字段判斷來源是否合法。實現步驟創建 Referer 攔截…

從 JSON 到 Python 對象:一次通透的序列化與反序列化之旅

目錄 一、為什么要談 JSON 二、最快速上手:兩把鑰匙 dumps 與 loads 三、深入 dumps:參數是魔法棒 四、深入 loads:把風險擋在門外 五、文件級序列化:dump 與 load 六、處理中文與編碼陷阱 七、異常場景與調試技巧 八、實…