1. 程式人生 > >《Android開發藝術探索》筆記

《Android開發藝術探索》筆記

9 四大元件的工作過程

本章的意義在於加深對四大元件工作方式的認識,有助於加深對Android整體的體系結構的認識。很多情況下,只有對Android的體系結構有一定認識,在實際開發中才能寫出優秀的程式碼。 讀者對四大元件的工作過程有一個感性的認識並且能夠給予上層開發一些指導意義。

9.1 四大元件的執行狀態

Android的四大元件除了BroadcastReceiver以外,都需要在AndroidManifest檔案註冊,BroadcastReceiver可以通過程式碼註冊。呼叫方式上,除了ContentProvider以外的三種元件都需要藉助intent。

Activity


是一種展示型元件,用於向用戶直接地展示一個介面,並且可以接收使用者的輸入資訊從而進行互動,扮演的是一個前臺介面的角色。Activity的啟動由intent觸發,有隱式和顯式兩種方式。一個Activity可以有特定的啟動模式,finish方法結束Activity執行。

Service
是一種計算型元件,在後臺執行一系列計算任務。它本身還是執行在主執行緒中的,所以耗時的邏輯仍需要單獨的執行緒去完成。Activity只有一種狀態:啟動狀態。而service有兩種:啟動狀態和繫結狀態。當service處於繫結狀態時,外界可以很方便的和service進行通訊,而在啟動狀態中是不可與外界通訊的。Service可以停止, 需要靈活採用stopService和unBindService

BroadcastReceiver
是一種訊息型元件,用於在不同的元件乃至不同的應用之間傳遞消
息。

  • 靜態註冊
    在清單檔案中進行註冊廣播, 這種廣播在應用安裝時會被系統解析, 此種形式的廣播不需要應用啟動就可以接收到相應的廣播.
  • 動態註冊
    需要通過Context.registerReceiver()來實現, 並在不需要的時候通過Context.unRegisterReceiver()來解除廣播. 此種形態的廣播要應用啟動才能註冊和接收廣播. 在實際開發中通過Context的一系列的send方法來發送廣播, 被髮送的廣播會被系統傳送給感興趣的廣播接收者, 傳送和接收的過程的匹配是通過廣播接收者的<intent-filter>
    來描述的.可以實現低耦合的觀察者模式, 觀察者和被觀察者之間可以沒有任何耦合. 但廣播不適合來做耗時操作.

ContentProvider
是一種資料共享型元件,用於向其他元件乃至其他應用共享資料。在它內部維持著一份資料集合, 這個資料集合既可以通過資料庫來實現, 也可以採用其他任何型別來實現, 例如list或者map. ContentProvider對資料集合的具體實現並沒有任何要求.要注意處理好內部的insert, delete, update, query方法的執行緒同步, 因為這幾個方法是在Binder執行緒池被呼叫.

9.2 Activity的工作過程

  1. Activity的所有 startActivity 過載方法最終都會呼叫 startActivityForResult 。
  2. 呼叫 mInstrumentation.execStartActivity.execStartActivity() 方法。
  3. 程式碼中啟動Activity的真正實現是由ActivityManagerNative.getDefault().startActivity()方法完成的. ActivityManagerService簡稱AMS. AMS繼承自ActivityManagerNative(), 而ActivityManagerNative()繼承自Binder並實現了IActivityManager這個Binder介面, 因此AMS也是一個Binder, 它是IActivityManager的具體實現.ActivityManagerNative.getDefault()本質是一個IActivityManager型別的Binder物件, 因此具體實現是AMS.
  4. 在ActivityManagerNative中, AMS這個Binder物件採用單例模式對外提供, Singleton是一個單例封裝類. 第一次呼叫它的get()方法時會通過create方法來初始化AMS這個Binder物件, 在後續呼叫中會返回這個物件.
  5. AMS的startActivity()過程
    1. checkStartActivityResult () 方法檢查啟動Activity的結果( 包括檢查有無在
      manifest註冊)
    2. Activity啟動過程經過兩次轉移, 最後又轉移到了mStackSupervisor.startActivityMayWait()這個方法, 所屬類為ActivityStackSupervisor. 在startActivityMayWait()內部又呼叫了startActivityLocked()這裡會返回結果碼就是之前checkStartActivityResult()用到的。
    3. 方法最後會呼叫startActivityUncheckedLocked(), 然後又呼叫了ActivityStack#resumeTopActivityLocked(). 這個時候啟動過程已經從ActivityStackSupervisor轉移到了ActivityStack類中.
  6. 在最後的 ActivityStackSupervisor. realStartActivityLocked() 中,呼叫了 app.thread.scheduleLaunchActivity() 方法。 這個app.thread是ApplicationThread 型別,繼承於 IApplicationThread 是一個Binder類,內部是各種啟動/停止 Service/Activity的介面。
  7. 在ApplicationThread中, scheduleLaunchActivity() 用來啟動Activity,裡面的實現就是傳送一個Activity的訊息( 封裝成 從ActivityClientRecord 物件) 交給Handler處理。這個Handler有一個簡潔的名字 H 。
  8. 在H的 handleMessage() 方法裡,通過 handleLaunchActivity() 方法完成Activity物件的建立和啟動,並且ActivityThread通過handleResumeActivity()方法來呼叫被啟動的onResume()這一生命週期方法。PerformLaunchActivity()主要完成了如下幾件事:
    1. 從ActivityClientRecord物件中獲取待啟動的Activity元件資訊
    2. 通過 Instrumentation 的 newActivity 方法使用類載入器建立Activity物件
    3. 通過 LoadedApk 的makeApplication方法嘗試建立Application物件,通過類載入器實現( 如果Application已經建立過了就不會再建立)
    4. 建立 ContextImpl 物件並通過Activity的 attach 方法完成一些重要資料的初始化(ContextImpl是一個很重要的資料結構, 它是Context的具體實現, Context中的大部分邏輯都是由ContentImpl來完成的. ContextImpl是通過Activity的attach()方法來和Activity建立關聯的,除此之外, 在attach()中Activity還會完成Window的建立並建立自己和Window的關聯, 這樣當Window接收到外部輸入事件收就可以將事件傳遞給Activity.)
    5. 通過 mInstrumentation.callActivityOnCreate(activity, r.state) 方法呼叫Activity的 onCreate 方法

9.3 Service的工作過程

  1. 啟動狀態:執行後臺計算
  2. 繫結狀態:用於其他元件與Service互動

兩種狀態是可以共存的

9.3.1 Service的啟動過程

  1. Service的啟動從 ContextWrapper 的 startService 開始
  2. 在ContextWrapper中,大部分操作通過一個 ContextImpl 物件mBase實現
  3. 在ContextImpl中, mBase.startService() 會呼叫 startServiceCommon 方法,而
    startServiceCommon方法又會通過 ActivityManagerNative.getDefault() ( 實際上就是AMS) 這個物件來啟動一個服務。
  4. AMS會通過一個 ActiveService 物件( 輔助AMS進行Service管理的類,包括Service的啟動,繫結和停止等) mServices來完成啟動Service: mServices.startServiceLocked() 。
  5. 在mServices.startServiceLocked()最後會呼叫 startServiceInnerLocked() 方法:將Service的資訊包裝成一個 ServiceRecord 物件,ServiceRecord一直貫穿著整個Service的啟動過程。通過 bringUpServiceLocked() 方法來處理,bringUpServiceLocked()又呼叫了 realStartServiceLocked() 方法,這才真正地去啟動一個Service了。
  6. realStartServiceLocked()方法的工作如下:
    1. app.thread.scheduleCreateService() 來建立Service並呼叫其onCreate()生命週期方法
    2. sendServiceArgsLocked() 呼叫其他生命週期方法,如onStartCommand()
    3. app.thread物件是 IApplicationThread 型別,實際上就是一個Binder,具體實現是ApplicationThread繼承ApplictionThreadNative
  7. 具體看Application對Service的啟動過程app.thread.scheduleCreateService():通過 sendMessage(H.CREATE_SERVICE , s) ,這個過程和Activity啟動過程類似,同時通過傳送訊息給Handler H來完成的。
  8. H會接受這個CREATE_SERVICE訊息並通過ActivityThread的 handleCreateService() 來完成Service的最終啟動。
  9. handleCreateService()完成了以下工作:
    1. 通過ClassLoader建立Service物件
    2. 建立Service內部的Context物件
    3. 建立Application,並呼叫其onCreate()( 只會有一次)
    4. 通過 service.attach() 方法建立Service與context的聯絡( 與Activity類似)
    5. 呼叫service的 onCreate() 生命週期方法,至此,Service已經啟動了
    6. 將Service物件儲存到ActivityThread的一個ArrayMap中

9.3.2 Service的繫結過程


和service的啟動過程類似的:

  1. Service的繫結是從 ContextWrapper 的 bindService 開始
  2. 在ContextWrapper中,交給 ContextImpl 物件 mBase.bindService()
  3. 最終會呼叫ContextImpl的 bindServiceCommon 方法,這個方法完成兩件事:
    • 將客戶端的ServiceConnection轉化成 ServiceDispatcher.InnerConnection 物件。ServiceDispatcher連線ServiceConnection和InnerConnection。這個過程通過 LoadedApk 的 getServiceDispatcher 方法來實現,將客戶端的ServiceConnection和ServiceDispatcher的對映關係存在一個ArrayMap中。
    • 通過AMS來完成Service的具體繫結過程 ActivityManagerNative.getDefault().bindService()
  4. AMS中,bindService()方法再呼叫 bindServiceLocked() ,bindServiceLocked()再呼叫 bringUpServiceLocked() ,bringUpServiceLocked()又會呼叫 realStartServiceLocked() 。
  5. AMS的realStartServiceLocked()會呼叫 ActiveServices 的requrestServiceBindingLocked() 方法,最終是呼叫了ServiceRecord物件r的 app.thread.scheduleBindService() 方法。
  6. ApplicationThread的一系列以schedule開頭的方法,內部都通過Handler H來中轉:scheduleBindService()內部也是通過 sendMessage(H.BIND_SERVICE , s)
  7. 在H內部接收到BIND_SERVICE這類訊息時就交給 ActivityThread 的handleBindService() 方法處理:
    1. 根據Servcie的token取出Service物件
    2. 呼叫Service的 onBind() 方法,至此,Service就處於繫結狀態了。
    3. 這時客戶端還不知道已經成功連線Service,需要呼叫客戶端的binder物件來呼叫客戶端的ServiceConnection中的 onServiceConnected() 方法,這個通過 ActivityManagerNative.getDefault().publishService() 進行。ActivityManagerNative.getDefault()就是AMS。
  8. AMS的publishService()交給ActivityService物件 mServices 的 publishServiceLocked() 來處理,核心程式碼就一句話 c.conn.connected(r.name,service) 。物件c的型別是 ConnectionRecord ,c.conn就是ServiceDispatcher.InnerConnection物件,service就是Service的onBind方法返回的Binder物件。
  9. c.conn.connected(r.name,service)內部實現是交給了mActivityThread.post(new RunnConnection(name ,service,0)); 實現。ServiceDispatcher的mActivityThread是一個Handler,其實就是ActivityThread中的H。這樣一來RunConnection就經由H的post方法從而執行在主執行緒中,因此客戶端ServiceConnection中的方法是在主執行緒中被回撥的。
  10. RunConnection的定義如下:
    • 繼承Runnable介面, run() 方法的實現也是簡單呼叫了ServiceDispatcher的 doConnected 方法。
    • 由於ServiceDispatcher內部儲存了客戶端的ServiceConntion物件,可以很方便地呼叫ServiceConntion物件的 onServiceConnected 方法。
    • 客戶端的onServiceConnected方法執行後,Service的繫結過程也就完成了。
    • 根據步驟8、9、10service繫結後通過ServiceDispatcher通知客戶端的過程可以說明ServiceDispatcher起著連線ServiceConnection和InnerConnection的作用。 至於Service的停止和解除繫結的過程,系統流程都是類似的。

9.4 BroadcastReceiver的工作過程

簡單回顧一下廣播的使用方法, 首先定義廣播接收者, 只需要繼承BroadcastReceiver並重寫onReceive()方法即可. 定義好了廣播接收者, 還需要註冊廣播接收者, 分為兩種靜態註冊或者動態註冊. 註冊完成之後就可以傳送廣播了.

9.4.1 廣播的註冊過程

  1. 動態註冊的過程是從ContextWrapper#registerReceiver()開始的. 和Activity或者Service一樣. ContextWrapper並沒有做實際的工作, 而是將註冊的過程直接交給了ContextImpl來完成.
  2. ContextImpl#registerReceiver()方法呼叫了本類的registerReceiverInternal()方法.
  3. 系統首先從mPackageInfo獲取到IIntentReceiver物件, 然後再採用跨程序的方式向AMS傳送廣播註冊的請求. 之所以採用IIntentReceiver而不是直接採用BroadcastReceiver, 這是因為上述註冊過程中是一個程序間通訊的過程. 而BroadcastReceiver作為Android中的一個元件是不能直接跨程序傳遞的. 所有需要通過IIntentReceiver來中轉一下.
  4. IIntentReceiver作為一個Binder介面, 它的具體實現是LoadedApk.ReceiverDispatcher.InnerReceiver, ReceiverDispatcher的內部同時儲存了BroadcastReceiver和InnerReceiver, 這樣當接收到廣播的時候, ReceiverDispatcher可以很方便的呼叫BroadcastReceiver#onReceive()方法. 這裡和Service很像有同樣的類, 並且內部類中同樣也是一個Binder介面.
  5. 由於註冊廣播真正實現過程是在AMS中, 因此跟進AMS中, 首先看registerReceiver()方法, 這裡只關心裡面的核心部分. 這段程式碼最終會把遠端的InnerReceiver物件以及IntentFilter物件儲存起來, 這樣整個廣播的註冊就完成了.

9.4.2 廣播的傳送和接收過程


廣播的傳送有幾種:普通廣播、有序廣播和粘性廣播,他們的傳送/接收流程是類似的,因此
只分析普通廣播的實現。

  1. 廣播的傳送和接收, 本質就是一個過程的兩個階段. 廣播的傳送仍然開始於ContextImpl#sendBroadcase()方法, 之所以不是Context, 那是因為Context#sendBroad()是一個抽象方法. 和廣播的註冊過程一樣, ContextWrapper#sendBroadcast()仍然什麼都不做, 只是把事情交給了ContextImpl去處理.
  2. ContextImpl裡面也幾乎什麼都沒有做, 內部直接向AMS發起了一個非同步請求用於傳送廣播.
  3. 呼叫AMS#broadcastIntent()方法,繼續呼叫broadcastIntentLocked()方法。
  4. 在broadcastIntentLocked()內部, 會根據intent-filter查找出匹配的廣播接收者並經過一系列的條件過濾. 最終會將滿足條件的廣播接收者新增到BroadcastQueue中, 接著BroadcastQueue就會將廣播發送給相應廣播接收者.
  5. BroadcastQueue#scheduleBroadcastsLocked()方法內並沒有立即傳送廣播, 而是傳送了一個BROADCAST_INTENT_MSG型別的訊息, BroadcastQueue收到訊息後會呼叫processNextBroadcast()方法。
  6. 無序廣播儲存在mParallelBroadcasts中, 系統會遍歷這個集合並將其中的廣播發送給他們所有的接收者, 具體的傳送過程是通過deliverToRegisteredReceiverLocked()方法實現. deliverToRegisteredReceiverLocked()負責將一個廣播發送給一個特定的接收者, 它的內部呼叫了performReceiverLocked方法來完成具體傳送過程.
  7. performReceiverLocked()方法呼叫的ApplicationThread#scheduleRegisteredReceiver()實現比較簡單, 它通過InnerReceiver來實現廣播的接收
  8. scheduleRegisteredReceiver()方法中,receiver.performReceive()中的receiver對應著IIntentReceiver型別的介面. 而具體的實現就是ReceiverDispatcher$InnerReceiver. 這兩個巢狀的內部類是所屬在LoadedApk中的。
  9. 又呼叫了LoadedApk$ReceiverDispatcher#performReceive()的方法.在performReceiver()這個方法中, 會建立一個Args物件並通過mActivityThread的post方法執行args中的邏輯. 而這些類的本質關係就是:
    • Args: 實現類Runnable
    • mActivityThread: 是一個Handler, 就是ActivityThread中的mH. mH就是ActivityThread$H. 這個內部類H以前說過.
  10. 實現Runnable介面的Args中BroadcastReceiver#onReceive()方法被執行了, 也就是說應用已經接收到了廣播, 同時onReceive()方法是在廣播接收者的主執行緒中被呼叫的.

android 3.1開始就增添了兩個標記為. 分別是FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_EXCLUDE_STOPPED_PACKAGES. 用來控制廣播是否要對處於停止的應用起作用.

  • FLAG_INCLUDE_STOPPED_PACKAGES: 包含停止應用, 廣播會發送給已停止的應用.
  • FLAG_EXCLUDE_STOPPED_PACKAGES: 不包含已停止應用, 廣播不會發送給已停止的應用

在android 3.1開始, 系統就為所有廣播預設添加了FLAG_EXCLUDE_STOPPED_PACKAGES標識。 當這兩個標記共存的時候以FLAG_INCLUDE_STOPPED_PACKAGES(非預設項為主).

應用處於停止分為兩種

  • 應用安裝後未執行
  • 被手動或者其他應用強停

開機廣播同樣受到了這個標誌位的影響. 從Android 3.1開始處於停止狀態的應用同樣無法接受到開機廣播, 而在android 3.1之前處於停止的狀態也是可以接收到開機廣播的.

更多參考1、參考2、參考3

9.5 ContentProvider的工作機制

ContentProvider是一種內容共享型元件, 它通過Binder向其他元件乃至其他應用提供資料. 當ContentProvider所在的程序啟動時, ContentProvider會同時啟動併發布到AMS中. 要注意:這個時候ContentProvider的onCreate()方法是先於Application的onCreate()執行的,這一點在四大元件是少有的現象.

  1. 當一個應用啟動時,入口方法是ActivityThread的main方法,其中建立ActivityThread的例項並建立主執行緒的訊息佇列;
  2. ActivityThread的attach方法中會遠端呼叫ActivityManagerService的attachApplication,並將ApplicationThread提供給AMS,ApplicationThread主要用於ActivityThread和AMS之間的通訊;
  3. ActivityManagerService的attachApplication會呼叫ApplicationThread的bindApplication方法,這個方法會通過H切換到ActivityThread中去執行,即呼叫handleBindApplication方法;
  4. handleBindApplication方法會建立Application物件並載入ContentProvider,注意是先載入ContentProvider,然後呼叫Application的onCreate方法。
  5. ContentProvider啟動後, 外界就可以通過它所提供的增刪改查這四個介面來操作ContentProvider中的資料來源, 這四個方法都是通過Binder來呼叫的, 外界無法直接訪問ContentProvider, 它只能通過AMS根據URI來獲取到對應的ContentProvider的Binder介面IContentProvider, 然後再通過IContentProvider來訪問ContentProvider中的資料來源.

ContentProvider的android:multiprocess屬性決定它是否是單例項,預設值是false,也就是預設是單例項。當設定為true時,每個呼叫者的程序中都存在一個ContentProvider物件。

當呼叫ContentProvider的insert、delete、update、query方法中的任何一個時,如果ContentProvider所在的程序沒有啟動的話,那麼就會觸發ContentProvider的建立,並伴隨著ContentProvider所在程序的啟動。

以query呼叫為例

  1. 首先會獲取IContentProvider物件, 不管是通過acquireUnstableProvider()方法還是直接通過acquireProvider()方法, 他們的本質都是一樣的, 最終都是通過acquireProvider方法來獲取ContentProvider.
  2. ApplicationContentResolver#acquireProvider()方法並沒有處理任何邏輯, 它直接呼叫了ActivityThread#acquireProvider()
  3. 從ActivityThread中查詢是否已經存在了ContentProvider了, 如果存在那麼就直接返回. ActivityThread中通過mProviderMap來儲存已經啟動的ContentProvider物件, 這個集合的儲存型別ArrayMap mProviderMap. 如果目前ContentProvider沒有啟動, 那麼就傳送一個程序間請求給AMS讓其啟動專案目標ContentProvider, 最後再通過installProvider()方法來修改引用計數.
  4. AMS是如何啟動ContentProvider的呢?首先會啟動ContentProvider所在的程序, 然後再啟動ContentProvider. 啟動程序是由AMS#startProcessLocked()方法來完成, 其內部主要是通過Process#start()方法來完成一個新程序的啟動, 新程序啟動後其入口方法為ActivityThread#main()方法。
  5. ActivityThread#main()是一個靜態方法, 在它的內部首先會建立ActivityThread例項並呼叫attach()方法來進行一系列初始化, 接著就開始進行訊息迴圈. ActivityThread#attach()方法會將Application物件通過AMS#attachApplication方法跨程序傳遞給AMS, 最終AMS會完成ContentProvider的建立過程.
  6. AMS#attachApplication()方法呼叫了attachApplication(), 然後又呼叫了ApplicationThread#bindApplication(), 這個過程也屬於程序通訊.bindApplication()方法會發送一個BIND_APPLICATION型別的訊息給mH, 這是一個Handler, 它收到訊息後會呼叫ActivityThread#handleBindApplication()方法.
  7. ActivityThread#handlerBindApplication()則完成了Application的建立以及ContentProvider 可以分為如下四個步驟:
    1. 建立ContentProvider和Instrumentation
    2. 建立Application物件
    3. 啟動當前程序的ContentProvider並呼叫onCreate()方法. 主要內部實現是installContentProvider()完成了ContentProvider的啟動工作, 首先會遍歷當前程序的ProviderInfo的列表並一一呼叫installProvider()方法來啟動他們, 接著將已經啟動的ContentProvider釋出到AMS中, AMS會把他們儲存在ProviderMap中, 這樣一來外部呼叫者就可以直接從AMS中獲取到ContentProvider. installProvider()內部通過類載入器建立的ContentProvider例項並在方法中呼叫了attachInfo(), 在這內部呼叫了ContentProvider#onCreate()
    4. 呼叫Application#onCreate()

經過了上述的四個步驟, ContentProvider已經啟動成功, 並且其所在的程序的Application也已經成功, 這意味著ContentProvider所在的程序已經完成了整個的啟動過程, 然後其他應用就可以通過AMS來訪問這個ContentProvider了.

當拿到了ContentProvider以後, 就可以通過它所提供的介面方法來訪問它. 這裡要注意: 這裡的ContentProvider並不是原始的ContentProvider. 而是ContentProvider的Binder型別物件IContentProvider, 而IContentProvider的具體實現是ContentProviderNative和ContentProvider.Transport. 後者繼承了前者.

如果還用query方法來解釋流程: 那麼最開始其他應用通過AMS獲取到ContentProvider的Binder物件就是IContentProvider. 而IContentProvider的實際實現者是ContentProvider.Transport. 因此實際上外部應用呼叫的時候本質上會以程序間通訊的方式呼叫ContentProvider.Transport的query()方法。

10 Android的訊息機制

Android的訊息機制主要是指Handler的執行機制。從開發的角度來說,Handler是Android訊息機制的上層介面。Handler的執行需要底層的 MessageQueue 和 Looper 的支撐。

  • MessageQueue是一個訊息佇列,內部儲存了一組訊息,以佇列的形式對外提供插入和刪除的工作,內部採用單鏈表的資料結構來儲存訊息列表。
  • Lopper會以無限迴圈的形式去查詢是否有新訊息,如果有就處理訊息,否則就一直等待著。
  • ThreadLocal並不是執行緒,它的作用是在每個執行緒中儲存資料。Handler通過ThreadLocal可以獲取每個執行緒中的Looper。
  • 執行緒是預設沒有Looper的,使用Handler就必須為執行緒建立Looper。我們經常提到的主執行緒,也叫UI執行緒,它就是ActivityThread,被建立時就會初始化Looper。

10.1 Android的訊息機制概述

  • Handler的主要作用是將某個任務切換到Handler所在的執行緒中去執行。為什麼Android要提供這個功能呢?這是因為Android規定訪問UI只能通過主執行緒,如果子執行緒訪問UI,程式可能會導致ANR。那我們耗時操作在子執行緒執行完畢後,我們需要將一些更新UI的操作切換到主執行緒當中去。所以系統就提供了Handler。
  • 系統為什麼不允許在子執行緒中去訪問UI呢? 因為Android的UI控制元件不是執行緒安全的,多執行緒併發訪問可能會導致UI控制元件處於不可預期的狀態,為什麼不加鎖?因為加鎖機制會讓UI訪問邏輯變得複雜;其次鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些執行緒的執行。所以Android採用了高效的單執行緒模型來處理UI操作。
  • Handler建立時會採用當前執行緒的Looper來構建內部的訊息迴圈系統,如果當前執行緒沒有Looper就會報錯。Handler可以通過post方法傳送一個Runnable到訊息佇列中,也可以通過send方法傳送一個訊息到訊息佇列中,其實post方法最終也是通過send方法來完成。
  • MessageQueue的enqueueMessage方法最終將這個訊息放到訊息佇列中,當Looper發現有新訊息到來時,處理這個訊息,最終訊息中的Runnable或者Handler的handleMessage方法就會被呼叫,注意Looper是執行Handler所在的執行緒中的,這樣一來業務邏輯就切換到了Handler所在的執行緒中去執行了。

10.2 Android的訊息機制分析

10.2.1 ThreadLocal的工作原理

ThreadLocal是一個執行緒內部的資料儲存類,通過它可以在指定執行緒中儲存資料,資料儲存後,只有在指定執行緒中可以獲取到儲存的資料,對於其他執行緒來說無法獲得資料。

在某些特殊的場景下,ThreadLocal可以輕鬆實現一些很複雜的功能。Looper、ActivityThread以及AMS都用到了ThreadLocal。當某些資料是以執行緒為作用域並且不同執行緒具有不同的資料副本的時候,就可以考慮採用ThreadLocal。

對於Handler來說,它需要獲取當前執行緒的Looper,而Looper的作用於就是執行緒並且不同的執行緒具有不同的Looper,通過ThreadLocal可以輕鬆實現執行緒中的存取。

ThreadLocal的另一個使用場景是可以讓監聽器作為執行緒內的全域性物件而存在,線上程內部只要通過get方法就可以獲取到監聽器。如果不採用ThreadLocal,只能採用函式引數呼叫和靜態變數的方式。而第一種方式在呼叫棧很深時很糟糕,第二種方式不具有擴充套件性,比如同時多個執行緒執行。

雖然在不同執行緒訪問同一個ThreadLocal物件,但是獲得的值卻是不同的。不同執行緒訪問同一個ThreadLoacl的get方法,ThreadLocal的get方法會從各自的執行緒中取出一個數組,然後再從陣列中根據當前ThreadLocal的索引去查詢對應的Value值。
ThreadLocal的set方法:

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    //通過values方法獲取當前執行緒中的ThreadLoacl資料——localValues
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    } 
    values.put(this, value);
}
  1. 在 localValues 內部有一個數組: private Object[] table ,ThreadLocal的值就存在這個陣列中。
  2. ThreadLocal的值在table陣列中的儲存位置總是ThreadLocal的reference欄位所標識的物件的下一個位置。

ThreadLocal的get方法:

    public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);//找到localValues物件
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {//找到ThreadLocal的reference物件在table陣列中的位置
            return (T) table[index + 1];//reference欄位所標識的物件的下一個位置就是ThreadLocal的值
        }
    } else {
        values = initializeValues(currentThread);
    } 
    return (T) values.getAfterMiss(this);
}

從ThreadLocal的set/get方法可以看出,它們所操作的物件都是當前執行緒的localValues物件的table陣列,因此在不同執行緒中訪問同一個ThreadLocal的set/get方法,它們ThreadLocal的讀/寫操作僅限於各自執行緒的內部。理解ThreadLocal的實現方式有助於理解Looper的工作原理。

10.2.2 訊息佇列的工作原理

訊息佇列指的是MessageQueue,主要包含兩個操作:插入和讀取。讀取操作本身會伴隨著刪除操作。

MessageQueue內部通過一個單鏈表的資料結構來維護訊息列表,這種資料結構在插入和刪除上的效能比較有優勢。

插入和讀取對應的方法分別是:enqueueMessage和next方法。
enqueueMessage()的原始碼實現主要操作就是單鏈表的插入操作
next()的原始碼實現也是從單鏈表中取出一個元素的操作,next()方法是一個無線迴圈的方法,如果訊息佇列中沒有訊息,那麼next方法會一直阻塞在這裡。當有新訊息到來時,next()方法會返回這條訊息並將其從單鏈表中移除。

10.2.3 Looper的工作原理

Looper在Android的訊息機制中扮演著訊息迴圈的角色,具體來說就是它會不停地從MessageQueue中檢視是否有新訊息,如果有新訊息就會立即處理,否則就一直阻塞在那裡。

通過Looper.prepare()方法即可為當前執行緒建立一個Looper,再通過Looper.loop()開啟訊息迴圈。prepareMainLooper()方法主要給主執行緒也就是ActivityThread建立Looper使用的,本質也是通過prepare方法實現的。

Looper提供quit和quitSafely來退出一個Looper,區別在於quit會直接退出Looper,而quitSafely會把訊息佇列中已有的訊息處理完畢後才安全地退出。 Looper退出後,這時候通過Handler傳送的訊息會失敗,Handler的send方法會返回false。
在子執行緒中,如果手動為其建立了Looper,在所有事情做完後,應該呼叫Looper的quit方法來終止訊息迴圈,否則這個子執行緒就會一直處於等待狀態;而如果退出了Looper以後,這個執行緒就會立刻終止,因此建議不需要的時候終止Looper。
loop()方法會呼叫MessageQueue的next()方法來獲取新訊息,而next是是一個阻塞操作,但沒有資訊時,next方法會一直阻塞在那裡,這也導致loop方法一直阻塞在那裡。如果MessageQueue的next方法返回了新訊息,Looper就會處理這條訊息:msg.target.dispatchMessage(msg),這裡的msg.target是傳送這條訊息的Handler物件,這樣Handler傳送的訊息最終又交給Handler來處理了。

10.2.4 Handler的工作原理

Handler的工作主要包含訊息的傳送和接收過程。通過post的一系列方法和send的一系列方法來實現。

Handler傳送過程僅僅是向訊息佇列中插入了一條訊息。MessageQueue的next方法就會返回這條訊息給Looper,Looper拿到這條訊息就開始處理,最終訊息會交給Handler的dispatchMessage()來處理,這時Handler就進入了處理訊息的階段。

handler處理訊息的過程

Handler的構造方法

  1. 派生Handler的子類
  2. 通過Callback
    Handler handler = new Handler(callback)
    其中,callback介面定義如下
     public interface Callback{
         public boolean handleMessage(Message msg);
     }
    
  3. 通過Looper
     public Handler(Looper looper){
     this(looper,null,false);
     }
    

10.3 主執行緒的訊息迴圈