Handler 機制分析

android 子線程和UI線程的交互主要使用Handler的方法進行通信。本文分析Handler機制

Handler 如何使用?

Handler的使用比較簡單

    public class MainActivity extends Activity{private Handler handler = new Handler() {  public void handleMessage(Message msg) {   switch (msg.what) {   case 0x01://do somethings}}};@Override  protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);  handler = new Handler();new Thread(new Runnable(){@Overridepublic void run(){Message message = new Message();   message.what = 0x01;   handler.sendMessage(message);}}).start();}}復制代碼

如代碼就是一個簡單的Handler的使用Demo,有如下幾個問題

  1. Handler 是否可以在子線程中初始化。可以,但是如下代碼執行的話會拋出該錯誤"Can't create handler inside thread that has not called Looper.prepare() ".說
    是不能再沒有調用Looper.prepare()的線程中創建Handler。因此如果需要在線程中創建Handler首先調用一下Looper.prepare
    new Thread(new Runnable() {  @Override  public void run() {  Handler handler = new Handler();  }  }).start();復制代碼

這樣調用將不會拋出異常。

    new Thread(new Runnable() {  @Override  public void run() {Looper.prepare();Handler handler = new Handler();  Looper.loop();}  }).start();復制代碼

Looper和Handler的聯系是什么樣的呢?

我們看一下Handler初始化的代碼

    public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}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;}復制代碼

可以看到如果mLooper是通過Looper.myLooper獲得一個Looper對象,如果Looper對象為空,則拋出上述異常。那Looper.myLooper是如何定義的呢?

     public static @Nullable Looper myLooper() {return sThreadLocal.get();}復制代碼

該方法很簡單就是從sThreadLocal對象中獲得Looper對象。如果sThreadLocal中存在就返回Looper,如果沒有就返回null。那Looper是如何存放在sThreadLocal中,
不錯就是Looper.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));}復制代碼

可以看到,首先判斷sThreadLocal中是否存在Looper對象,如果已經存在,那么如果還有prepare Looper則拋出異常;否則就新建一個Looper存放到sThreadLocal中。
該代碼同時說明每個線程最多一個Looper對象。

Looper采用ThreadLocal來維護各個線程的Looper對象。ThreadLocal是什么呢?官方定義是:ThreadLocal實現了線程本地存儲。所有線程共享同一個ThreadLocal對象,但不同線程僅能訪問與其線程相關聯的值,一個線程修改ThreadLocal對象對其他線程沒有影響。
我們可以將ThreadLocal理解為一塊存儲區,將這一大塊存儲區分割為多塊小的存儲區,每一個線程擁有一塊屬于自己的存儲區,那么對自己的存儲區操作就不會影響其他線程。對于ThreadLocal,則每一小塊存儲區中就保存了與特定線程關聯的Looper。

主線程中使用Handler時為什么沒有執行Looper.prepare()也可以使用Handler呢?其實在進程啟動的時候我們已經創建了主線程也依賴的Looper,代碼在ActivityThread中main方法中。

     public static void main(String[] args) {....Looper.prepareMainLooper();ActivityThread thread = new ActivityThread();thread.attach(false);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");}復制代碼

可以看到該方法執行的是Looper.prepareMainLooper方法,可看到歸根到底還是執行prepare方法

    public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}復制代碼

Message又是如何引入到Handler機制的?

眾所周知,我們都知道Handler,Message,Looper是Handler機制不可或缺的要素。那么Message都是如何引入到Handler.我們看一下上述例子Message是通過handler.sendMessage(message)引入到Handler中。

      public final boolean sendMessage(Message msg){return sendMessageDelayed(msg, 0);}public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}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);}復制代碼

可以看到sendMessage最終調用MessageQueue中enqueueMessage

    boolean enqueueMessage(Message msg, long when) {....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 {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.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.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}復制代碼

MessageQueue并沒有使用一個集合把信息保存,它只是通過使用mMessage對象表示當前需要處理消息,然后根據時間把msg進行排序。具體方法是根據時間順序調用msg.next。從而為每一個消息指定它
的下一個消息是什么。如果需要將msg作為隊頭插入到MessageQueue中可以調用sendMessageAtFrontOfQueue實現。

這樣消息就進入到MessageQueue中,那如何從MessageQueue中將消息取出來呢?
大家有沒有注意到Loop.prepare一般和Looper.loop對應使用。其實Looper.loop就是用來從MessageQueue中取出message。

    public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}try {msg.target.dispatchMessage(msg);} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}....}}復制代碼

上文我們知道每個Thread都有一個Looper,其實每個Looper都對應一個MessageQueue。loop方法我們獲得對應looper中的MessageQueue不斷取出msg,并傳入到dispatchMessage.
dispatchMessage方法將取出的msg傳遞到定義Handler時重寫的handleMessage方法。

    public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}復制代碼

Handler,Message,MessageQueue,Looper流程示意圖如下:

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

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

相關文章

gearman mysql編譯_gearman初探(一、編譯和安裝)

gearman是一個任務分發系統&#xff0c;將計算比較耗時的任務分發給不同的機器專門進行計算&#xff0c;而任務發起的初始程序不必等待這些任務完成就可以返回而繼 續執行。筆者最開始做PHP郵件發送的時候&#xff0c;因為郵件發送耗時比較長&#xff0c;這時PHP腳本就會被阻塞…

《假如編程是魔法之零基礎看得懂的Python入門教程 》——(四)了解魔法百寶箱列表、字典及基本數據類型

學習目標 了解魔法世界中可更改容器——變量了解魔法世界的基本數值類型——字符串、整數了解魔法百寶箱——字典、列表了解列表如何添加值了解字典如何添加值了解字典與列表定義的不同符號 目錄 第一篇&#xff1a;《假如編程是魔法之零基礎看得懂的Python入門教程 》——&…

TCP協議之如何保證傳輸的可靠性

一、問題 TCP協議之如何保證傳輸的可靠性?我們先看下TCP的頭部圖片和TCP頭部的字段 /*TCP頭定義,共20個字節*/ typedef struct _TCP_HEADER {short m_sSourPort;       // 源端口號16bitshort m_sDestPort;       // 目的端口號16bitunsigned int …

【工具篇】在Mac上開發.Net Core需要的工具

微信公眾號&#xff1a;趣編程ACE關注可了解更多的.NET日常開發技巧,如需幫助&#xff0c;請后臺留言&#xff1b;[如果覺得本公眾號對您有幫助&#xff0c;歡迎關注]在Mac上開發.Net Core需要的工具如果您是一個.NET 開發者&#xff0c;想從Windows切換到Mac上開發.NET或者您已…

【Pix4d精品教程】Pix4Dmapper完整航測內業操作流程手把手圖文教程

1. 作業流程圖 2. 原始資料準備 原始資料包括影像數據、POS數據以及控制點數據。 確認原始數據的完整性,檢查獲取的影像中有沒有質量不合格的相片。同時查看POS數據文件,主要檢查航帶變化處的相片號,防止POS數據中的相片號與影像數據相片號不對應,出現不對應情況應手動調…

關于構造函數和this調用的思考

文中一系列思考和內容引發自以下問題&#xff1a;我需要在一個類的構造函數中調用另一個對象的構造函數&#xff0c;并使用this初始化其中的一個引用成員。 主要遇到的問題&#xff1a; 1. 構造函數的初始化列表中能訪問this嗎&#xff1f; 很明顯c創建一個對象分為兩部分&…

mysql semi join_MySQL 5.6 Semi join優化之materialization strategy

8月 24, 2014 |Nix.Huang考慮如下查詢&#xff1a;select * from Countrywhere Country.code IN (select City.Countryfrom Citywhere City.Population > 7*1000*1000)and Country.continentEurope這個子查詢是非相關子查詢&#xff0c;我們能和外層循環獨立的執行它&#x…

【ArcGIS風暴】何為動態投影?這次全面為您揭開ArcGIS中動態投影的神秘面紗!

本課程配套藍光視頻: 【ArcGIS風暴】GIS動態投影問題 GISer們都見過以下警告,該警告的原因是當前加載的數據的坐標系和當前數據框坐標系不一致導致的,核心問題是地理坐標系不一致。如當前數據的坐標系是GCS_Xian_1980,而數據框的坐標系有可能是WGS_1984等,總之跟要加載的數…

《假如編程是魔法之零基礎看得懂的Python入門教程 》——(五)我的魔法竟然有了一絲邏輯

學習目標 了解魔法世界中的結構表現——縮進了解魔法世界的邏輯判斷——if了解魔法世界的多次邏輯判斷——ifelse嵌套了解魔法世界中的邏輯運算——且 and 與或 or 推薦 1.《備受好評的看得懂的C語言入門教程》 目錄 第一篇&#xff1a;《假如編程是魔法之零基礎看得懂的P…

類和類之間的關系

一、類和類之間的關系 UML類圖中&#xff0c;常見以下幾種關系: 1、泛化&#xff08;Generalization&#xff09; 是一種繼承關系&#xff0c;比如動物類和老虎類&#xff0c;老虎繼承動物&#xff0c;子類如何特化父類的所有特征和行為 箭頭指向:帶三角箭頭的實線&#xff0…

Java SpringMvc+hibernate架構中,調用Oracle中的sp,傳遞數組參數

一、問題 我們調用數據&#xff0c;大都是可以直接獲取表中的數據&#xff0c;或者用復雜點的sql語句組成的。但是&#xff0c;有時候&#xff0c;當這樣達不到我們要的全部數據的時候&#xff0c;這時&#xff0c;我們就用到了存儲過程【sp】&#xff0c;如果sp需要參數是數組…

js模擬blur

<div></div> 某個事件給div加個屬性 $(div).attr(wait,true); $(div).click(function() { if(false!$(this).attr(wait)) return false; })轉載于:https://www.cnblogs.com/cndxk/p/4788414.html

中國第一朵企業云

本文講的是中國第一朵企業云&#xff0c;【IT168 資訊】算起來&#xff0c;中國中化集團公司的ERP完全運行在“云”上已經一個多月了&#xff0c;每每提到這個“創舉”&#xff0c;信息技術部總經理彭勁松顯得有些興奮&#xff0c;卻仍然很謹慎。作為中國第一家企業云的實踐者&…

查缺補漏系統學習 EF Core 6 - 實體配置

推薦關注「碼俠江湖」加星標&#xff0c;時刻不忘江湖事這是 EF Core 系列的第二篇文章&#xff0c;上一篇文章講解了 EF Core 的一些基礎概念&#xff0c;這一篇文章主要圍繞實體屬性的配置。點擊上方或后方藍字&#xff0c;閱讀 EF Core 系列合集。實體配置配置實體的目的&am…

【ArcGIS風暴】捕捉VS經典捕捉,誰更有用武之地?

幾乎所有的GIS軟件都具有捕捉功能!今天我們一起來聊一聊ArcGIS軟件中的捕捉功能吧。 ArcGIS軟件中有兩個重要的捕捉工具:捕捉和經典捕捉。 目錄 一、捕捉(Snapping) 1、捕捉類型 2、捕捉選項

mysql innodb 索引 延遲更新_Mysql覆蓋索引與延遲關聯

延遲關聯&#xff1a;通過使用覆蓋索引查詢返回需要的主鍵,再根據主鍵關聯原表獲得需要的數據。為什innodb的索引葉子節點存的是主鍵&#xff0c;而不是像myisam一樣存數據的物理地址指針&#xff1f;如果存的是物理地址指針不就不需要二次查找了嗎&#xff0c;根據myisam和inn…

Android之在筆記本電腦adb devices識別不了oppo A9手機(設備管理器ADB Interface里面有個黃色感嘆號)

1 問題 記本電腦adb devices識別不了oppo A9手機&#xff08;設備管理器ADB Interface里面有個黃色感嘆號&#xff09; 圖片如下 2 分析 很明顯這里是驅動問題&#xff0c;ADB Interface有感嘆號提示&#xff0c;所以這里需要解決驅動問題 3 解決辦法 1&#xff09;可以嘗試…

《假如編程是魔法之零基礎看得懂的Python入門教程 》——(六)精簡魔法更強大

學習目標 了解對相似邏輯的簡化編寫——循環 推薦 1.《備受好評的看得懂的C語言入門教程》 目錄 第一篇&#xff1a;《假如編程是魔法之零基礎看得懂的Python入門教程 》——&#xff08;一&#xff09;既然你選擇了這系列教程那么我就要讓你聽得懂 第二篇&#xff1a;《假…

Tiny模板語言(VelocityPlus)初步入門

2019獨角獸企業重金招聘Python工程師標準>>> 1 關于用戶手冊 本文主要介紹如何在模板中使用Tiny模板語言&#xff0c;通過查閱本手冊&#xff0c;可以對Tiny模板語言 TTL(Tiny Template Language)的用法有一個較全面的認識&#xff0c;并學會如何有效地使用Tiny模板…

第十二周學習進度表

第十二周所花時間&#xff08;包括上課&#xff09;上課&#xff1a;4小時&#xff08;2小時的實驗&#xff09;&#xff0c;周一&#xff1a;2小時&#xff0c;周三&#xff1a;3小時&#xff0c;周四&#xff1a;2小時&#xff0c;周五&#xff1a;2小時&#xff0c;周六、日…