Android學習筆記-事件處理之Handler消息傳遞機制

来源:http://www.cnblogs.com/qianzixuan1996/archive/2017/12/08/8001013.html
-Advertisement-
Play Games

內容摘要:Android Handler消息傳遞機制的學習總結、問題記錄 Handler消息傳遞機制的目的: 1.實現線程間通信(如:Android平臺只允許主線程(UI線程)修改Activity里的UI組件,而實際開發時會遇到新開的線程要改變界面組件屬性的情況,這時就要有一種辦法通知主線程更新UI ...


內容摘要:Android Handler消息傳遞機制的學習總結、問題記錄

 

Handler消息傳遞機制的目的:

1.實現線程間通信(如:Android平臺只允許主線程(UI線程)修改Activity里的UI組件,而實際開發時會遇到新開的線程要改變界面組件屬性的情況,這時就要有一種辦法通知主線程更新UI)。Handler消息傳遞機制可用於線程間傳遞消息。

2.實現消息的非同步處理。

 

機制的實現:(工作原理涉及Handler、Looper、Message(消息)、MessageQueue(消息隊列);代碼分消息接收方,發送方2處)

原理說明(本人理解有限,比較好的Handler說明看這篇):

Handler可發送Message到MessageQueue或處理從Looper收到的Message。

Message消息對象,在整個機制中傳遞。

MessageQueue是一個以先進先出方式管理Message的隊列。

Looper管理MessageQueue,它把從MessageQueue里取到的Message分發給相應的Handler。

 

原理圖:

 

註意:

1.Handler是建立在Looper上,實現Thread的消息系統處理模型,實現消息非同步處理的;

2.MessageQueue會在Looper(Looper()構造函數)初始化時創建關聯;

3.一個線程最多只能有一個Looper對象(Looper.prepare()方法創建Looper對象,規定了這個);

4.主線程(UI線程)系統已經幫初始化了一個Looper對象(簡單分析看這主線程源碼詳細分析看這),因此程式直接創建Handler即可;程式員自己啟動的線程必須先創建Looper對象並啟動(調用Looper.loop()),然後才能向該線程的消息隊列發消息。

 

Looper源碼參考:

prepare()方法保證每個線程最多只有一個Looper對象,loop()方法使用一個死迴圈不斷取出MessageQueue中的消息,並把取出的消息分給對應的Handler處理。

1 //Looper初始化時創建並關聯MessageQueue
2 private Looper(boolean quitAllowed) {
3     mQueue = new MessageQueue(quitAllowed);
4     mThread = Thread.currentThread();
5 }
 1 //一個線程最多一個Looper,調用prepare()方法創建Looper對象
 2 public static void prepare() {
 3     prepare(true);
 4 }
 5 
 6 private static void prepare(boolean quitAllowed) {
 7     if (sThreadLocal.get() != null) {
 8         throw new RuntimeException("Only one Looper may be created per thread");
 9     }
10     sThreadLocal.set(new Looper(quitAllowed));
11 }
 1 /**
 2  * Initialize the current thread as a looper, marking it as an
 3  * application's main looper. The main looper for your application
 4  * is created by the Android environment, so you should never need
 5  * to call this function yourself.  See also: {@link #prepare()}
 6  */
 7 //主UI線程初始化Looper對象調用的方法
 8 public static void prepareMainLooper() {
 9     prepare(false);
10     synchronized (Looper.class) {
11         if (sMainLooper != null) {
12             throw new IllegalStateException("The main Looper has already been prepared.");
13         }
14         sMainLooper = myLooper();
15     }
16 }
 1 /**
 2  * Run the message queue in this thread. Be sure to call
 3  * {@link #quit()} to end the loop.
 4  */
 5 public static void loop() {
 6     final Looper me = myLooper();
 7     if (me == null) {
 8         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
 9     }
10     final MessageQueue queue = me.mQueue;
11 
12     // Make sure the identity of this thread is that of the local process,
13     // and keep track of what that identity token actually is.
14     Binder.clearCallingIdentity();
15     final long ident = Binder.clearCallingIdentity();
16 
17    //使用一個死迴圈不斷從MessageQueue取Message,併發給對應Handler
18     for (;;) {
19         Message msg = queue.next(); // might block
20         if (msg == null) {
21             // No message indicates that the message queue is quitting.
22             return;
23         }
24 
25         // This must be in a local variable, in case a UI event sets the logger
26         final Printer logging = me.mLogging;
27         if (logging != null) {
28             logging.println(">>>>> Dispatching to " + msg.target + " " +
29                     msg.callback + ": " + msg.what);
30         }
31 
32         final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
33 
34         final long traceTag = me.mTraceTag;
35         if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
36             Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
37         }
38         final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
39         final long end;
40         try {
41             msg.target.dispatchMessage(msg);
42             end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
43         } finally {
44             if (traceTag != 0) {
45                 Trace.traceEnd(traceTag);
46             }
47         }
48         if (slowDispatchThresholdMs > 0) {
49             final long time = end - start;
50             if (time > slowDispatchThresholdMs) {
51                 Slog.w(TAG, "Dispatch took " + time + "ms on "
52                         + Thread.currentThread().getName() + ", h=" +
53                         msg.target + " cb=" + msg.callback + " msg=" + msg.what);
54             }
55         }
56 
57         if (logging != null) {
58             logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
59         }
60 
61         // Make sure that during the course of dispatching the
62         // identity of the thread wasn't corrupted.
63         final long newIdent = Binder.clearCallingIdentity();
64         if (ident != newIdent) {
65             Log.wtf(TAG, "Thread identity changed from 0x"
66                     + Long.toHexString(ident) + " to 0x"
67                     + Long.toHexString(newIdent) + " while dispatching to "
68                     + msg.target.getClass().getName() + " "
69                     + msg.callback + " what=" + msg.what);
70         }
71 
72         msg.recycleUnchecked();
73     }
74 }

原理總結到這,下麵看看在代碼中具體如何實現

 

代碼示例(只是一種實現,更多Handler用法總結看這):

1.消息接收方線程先調用Looper.prepare()創建Looper對象,然後創建Handler對象並定義處理消息的方法,接下來調用Looper.loop()啟動Looper。

 1 class CallbackThread extends Thread {
 2     public Handler mHandler;
 3         
 4     public void run() {
 5         Looper.prepare();
 6         mHandler = new Handler() {
 7             @Override
 8             public void handleMessage(Message msg) {
 9                 if (msg.what == 123) {
10                     Toast.makeText(MainActivity.this, "get message!", Toast.LENGTH_SHORT).show();
11                 }
12             }
13         };
14         Looper.loop();
15     }
16 }

2.消息發送方線程通過調用Handler類相關方法向接收方線程的Handler對象發送消息,可用的方法有:

 1 callbackThread = new CallbackThread();
 2 callbackThread.start();
 3 
 4 //發空消息
 5 callbackThread.mHandler.sendEmptyMessage(123);
 6 
 7 //創建消息發送
 8 Message msg = new Message();
 9 msg.what = 123;
10 callbackThread.mHandler.sendMessage(msg);

以上是Handler學習總結,接下來是學習過程中遇到的問題記錄。

 

問題記錄

1.代碼示例中接收消息的線程是先調用Looper.prepare(),再創建Handler實現消息處理方法,最後再Looper.loop()。為什麼是prepare->Handler->loop這個順序?可不可以換?

答:順序不能換。首先prepare是肯定要在loop之前,因為prepare()方法源碼註釋中有這樣一句話(Be sure to call* {@link #loop()} after calling this method),那就按它的來(因為我沒看源碼......)。可還有兩種順序是吧,一個個試下。Handler->prepare->loop不行,現象是發出的消息沒被接收。prepare->loop->Handler也不可以,現象是導致APP退出。我(zhao)的(chao)理(wang)解(shang)是Handler要想正常工作首先要保證當前線程中有Looper對象(why?可能是能發消息首先要有消息隊列?),所以先要prepare創建Looper對象;然後Looper.loop()使用死迴圈取消息,且當沒有消息時會阻塞,這樣的話放在它之後的代碼——創建Handler的代碼不會執行,當調用該Handler對象的sendMessage()一類方法時便會產生NullPointerException,如下(AV畫質):

 

 

2.不是說只有UI線程能對UI組件操作,為什麼當上面截圖中代碼把創建Handler對象放在prepare和loop之間時,子線程使用Toast不報錯還能顯示?

答:首先子線程直接用Toast是不行的,不會彈出Toast只會報錯......之後我上網找啊找啊找到這篇,只是後面得出結論的時候說“Toast可能是屬於修改UI界面”???這幾個意思???於是我又找啊找啊,知乎找到這個問題,天啊!用了Toast這麼久難道它不是更新UI操作,可能正如知乎上大佬說的——“吐司操作的是window,不屬於checkThread拋主線程不能更新UI異常的管理範疇”?信息量太大,我能力有限還沒理解,先存疑記錄//Todo。總之,現在知道Toast要在子線程中使用可以藉助Looper。

 

3.Looper.loop()使用死迴圈取消息難道不會很耗資源嗎?

答:並不會,具體看這篇。簡而言之,死迴圈中調用queue.next()讀取下一條消息(在loop調用的線程中),如果讀取到了就msg.target.dispatchMessage(),否則queue.next()則會一直阻塞到超過超時時間。

 

4.主線程Looper也調用了loop(),會不會也阻塞?

答:也會有阻塞,但不會卡死,其實和問題3是一個道理,MessageQueue沒消息了都會阻塞進入休眠,之後會被句柄寫操作喚醒epoll.wait。參考:知乎問題CSDN文章(雖然文章標題和結論矛盾)。

 

5.queue.next()的阻塞是怎麼實現的?

答:參考3,4中的鏈接。關鍵字:Linux pipe/epoll機制,loop()的queue.next()中的nativePollOnce()方法。

 

感想:第一篇博客花了我一晚上一早上加半個下午,媽呀!那些大佬都怎麼這麼高產的。問題其實還有更多的,但一部分忘了記錄下來,一部分太不成熟,再就是還存在沒發現的問題......越來越懵逼了,完全沒有豁然開朗的感覺???主要是知道的太少了,一次性見識到這麼多新的事物消化不來,學習筆記也很亂,畢竟第一次寫博客,慢慢學吧,Android之路長著呢,嘻嘻嘻!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Redis是一個開源的使用ANSI C語言編寫、支持網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。 Redis是 NoSQL技術陣營中的一員。 說到NoSQL,什麼是NoSQL? NoSQL是一類新出現的資料庫即not only sql,它不像關係型資料庫,由 ...
  • 1.慢sql情況查詢: 可以使用以下三種方式查詢,第一種是瞭解MySQL進程大概情況;第二種是按照影響時間倒序的,可以查詢到目前最慢的一條sql;第三種是防止sql 的info消息過長而無法顯示完整。 2.連接數的查詢: 可以使用以下sql查詢到當前實例下所有庫的連接數(由於該sql是根據同一個ho ...
  • 開發中常常會遇到因為數據存在多條導致查詢報錯的問題,下麵的方法一鍵解決此問題。 1.查詢重覆數據條數 select count(1) from tb_jf_order_drawcash where order_id in (select order_idfrom tb_jf_order_drawca ...
  • Android如何使用Https,這一篇文章是NoHttp系列中比較重要的,為大家介紹一下內容。 什麼是Https? HTTPS(全稱:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版 ...
  • 有沒有覺得Android的findViewById挺煩人的。使用Kotlin可以讓你徹底拋棄這個煩惱 步驟1、在build.gradle(Module:app)中添加如下一句話 這個在老一點版本的Android Studio中需要手動添加,我的是Android Studio3.0的,這句話是預設加上 ...
  • Android系統中,目前Dangerous級別的許可權都需要動態申請。步驟如下; 1、AndroidManfiest.xml中申明需要的動態許可權 2、代碼中檢查許可權、申請許可權 如下方法執行之後,會彈出提示框,提示要申請該許可權 3、獲取結果 轉載請註明鏈接:http://www.cnblogs.com ...
  • MvvmCross從4.0之後plugin的註冊介面做了重構,網上例子不多,這裡給個參考。本例子使用MvvmCross.Plugins.DownloadCache和MvvmCross.Plugins.File.PluginLoader來顯示網上的一個圖片。 1,View里先給個UIKit.UIIma ...
  • 在iOS開發中、經常用到圖片的本地化。 iOS 圖片本地存儲、本地獲取、本地刪除,可以通過以下類方法實現。 //將圖片保存到本地 + (void)SaveImageToLocal:(UIImage*)image Keys:(NSString*)key { //首先,需要獲取沙盒路徑 NSString ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...