Android - 消息機制與線程通信


以下資料摘錄整理自老羅的Android之旅博客,是對老羅的博客關於Android底層原理的一個抽象的知識概括總結(如有錯誤歡迎指出)(侵刪):
http://blog.csdn.net/luoshengyang/article/details/8923485
http://blog.csdn.net/luoshengyang/article/details/12957169

 整理by Doing

消息機制
          Android應用程序是通過消息來驅動的,系統為每一個應用程序維護一個消息隊列,應用程序的主線程不斷地從這個消息隊例中獲取消息(Looper),然后對這些消息進行處理(Handler),這樣就實現了通過消息來驅動應用程序的執行
          ActivityManagerService需要與應用程序進行並互時,如加載Activity和Service、處理廣播待,會通過Binder進程間通信機制來知會應用程序,應用程序接收到這個請求時,它不是馬上就處理這個請求,而是將這個請求封裝成一個消息,然后把這個消息放在應用程序的消息隊列中去,然后再通過消息循環來處理這個消息。這樣做的好處就是消息的發送方只要把消息發送到應用程序的消息隊列中去就行了,它可以馬上返回去處理別的事情,而不需要等待消息的接收方去處理完這個消息才返回,這樣就可以提高系統的並發性。實質上,這就是一種異步處理機制
 
Android應用程序有兩種類型的線程
  1. 帶有消息隊列,用來執行循環性任務有消息時就處理;沒有消息時就睡眠;(例子:主線程android.os.HandlerThread
  2. 沒有消息隊列,用來執行一次性任務任務一旦執行完成便退出;(例子:java.lang.Thread
 
帶有消息隊列的線程四要素
Message(消息)、 MessageQueue(消息隊列)、 Looper(消息循環)、 Handler(消息發送和處理)
 
Message、 MessageQueue、 Looper和Handler的交互過程
MessageQueue與Looper的關系
 
消息循環
          消息循環過程是由Looper類來實現(主線程中已默認創建)。 Android應用程序進程在啟動的時候,會在進程中加載ActivityThread類,並且執行這個類的main函數,應用程序的消息循環過程就是在這個main函數里面實現的。
          在消息處理機制中,消息都是存放在一個消息隊列中去,而應用程序的主線程就是圍繞這個消息隊列進入一個無限循環的,直到應用程序退出。如果隊列中有消息,應用程序的主線程就會把它取出來,並分發給相應的Handler進行處理;如果隊列中沒有消息,應用程序的主線程就會進入空閑等待狀態,等待下一個消息的到來。
 
  • 創建Java層的Looper 在Java層,創建了一個Looper對象,這個Looper對象是用來進入消息循環的,它的內部有一個消息隊列MessageQueue對象mQueue:
Looper.prepare/prepareMainLooper:
  • Looper類的靜態成員函數prepareMainLooper是專門應用程序的主線程調用的,應用程序的其它子線程都不應該調用這個函數來在本線程中創建消息循環對象,而應該調用prepare函數來在本線程中創建消息循環對象:其它地方能夠方便地通過Looper類的getMainLooper函數來獲得應用程序主線程中的消息循環對象,進而能向應用程序主線程發送消息
 
  • 創建JNI層的Looper對象在JNI層,創建了一個NativeMessageQueue對象,這個NativeMessageQueue對象保存在Java層的消息隊列對象mQueue的成員變量mPtr中; 在C++層,創建了一個Looper對象,保存在JNI層的NativeMessageQueue對象的成員變量mLooper中,這個對象的作用是,當Java層的消息隊列中沒有消息時,就使Android應用程序主線程進入等待狀態,而當Java層的消息隊列中來了新的消息后,就喚醒Android應用程序的主線程來處理這個消息:
 
          JNI層的Looper對象通過pipe系統調用來創建了一個管道
          管道 是Linux系統中的一種進程間通信機制, 管道就是一個文件,在管道的兩端,分別是兩個打開文件文件描述符,這兩個打開文件描述符都是對應同一個文件,其中一個是用來讀的,別一個是用來寫的 ,一般的使用方式就是, 一個線程通過讀文件描述符中來讀管道的內容,當管道沒有內容時,這個線程就會進入等待狀態 ,而 另外一個線程通過寫文件描述符來向管道中寫入內容,寫入內容的時候,如果另一端正有線程正在等待管道中的內容,那么這個線程就會被喚醒:
  1. 一種進程/線程間通信機制
  2. 包含一個寫端文件描述符和一個讀端文件描述符
  3. Looper通過讀端文件描述符等待新消息的到來
  4. Handler通過寫端文件描述符通知Looper新消息的到來
 
          epoll 管道上的等待和喚醒的操作要借助Linux系統中的epoll機制, Looper利用epoll來監控消息隊列是否有新的消息,也就是監控消息管道的讀端文件描述符  Linux系統中的epoll機制為處理大批量句柄而作了改進的poll,是Linux下多路復用IO接口select/poll的增強版本,它能顯著減少程序在大量並發連接中只有少量活躍的情況下的系統CPU利用率:
  1. 一種I/O多路復用技術,select/poll加強版
  2. epoll_create:創建一個epoll句柄
  3. epoll_ctl:設置要監控的文件描述符
  4. epoll_wait:等待監控的文件描述符發生IO事件
          為什么要用epoll?: Looper除了監控消息管道之外,還需要監控其它文件描述符,例如,用來接收鍵盤/觸摸屏事件的文件描述符
 
消息的發送
  •  在ActivityThread.queueOrSendMessage函數中,把上面傳進來的參數封裝成一個Message對象msg,然后通過mH.sendMessage函數把這個消息對象msg加入到應用程序的消息隊列中去。(mH為H類,繼承於Handler類)
  • 在Looper::wake()中,通過打開文件描述符mWakeWritePipeFd往管道的寫入一個"W"字符串。其實,往管道寫入什么內容並不重要,往管道寫入內容的目的是為了喚醒應用程序的主線程。
 
JAVA層常用的消息發送接口:
  • Handler.sendMessage帶一個Message參數,用來描述消息的內容
  • Handler.post帶一個Runnable參數,會被轉換為一個Message參數
Handler.sendMessage/post、 Handler.sendMessageDelayed、 Handler.sendMessageAtTime.....
 
Message
 
消息的處理
調用Handler類的handleMessage函數來處理消息
 
總結
         A. Android應用程序的消息處理機制由 消息循環消息發送消息處理三個部分組成的。
         B. Android應用程序的主線程在進入消息循環過程前,會 在內部創建一個Linux管道(Pipe),這個管道的作用是使得Android應用程序主線程在消息隊列為空時可以進入空閑等待狀態,並且使得當應用程序的消息隊列有消息需要處理時喚醒應用程序的主線程。
         C. Android應用程序的 主線程進入空閑等待狀態的方式實際上就是在管道的讀端等待管道中有新的內容可讀,具體來說就是是通過Linux系統的 Epoll機制中的epoll_wait函數進行的。
         D. 當往Android應用程序的消息隊列中加入新的消息時,會同時 往管道中的寫端寫入內容,通過這種方式就可以 喚醒正在等待消息到來的應用程序主線程
         E. 當應用程序 主線程在進入空閑等待前,會認為當前線程處理空閑狀態,於是就會 調用那些已經注冊了的IdleHandler接口,使得應用程序有機會在空閑的時候處理一些事情

消息在異步任務的應用
在主線程為什么要用異步任務?
-      主線程任務繁重
  • 執行組件生命周期函數
  • 執行業務邏輯
  • 執行用戶交互
  • 執行UI渲染
-      主線程處理某一個消息時間過長時會產生ANR
  • Service生命周期函數 – 20s
  • Broadcast Receiver接收前台優先級廣播函數 –10s
  • Broadcast Receiver接收后台優先級廣播函數 – 60s
  • 影響輸入事件處理的函數 – 5s
  • 影響進程啟動的函數 – 10s
  • 影響Activity切換的函數– 2s
 
基於消息的異步任務接口
  • android.os.HandlerThread適合用來處於不需要更新UI的后台任務
  • android.os.AyncTask適合用來處於需要更新UI的后台任務
 
HandlerThread
  • HandlerThread類繼承了Thread類,因此,通過它可以在應用程序中創建一個子線程;其次,在它的run函數中,首先是調用Looper類的靜態成員函數prepare來准備一個消息循環對象,然后會進入一個消息循環中,因此,這個子線程可以常駐在應用程序中,直到它接收收到一個退出消息為止
  • Looper類的myLooper成員函數將這個子線程中的消息循環對象保存在HandlerThread類中的成員變量mLooper中, 這樣,其它地方就可以方便地通過它的getLooper函數來獲得這個消息循環對象了,有了這個消息循環對象后,就可以往這個子線程的消息隊列中發送消息,通知這個子線程執行特定的任務
android.os.HandlerThread
 
AsyncTask類
  • 內部實現ThreadPoolExecutor類線程池
  • 獲取創建AsyncTask對象的當前所在線程的Handler進行消息發送和處理
 
源碼分析:
  • 當第一次創建一個AsyncTask對象時,首先會創建一個線程池sExecutor(ThreadPoolExecutor類,是Java提供的多線程機制之一。)
ThreadPoolExecutor:
  1. ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,   
  2.     BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)  
        各個參數的意義如下:
        corePoolSize -- 線程池的核心線程數量
        maximumPoolSize -- 線程池的最大線程數量
        keepAliveTime -- 若線程池的線程數數量大於核心線程數量,那么空閑時間超過keepAliveTime的線程將被回收
        unit -- 參數keepAliveTime使用的時間單位
        workerQueue -- 工作任務隊列
        threadFactory -- 用來創建線程池中的線程
        ThreadPoolExecutor的運行機制:每一個工作任務用一個Runnable對象來表示,當我們要把一個工作任務交給這個線程池來執行的時候,就通過調用ThreadPoolExecutor的execute函數來把這個工作任務加入到線程池中去。此時,如果線程池中的線程數量小於corePoolSize,那么就會調用threadFactory接口來創建一個新的線程並且加入到線程池中去,再執行這個工作任務;如果線程池中的線程數量等於corePoolSize,但是工作任務隊列workerQueue未滿,則把這個工作任務加入到工作任務隊列中去等待執行;如果線程池中的線程數量大於corePoolSize,但是小於maximumPoolSize,並且工作任務隊列workerQueue已經滿了,那么就會調用threadFactory接口來創建一個新的線程並且加入到線程池中去,再執行這個工作任務;如果線程池中的線程量已經等於maximumPoolSize了,並且工作任務隊列workerQueue也已經滿了,這個工作任務就被拒絕執行了。
 
  • 創建好了線程池后,再創建一個消息處理器
private   static   final  InternalHandler sHandler =  new  InternalHandler();  
    這行代碼是在應用程序的 主線程中執行的,因此, 這個消息處理器sHandler內部引用的消息循環對象looper是應用程序主線程的消息循環對象
 
  • 創建AsyncTask對象,即執行AsyncTask類的構造函數:
[java]  view plain  copy
 
在CODE上查看代碼片 派生到我的代碼片
  1. public AsyncTask() {  
  2.     mWorker = new WorkerRunnable<Params, Result>() {  
  3.         public Result call() throws Exception {  
  4.             ......  
  5.             return doInBackground(mParams);  
  6.         }  
  7.     };  
  8.   
  9.     mFuture = new FutureTask<Result>(mWorker) {  
  10.         @Override  
  11.         protected void done() {  
  12.             Message message;  
  13.             Result result = null;  
  14.   
  15.             try {  
  16.                 result = get();  
  17.             } catch (InterruptedException e) {  
  18.                 android.util.Log.w(LOG_TAG, e);  
  19.             } catch (ExecutionException e) {  
  20.                 throw new RuntimeException("An error occured while executing doInBackground()",  
  21.                     e.getCause());  
  22.             } catch (CancellationException e) {  
  23.                 message = sHandler.obtainMessage(MESSAGE_POST_CANCEL,  
  24.                     new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));  
  25.                 message.sendToTarget();  
  26.                 return;  
  27.             } catch (Throwable t) {  
  28.                 throw new RuntimeException("An error occured while executing "  
  29.                     + "doInBackground()", t);  
  30.             }  
  31.   
  32.             message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
  33.                 new AsyncTaskResult<Result>(AsyncTask.this, result));  
  34.             message.sendToTarget();  
  35.         }  
  36.     };  
  37. }  
 
1)      WorkerRunnable對象mWorker:WorkerRunnable類實現了Callable接口,它的內部成員變量mParams用於保存從AsyncTask對象的execute函數傳進來的參數列表:
  1. private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {  
  2.     Params[] mParams;  
  3. }  
 
2)      FutureTask對象mFuture:實現了Runnable接口,可以作為一個工作任務通過調用AsyncTask類的execute函數添加到sExecuto線程池中去:
  1. public final AsyncTask<Params, Progress, Result> execute(Params... params) {  
  2.     ......  
  3.   
  4.     mWorker.mParams = params;  
  5.     sExecutor.execute(mFuture);  
  6.   
  7.     return this;  
  8. }  
    當mFuture加入到線程池中執行時,它調用的是mWorker對象的call函數,  在call函數里面,會調用AsyncTask類的doInBackground函數來執行真正的任務
  1. mWorker = new WorkerRunnable<Params, Result>() {  
  2.     public Result call() throws Exception {  
  3.            ......  
  4.            return doInBackground(mParams);  
  5.         }  
  6. };  
 
當mWorker執行call完成 工作任務 的時候,mFuture對象中的done函數就會被被調用,根據任務的完成狀況,執行相應的操作:例如,如果是因為異常而完成時,就會拋異常;如果是正常完成,就會把任務執行結果封裝成一個AsyncTaskResult對象:
  1. private static class AsyncTaskResult<Data> {  
  2.     final AsyncTask mTask;  
  3.     final Data[] mData;  
  4.   
  5.     AsyncTaskResult(AsyncTask task, Data... data) {  
  6.         mTask = task;  
  7.         mData = data;  
  8.     }  
  9. }  
    其中,成員變量mData保存的是任務執行結果,mTask指向前面創建的AsyncTask對象。
    后把這個AsyncTaskResult對象封裝成一個消息,並且 通過消息處理器sHandler( InternalHandler類 )加入到應用程序主線程的消息隊列中
  1. message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
  2.     new AsyncTaskResult<Result>(AsyncTask.this, result));  
  3. message.sendToTarget();  
     這個消息最終就會在 InternalHandler類的handleMessage函數中處理(主線程中):
  1. private static class InternalHandler extends Handler {  
  2.     @SuppressWarnings({"unchecked""RawUseOfParameterizedType"})  
  3.     @Override  
  4.     public void handleMessage(Message msg) {  
  5.         AsyncTaskResult result = (AsyncTaskResult) msg.obj;  
  6.         switch (msg.what) {  
  7.         case MESSAGE_POST_RESULT:  
  8.             // There is only one result  
  9.             result.mTask.finish(result.mData[0]);  
  10.             break;  
  11.         ......  
  12.         }  
  13.     }  
  14. }  
在這個函數里面,最終會調用前面創建的這個AsyncTask對象的finish函數來進一步處理(這個函數是在應用程序的主線程中執行的,因此,它可以操作應用程序的界面):
  1. private void finish(Result result) {  
  2.        ......  
  3.        onPostExecute(result);  
  4.        ......  
  5. }  
 
  • 在任務執行的過程當中,即執行doInBackground函數時候,可能通過調用publishProgress函數來將中間結果封裝成一個消息發送到應用程序主線程中的消息隊列中去,這個消息最終也是由InternalHandler類的handleMessage函數來處理的(這個函數是在應用程序的主線程中執行的,因此,它和前面的onPostExecute函數一樣,可以操作應用程序的界面):
  1. protected final void publishProgress(Progress... values) {  
  2.     sHandler.obtainMessage(MESSAGE_POST_PROGRESS,  
  3.         new AsyncTaskResult<Progress>(this, values)).sendToTarget();  
  4. }  
 

鍵盤(Keyboard)消息處理機制
        在系統啟動的時候,SystemServer會啟動窗口管理服務 WindowManagerService,WindowManagerService在啟動的時候就會通過 系統輸入管理器InputManager來總負責監控鍵盤消息。這些鍵盤消息一般都是分發給當前激活的Activity窗口來處理的,因此, 當前激活的Activity窗口在創建的時候,會到WindowManagerService中去注冊一個接收鍵盤消息的通道,表明它要處理鍵盤消息,而 當InputManager監控到有鍵盤消息時,就會分給給它處理當當前激活的Activity窗口不再處於激活狀態時,它也會到WindowManagerService中去反注冊之前的鍵盤消息接收通道,這樣,InputManager就不會再把鍵盤消息分發給它來處理
 
InputManager的初始化工作
        A. 在Java層中的WindowManagerService中創建了一個InputManager對象,由它來負責管理Android應用程序框架層的鍵盤消息處理;
        B. 在C++層也相應地創建一個InputManager本地對象來負責監控鍵盤事件;
        C. 在C++層中的InputManager對象中,分別創建了一個InputReader對象和一個InputDispatcher對象,前者負責讀取系統中的鍵盤消息,后者負責把鍵盤消息分發出去;
        D. InputReader對象和一個InputDispatcher對象分別是通過InputReaderThread線程實例和InputDispatcherThread線程實例來實現鍵盤消息的讀取和分發的。
          在Looper類中,會創建一個管道,當調用Looper類的pollOnce函數時,如果管道中沒有內容可讀,那么當前線程就會進入到空閑等待狀態;當有鍵盤事件發生時,InputReader就會往這個管道中寫入新的內容,這樣就會喚醒前面正在等待鍵盤事件發生的線程。
 
應用程序注冊鍵盤消息接收通道的過程
當InputManager監控到有鍵盤消息時,就會先找到當前被激活的窗口,然后找到其在InputManager中對應的鍵盤消息接收通道,通過這個通道在InputManager中的一端來通知在應用程序消息循環中的另一端,就把鍵盤消息分發給當前激活的Activity窗口了。
        A. 即將會被激活的Activity窗口,會通知InputManager,它是當前激活的窗口,因此,一旦發生鍵盤事件的時候,InputManager就把這個鍵盤事件拋給這個Activity處理;
        B. 應用程序會為這個Activity窗口和InputManager之間創建一個鍵盤消息接收通道,這個通道的一端由一個Server端的InputChannel構成,另一端由Client端的InputChannel構成,Server端的InputChannel注冊在由InputManager所管理的InputDispatcher中,而Client端的InputChannel注冊在由應用程序主線程的消息循環對象Looper中;
        C. 注冊在InputDispatcher中的InputChannel由一個反向管道的讀端和一個前向管道的寫端組成,而注冊在應用程序主線程的消息循環對象Looper中的InputChannel由這個前向管道的讀端和反向管道的寫端組成,這種交叉結構使得當有鍵盤事件發生時,InputDispatcher可以把這個事件通知給應用程序。
 
InputManager分發鍵盤消息給應用程序的過程
        A. 鍵盤事件發生,InputManager中的InputReader被喚醒,此前InputReader睡眠在/dev/input/event0這個設備文件上;
        B. InputReader被喚醒后,它接着喚醒InputManager中的InputDispatcher,此前InputDispatcher睡眠在InputManager所運行的線程中的Looper對象里面的管道的讀端上;
        C. InputDispatcher被喚醒后,它接着喚醒應用程序的主線程來處理這個鍵盤事件,此前應用程序的主線程睡眠在Client端InputChannel中的前向管道的讀端上;
        D. 應用程序處理處理鍵盤事件之后,它接着喚醒InputDispatcher來執行善后工作,此前InputDispatcher睡眠在Server端InputChannel的反向管道的讀端上,注意這里與第二個線索處的區別。
 
應用程序注銷鍵盤消息接收通道的過程
        當Activity窗口創建時,它會向InputManager注冊鍵盤消息接收通道,而當Activity窗口銷毀時,它就會向InputManager注銷前面注冊的鍵盤消息接收通道了。
        當我們按下鍵盤上的Back鍵時,當前激活的Activity窗口就會被失去焦點,但是這時候它還沒有被銷毀,它的狀態被設置為Stopped;當新的Activity窗口即將要顯示時,它會通知WindowManagerService,這時候WindowManagerService就會處理當前處理Stopped狀態的Activity窗口了,要執行的操作就是銷毀它們了,在銷毀的時候,就會注銷它們之前所注冊的鍵盤消息接收通道。
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM