Handler+Binder;看完這一篇就理解了
前言
在說Handler
和binder
之前,我們先看看關于Framework
都有什么內容

1.
Framework
通信(Binder
,Handler
,Livedata
)
2.Framework
底層服務(AMS
,PMS
,WMS
)
3.Framework
系統(tǒng)資源(ServiceManager
,Contxt
,Resource
)
4.Framework
事件機制
5.Framework UI
機制(UI
繪制原理,UI
自定義)
今天先來分析一下Handler
和Binder
相關視頻
Framework大全V:maniu0
一丶Handler
Handler、Message、MessageQueue、Looper;
以下為零散的記錄,最后有總結;?內存泄露的本質:
長生命周期對象持有短生命周期對象,導致短生命周期對象銷毀不掉;
持有鏈:
線程>>Looper
>>MessageQueue
>>Message
>>Handler
>>Activity
;
Message
對象的變量target
為發(fā)送消息的Handler
;?MessageQueue
?隊列里放Message
;?Looper
對象里實例化MessageQueue;
?一個線程綁定一個Looper
;
為什么要有handler
? 主要目的是要解決線程切換問題,handler
里的Message
機制解決了線程間通信;
為什么有隊列MessageQueue
??MessageQueue
是一個單向鏈表,next()
調用nativePollOnce->lunx
的epoll_wait()
等待實現阻塞時隊列;
在單線程中一次只能執(zhí)行一句代碼
假如發(fā)送了一個大消息A
處理這個大的消息A
但是處理的太慢了
從而導致其他后續(xù)要發(fā)送的消息發(fā)不出去
因為單線程阻塞到了第3步處理那個消息A的地方
隊列的出現解決了"處理消息"阻塞到"發(fā)送消息"的問題;
隊列是生產者消費者模式;
而要使用隊列需要至少兩個線程、和一個死循環(huán);
一個線程負責生產消息;
一個線程消費消息;
死循環(huán)需要取出放入隊列里的消息;
為什么有Looper
?
為了循環(huán)取出隊列里的消息;
一個線程有幾個Looper
,為什么不會有多個?
一個線程一個Looper
,放在ThreadLocalMap
中;
假如Looper
對象由Handler
創(chuàng)建,每創(chuàng)建一個Handler
就有一個Looper
,那么調用Looper.loop()
時開啟死循環(huán);在外邊調用Looper
的地方就會阻塞;

主線程中Looper
的死循環(huán)為什么沒有導致系統(tǒng)卡死?
我們的UI線程主線程其實是
ActivityThread
線程,而一個線程只會有一個Looper
;ActivityThread.java
的main
函數是一個APP進程的入口,如果不卡死,main
函數執(zhí)行完則整個應用進程就會退出;android
是以事件為驅動的操作系統(tǒng),當有事件來時,就去做對應的處理,沒有時就顯示靜態(tài)界面;
獲取當前線程:Thread.currentThread();
ThreadLocalMap
:類似于HashMap;
每個Thread
對象都有一個對應的ThreadLocalMap;

在Looper.prepare()
時,存入Looper
,存Looper
時ThreadLocalMap
的key為ThreadLocal
,value
為Looper
;


內存抖動根本的解決方式是復用;
handler.obtainMessage();
從
Looper
的回收池中取Message
;Message
是一個單向鏈表,Message
不是一個單純的對象,而是一個鏈表集合最大長度固定50個
Linux函數:?
epoll_create
:App注冊進紅黑樹中,拿到一個事件fd的值;?epoll_ctl
:注冊事件類型,監(jiān)聽fd是否改變(Linux中事件都會被寫入文件中,如觸摸屏幕事件會寫入到:dev/input/event0
文件中),fd有改變時喚醒epoll_wait
;?epoll_wait
:有事件時就分發(fā),沒事件就阻塞

總結:?handler
如何做的線程切換的??首先Handler
的使用步驟:
調用
Looper.prepare();
創(chuàng)建
Handler
對象;調用
Looper.Loop()
方法。線程中發(fā)送消息。
在第一步時,創(chuàng)建一個Looper
,并放到當前線程的變量threadLocals
中;threadLocals
是一個map,key為ThreadLocal
對象本身,value為Looper
;在Looper.loop()
時取出;

第二步,用戶在當前線程(可能是子線程)創(chuàng)建Handler
對象;
第三步,Looper.loop()
一直在死循環(huán),Looper.loop()
這句代碼下面的代碼是不會被調用的,調用Looper.loop()
函數時,先從當前線程的map變量中取出Looper
,再從Looper
中拿到隊列MessageQueue
,for循環(huán)中不斷從隊列中取出消息;

第四步,在其他線程調用handelr
發(fā)送消息時,Message
里有個target
,就是發(fā)送消息的handler
;

在Looper.loop()
時,隊列中取到消息時,調用msg.target.dispatchMessage(msg);
其實就是handler對象.dispatchMessage(msg);

所以不論在哪個線程調用發(fā)送消息,都會調用到handler
自己分發(fā)消息;而handler
所處的線程是創(chuàng)建時的“當前線程”,所以處理時也就回到了“當前線程”;實現了線程切換,和線程通信;
Looper
的死循環(huán)為什么不會讓主線程卡死(或ANR)??簡單版:
我們的UI線程主線程其實是
ActivityThread
所在的線程,而一個線程只會有一個Looper
;ActivityThread.java
的main函數是一個APP進程的入口,如果不一直循環(huán),則在main函數執(zhí)行完最后一行代碼后整個應用進程就會退出;android是以事件為驅動的操作系統(tǒng),當有事件來時,就去做對應的處理,沒有時就顯示靜態(tài)界面;
ANR發(fā)生條件是:
Activity
:5 秒。應用在 5 秒內未響應用戶的輸入事件(如按鍵或者觸摸)BroadCastReceiver
?:10 秒。BroadcastReceiver
?未在 10 秒內完成相關的處理Service
:20 秒(均為前臺)。Service
?在20 秒內無法處理完成如果
Handler
收到以上三個相應事件在規(guī)定時間內完成了,則移除消息,不會ANR;若沒完成則會超時處理,彈出ANR對話框;
詳細:
App進程的入口為
ActivityThread.java的main()
函數,注意ActivityThread
不是一個線程;應用的ui主線程實際是調用
ActivityThread.java的main()
函數執(zhí)行時所在的線程,而這個線程對我們不可見,但是這就是主線程;參考:在
ActivityThread.java
的main()
函數中,會調用Looper.prepareMainLooper();
Looper.prepareMainLooper()
會創(chuàng)建一個Looper
并放到當前線程(主線程)的變量threadLocals
中進行綁定,threadLocals
是一個ThreadLocal.ThreadLocalMap;
在
ActivityThread.java
的main()
函數結尾,開啟Looper.loop()
進行死循環(huán),不讓main函數結束,從而讓App進程不會結束;Android系統(tǒng)是以事件作為驅動的操作系統(tǒng),當有事件來時,就去做對應處理,沒有事件時,就顯示當前界面,不做其他多余操作(浪費資源);
在
Looper.loop()
的死循環(huán)中,不僅要取用戶發(fā)的事件,還要取系統(tǒng)內核發(fā)的事件(如屏幕亮度改變等等);在調用
Looper.loop()
時,從MessageQueue.next()
中獲取事件,若沒有則阻塞,有則分發(fā);MessageQueue
其實不是一個隊列,用epoll
機制實現了阻塞;在
Looper.prepareMainLooper()
時,調用c++函數epoll_create()
會將App注冊進epoll機制的紅黑樹中得到fd的值,epoll_ctl()
給每個App注冊事件類型并監(jiān)聽fd值是否改變,fd有改變時喚醒epoll_wait
;epoll_wait()
有事件時就分發(fā),沒事件就阻塞
子線程的Looper
和子線程Looper
有什么不同?
子線程Looper
是可以退出的,主線程不行;
Framework大全V:maniu0
二丶Binder
進程隔離:
內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據 為了保證系統(tǒng)的安全,用戶空間和內核空間是天然隔離的 每個進程有自己的虛擬內存空間,為了安全,每個進程只能操作自己的虛擬內存空間,只有操作系統(tǒng)才有權限操作物理內存空間
為什么要用Binder?
Android系統(tǒng)內核是Linux內核
Linux內核進程通信有:管道、內存共享、
Socket
、File
;對比:

Binder
的一次拷貝發(fā)生在用戶空間拷貝到內核空間;
用戶空間:?App進程運行的內存空間;
內核空間:?系統(tǒng)驅動、和硬件相關的代碼運行的內存空間,也就是進程ID為0的進程運行的空間;
程序局部性原則:?只加載少量代碼;應用沒有運行的代碼放在磁盤中,運行時高速緩沖區(qū)進行加載要運行的代碼;默認一次加載一個頁(4K),若不夠4K就用0補齊;
MMU:內存管理單元;
給CPU提供虛擬地址;
當對變量操作賦值時:
CPU拿著虛擬地址和值給到MMU
MMU用虛擬地址匹配到物理地址,MMU去物理內存中進行賦值;
物理地址:?物理內存的實際地址,并不是磁盤;
虛擬地址:?MMU根據物理內存的實際地址翻譯出的虛擬地址;提供給CPU使用;


頁命中:CPU讀取變量時,MMU在物理內存的頁表中找到了這個地址;
頁未命中:CPU讀取變量時,MMU在物理內存的頁表中沒有找到了這個地址,此時會觸發(fā)MMU去磁盤讀取變量并存到物理內存中;
普通的二次拷貝:
應用A拷貝到服務端:coay_from_user
從服務端拷貝到應用B:coay_to_user
mmap():
在物理內存中開辟一段固定大小的內存空間
將磁盤文件與物理內存進行映射(理解為綁定)
MMU將物理內存地址轉換為虛擬地址給到CPU(虛擬地址映射物理內存)
共享內存進程通信:
進程A調用
mmap()
函數會在內核空間中虛擬地址和一塊同樣大小的物理內存,將兩者進行映射得到一個虛擬地址
進程B調用
mmap()
函數,傳參和步驟1一樣的話,就會得到一個和步驟2相同的虛擬地址進程A和進程B都可以用同一虛擬地址對同一塊映射內存進行操作
進程A和進程B就實現了通信
沒有發(fā)生拷貝,共享一塊內存,不安全
Binder通信原理:
角色:Server端A、Client端B、Binder驅動、內核空間、物理內存
Binder驅動在物理內存中開辟一塊固定大小(1M-8K)的物理內存w,與內核空間的虛擬地址x進行映射得到
A的用戶空間的虛擬地址ax和物理內存w進行映射
此時內核空間虛擬地址x和物理內存w已經進行了映射,物理內存w和Server端A的用戶空間虛擬地址ax進行了映射:也就是 內核空間的虛擬地址x = 物理內存w = Server端A的用戶空間虛擬地址ax
B發(fā)送請求:將數據按照binder協議進行打包給到Binder驅動,Binder驅動調用
coay_from_user()
將數據拷貝到內核空間的虛擬地址x因步驟3中的三塊區(qū)域進行了映射
Server端A就得到了Client端B發(fā)送的數據
通過內存映射關系,只發(fā)生了一次拷貝

Activity
跳轉時,最多攜帶1M-8k(1兆減去8K)的數據量;
真實數據大小為:1M內存-兩頁的請求頭數據=1M-8K;
應用A直接將數據拷貝到應用B的物理內存空間中,數據量不能超過1M-8K;拷貝次數少了一次,少了從服務端拷貝到用戶;
IPC通信機制:
服務注冊
服務發(fā)現
服務調用
以下為簡單的主進程和子進程通信:
1、服務注冊: 緩存中心中有三張表(暫時理解為三個HashMap,Binder用的是native的紅黑樹):
第一種:放key :String - value:類的Class;
第二種:放key :Class的類名 - value:類的方法集合;
第三種:放key :Class的類名 - value:類的對象;
類的方法集合:key-value;
key:方法簽名:“方法名” 有參數時用 “方法名-參數類型-參數類型-參數類型......”;
value: 方法本身;
注冊后,服務若沒被調用則一直處于沉默狀態(tài),不會占用內存,這種情況只是指用戶進程里自己創(chuàng)建的服務,不適用于AMS這種;
2、服務發(fā)現: 當被查詢到時,要被初始化;
客戶端B通過發(fā)送信息到服務端A
服務端解析消息,反序列化
通過反射得到消息里的類名,方法,從注冊時的第一種、第二種表里找到Class,若對象沒初始化則初始化對象,并將對象添加到第三種的表里;
3、服務調用:
使用了動態(tài)代理
客戶端在服務發(fā)現時,拿到對象(其實是代理)
客戶端調用對象方法
代理發(fā)送序列化數據到服務端A
服務端A解析消息,反序列化,得到方法進行處理,得到序列化數據結果
將序列化結果寫入到客戶端進程的容器中;
回調給客戶端
AIDL:?BpBinder:數據發(fā)送角色 BbBinder:數據接收角色

編譯器生成的AIDL的java接口.Stub.proxy.transact()
為數據發(fā)送處;
發(fā)送的數據包含:數據+方法code+方法參數等等;
發(fā)送時調用了Linux的驅動
調用
copy_from_user()
拷貝用戶發(fā)送的數據到內核空間拷貝成功后又進行了一次請求頭的拷貝:
copy_from_user()
也就是把一次的數據分為兩次拷貝
請求頭:包含了目的進程、大小等等參數,這些參數占了8K
編譯器生成的AIDL的java接口.Stub.onTransact()
為數據接收處;
Binder中的IPC機制:
每個App進程啟動時會在內核空間中映射一塊1M-8K的內存
服務端A的服務注冊到
ServiceManager
中:服務注冊客戶端B想要調用服務端A的服務,就去請求
ServiceManager
ServiceManager
去讓服務端A實例化服務:服務發(fā)現返回一個用來發(fā)送數據的對象BpBinder給到客戶端B
客戶端B通過BpBinder發(fā)送數據到服務端A的內核的映射區(qū)域(傳參時客戶端會傳一個reply序列化對象,在底層會將這個地址一層一層往下傳,直至傳到回調客戶端):這里發(fā)生了一次通信
copy_from_user
:服務調用服務端A通過BBBinder得到數據并處理數據
服務端喚醒客戶端等待的線程;將返回結果寫入到客戶端發(fā)送請求時傳的一個reply容器地址中,調用
onTransact
返回;客戶端在
onTransac
中得到數據;通信結束;
ServiceManager
維持了Binder這套通信框架;
Framework大全V:maniu0