網站首頁 編程語言 正文
前言
Binder機制可謂是Android 知識體系里的重中之重,作為偏底層的基礎組件,平時我們很少關注它,而它卻是無處不在,也是Android 面試易考察的點之一。網上很多文章,要么知識點比較陳舊,要么源碼貼一堆,要么沒有成體系地分析,導致讀者一知半解,似是而非。
本篇將從流程上將Binder通信過一遍,盡量多用圖展示。
通過本篇文章,你將了解到:
Binder的作用
進程與Binder驅動如何通信
ServiceManager進程的作用
進程添加服務到ServiceManager的流程
進程從ServiceManager獲取服務的流程
Binder服務端數據接收
Binder 通信全流程圖
1. Binder的作用
先看Linux下進程地址映射關系:
我們知道,對象調用本身就是地址空間的訪問。
如上,進程之間各自訪問各自的內存地址,它們之間無法直接訪問對方的地址,也就是說微信不能直接調用支付寶提供的接口。而內核具有訪問其它進程地址空間的權限,因此微信可以將消息發送給內核,讓內核幫忙轉發給支付寶,這種方式叫做:存儲/轉發方式。
由此衍生的幾種IPC(進程間通信)如:管道、消息隊列、socket等,而Android 上采用了新的機制:
Binder,相比傳統的方式,Binder只需要一次數據拷貝,并且Binder更安全。
Binder機制是Android 里用來做IPC的主要方式。
2. 進程與Binder驅動如何通信
既然得要內核進行消息中轉,那么Binder驅動得運行在內核空間,而事實上也確實如此,Binder驅動加載后在內核空間運行,進程只需要和Binder驅動取得聯系,通過Binder驅動聯系另一個進程,那么一次消息的傳送過程就可以實現了。
內核提供提供一系列的系統調用接口給用戶進程使用,當用戶進程想要訪問內核時,只需要調用對應的接口,此時代碼就會從用戶空間切換到內核空間執行。
常見的系統調用函數如:open/read/write/ioctl/close/mmap/fork 等。
與Binder驅動通信分兩步:
打開Binder驅動:open("/dev/binder", O_RDWR | O_CLOEXEC)
通過ioctl 與Binder驅動進行數據通信:ioctl(mDriverFD, BINDER_WRITE_READ, &bwr)
bwr 為讀寫數據結構
3. ServiceManager進程的作用
Binder Client、Binder Server、ServiceManager關系
為方便起見,ServiceManager簡稱SM。
Binder 設計為C/S架構,C為Client(客戶端),S為Server(服務端),Server端提供接口(服務)給Client端使用,而這個服務是以Binder引用的形式提供的。
由之前的知識可知,C和S是不同的進程,那么C如何拿到S的Binder引用呢?
你可能會說,當然是SM了,S先將Binder引用存放在SM里,當C需要的時候向SM查詢即可。
這么看似乎講得通了,那問題又來了,SM也是一個單獨的進程,那S、C如何與SM進行通信呢?這就陷入了先有雞還是先有蛋的死循環了。
實際上C、S、SM之間都是依靠Binder通信,只是SM作為特殊的Binder(handle=0)提前放入了Binder驅動里,當C、S想要獲取SM的Binder引用,只需要獲取handle=0的Binder即可。
這么說沒有太直觀的印象,我們一步步剖析。
ServiceManager注冊進Binder
SM 注冊進Binder驅動后就會等待來自Binder驅動的消息,這里列出了兩個最常見的處理消息的Case:
其它進程添加服務到SM里
其它進程向SM查詢服務
SM里維護著一個鏈表,鏈表的元素是結構體:
主要記錄的是name和handle字段。
當SM收到添加服務的指令后,從Binder驅動里取出handle和name,并構造結構體插入到鏈表。
當SM收到查詢服務的指令后,從Binder驅動里取出name,并找到鏈表里相同的name,找到后取出handle,最后寫入到Binder驅動。
4. 進程添加服務到ServiceManager的流程
其它進程找到SM
現在SM已經翹首以盼其它進程的請求了,接著來看看如何添加一個服務到SM里。
以Java層添加服務為例,我們選擇振動服務作為切入點分析。
在system_server 進程里構造振動服務(VibratorService繼承自Binder),并添加到SM里。
可以看出,分兩步:
先找到ServiceManager
往ServiceManager里添加服務
getIServiceManager()繼續往下:
BinderInternal.getContextObject() 是native方法,后續流程較多,我們用圖表示。
尋找ServiceManager的過程涉及到Java層和Native層,主要的重點在Native層查找 ServiceManager對應的BpBinder對象,沒有找到的話則創建新的并存入緩存里以備下次直接獲取。
ProcessState里維護了一個單例,每個進程只有一個ProcessState對象,創建ProcessState時候就會去打開Binder驅動,同時會設置Binder線程池里線程個數等其它參數
Native層構造BpBinder(handle=0表示該BpBinder是ServiceManager在客戶端的引用),再構造BinderProxyNativeData持有BpBinder。
構造BinderProxy對象并持有BinderProxyNativeData,也就是間接持有BpBinder
最后構造了ServiceManagerProxy對象,它實現了IServiceManager接口,它的成員變量mRemote指向了BinderProxy
可以看出,獲取ServiceManager的過程并不是真正去獲取ServiceManager的Binder對象,而是獲取它在當前進程的代理:BpBinder
添加服務到ServiceManager
既然找到了SM的Binder代理,接下來看看如何使用它給SM添加服務。
#ServiceManagerNative.ServiceManagerProxy
public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
throws RemoteException {
//構造Parcel
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
data.writeString(name);
//寫入Binder
data.writeStrongBinder(service);
data.writeInt(allowIsolated ? 1 : 0);
data.writeInt(dumpPriority);
//通過BinderProxy發送
mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
reply.recycle();
data.recycle();
}
其中IPCThreadState與線程相關,不同的線程會維護一個單例。
由此可見,最終還是通過BpBinder發送消息,進而發送到Binder驅動。
此時驅動收到的信息包括不限于:
服務的名字
ServiceManager的handle
BBinder對象指針
驅動建立服務handle和BBinder對象指針的映射關系,并將服務的名字和服務的handle傳遞給ServiceManager(通過ServiceManager handle查找)。
ServiceManager拿到消息后建立映射關系,等待其它進程的請求。
至此,進程添加服務到ServiceManager過程已經分析完畢,用圖表示如下:
BBinder作用
Java層傳遞的是Binder對象,如何與Native的BBinder關聯起來呢?
重點在:
Parcel.writeStrongBinder(Binder)
也即是說Server端的Java Binder對象在Native層的代表是BBinder。
Binder驅動記錄了BBinder的地址,當有消息過來時通過找到BBinder對象進而找到Java層的Binder對象,最終調用Binder.onTransact()。
5. 進程從ServiceManager獲取服務的流程
其它進程找到SM
振動服務添加完成后,某些進程想要獲取振動服務進行振動,比如微信收到消息后需要振動用以提示用戶。
接著來看看如何獲取振動服務。
private void vibrate() {
//獲取振動服務
Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
//開始振動
vibrator.vibrate(1000);
}
與添加服務類似,想要獲取服務先要找到SM,找SM的過程上邊分析過了,此處不再細說。
從ServiceManager獲取服務
#ServiceManagerNative.ServiceManagerProxy
public IBinder getService(String name) throws RemoteException {
//構造Parcel
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IServiceManager.descriptor);
//寫入名字
data.writeString(name);
//通過BinderProxy發送
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
return binder;
}
由此可見,最終還是通過BpBinder發送消息,進而發送到Binder驅動。
此時驅動收到的信息包括不限于:
服務的名字
ServiceManager的handle
Binder驅動收到消息后,找到SM,并將服務的名字傳給SM,SM從自己維護的鏈表里找到服務名相同的節點,最終取出該服務的handle,發送給Binder驅動。
用圖表示如下:
對比添加服務流程和獲取服務流程,兩者前半部分都很相似,都是先拿到SM的BpBinder引用,然后寫入驅動,最后由SM進程處理。只是對于獲取服務流程來說,還需要將查詢的結果(handle)寫入驅動返回給調用方(對應圖上紅色部分)。
到這,大家可能會有疑惑了:"handle是整形值,而微信獲取的振動服務是一個Binder對象,這兩者是怎么結合起來的呢?"
handle轉換為Binder對象
handle表示的即是Binder服務端在客戶端的索引句柄,只要客戶端拿到了handle,它就能通過Binder驅動調用到服務端。
mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
IBinder binder = reply.readStrongBinder();
再回過頭看看獲取服務的代碼,當微信進程將查詢命令發給Binder驅動后就等待驅動回復的結果,SM查詢到結果后將handle寫入驅動,而后微信進程從驅動將結果讀出并將結果存入reply字段。
最后通過reply拿到Binder引用,也就是說重點在reply.readStrongBinder()方法。
直接看圖:
如上,通過驅動返回的handle構造BpBinder,最終封裝為Java層的BinderProxy。
至此,獲取服務流程就結束了,用圖展示簡化的流程
6. Binder服務端數據接收
微信進程拿到振動服務(在system_server進程里)的Binder(BinderProxy)后,就可以調用振動方法了,而后指令發送給驅動,驅動通過振動服務的handle找到對應的服務BBinder指針,從而調用服務的接收方法。
微信進程發送指令給Binder驅動前面已經分析過,重點來看看system_server進程是如何接收并處理指令的。
system_server進程啟動的時候就會開啟Binder線程池,并等待驅動數據到來。
當system_server進程添加振動服務到SM時,會將Java層的Binder轉為Native層的BBinder,并將BBinder對象指針寫入Binder驅動。
當微信進程調用system_server接口時:
微信進程調用BpBinder.transact()將handle和數據寫入Binder驅動
Binder驅動根據handle找到system_server進程
system_server進程從驅動拿到數據,并取出BBinder指針,最終調用到system_server進程Java層的Binder.onTransact()
如此一來,微信成功調用了振動服務,也就是說一次Client到Server端的通信就完成了。
7. Binder 通信全流程圖
縱觀Binder機制設計,最核心的點是handle。
通過handle構造Client端的BpBinder(Native層),與此對應的是Java層的BinderProxy
通過handle,驅動找到Server端進程,進而調用BBinder(Native層),與此對應的是Java層的Binder
通過handle的一系列中轉,Client.transact()成功調用了Server.onTransact(),一次Binder通信就過程就完成了
最后,用一張圖總結Binder機制的全過程:
以上就是整個Binder機制的梳理過程,此間省略了Binder驅動里的映射邏輯,可以將Binder驅動當做一個黑盒,而更重要的是Binder客戶端和服務端是如何進行映射的。
Binder流程比較繞,尤其是IPCThreadStsate作為客戶端的發送和服務端的數據接收的實體,需要區分不同的場景。
當然,jni基礎知識必不可少。
本文基于Android 10
原文鏈接:https://juejin.cn/post/7157710923547803655
相關推薦
- 2022-02-13 group?by用法詳解_oracle
- 2023-01-17 pytorch?geometric的GNN、GCN的節點分類方式_python
- 2022-03-25 如何使用postman(postman的使用方法詳解)
- 2022-05-04 基于Python中的turtle繪畫星星和星空_python
- 2022-05-11 解決 IntelliJ IDEA 中 .propertise 文件保存后中文亂碼
- 2022-06-06 解決Unity無限滾動復用列表的問題_C#教程
- 2022-06-08 Android即時通訊設計(騰訊IM接入和WebSocket接入)_Android
- 2022-12-11 C語言如何計算一個整數的位數_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支