Android之Binder學習(IPC跨進程通訊)

Binder是什麼?

“粘合劑”,可以理解爲:粘合兩個進程。它在不同的應用情景中,代表的意思不一樣:

  • 從通訊機制的角度,它是 binder 通訊機制,一種實現跨進程通訊的方式。
    作用:在 Andorid 中實現跨進程通訊
     
  • 從模型結構的角度,它是binder 驅動,一種虛擬的物理設備驅動( binder.c )。
    作用:連接三大進程(Client 端進程、Server 端進程、ServiceManager 進程)
     
  • 從安卓代碼的角度,它是 binder 類,一個java類( android.os.binder.java )。
    作用:在 Android 層用代碼方式去實現 binder 通訊機制

 

一些基礎背景

1、進程間通訊的本質

  • 1)進程之間的內存是相互獨立的,不能直接共享內存地址,也就是數據不能共享,不能直接通訊 
  • 2)進程A訪問進程B,無非是A訪問B的變量或方法

2、什麼是C/S架構

  • 一個進程要訪問另外一個進程,本進程叫client,被訪問那個叫server

3、Linux進程間通信(IPC)方式有7種:

  • 管道( pipe )、有名管道、信號量、消息隊列、信號、共享內存、套接字(socket),其中效率最高的是共享內存

4、Andorid 提供了4種跨進程通訊的方法

  • 分別對應着4大組件:Activity、Content Provider、Broadcast和Service。感興趣的朋友可以瞭解一下,而這四大組件的底層通訊實現,都依賴Binder。

5、數據拷貝次數

  • 共享內存不需要進行數據拷貝,binder 通訊方式需要一次數據拷貝

6、Android 五層架構圖,Binder驅動 位於最下層,由下往上結構分爲:

  • Linux Kernael:主要包括(10個):binder驅動、顯示、相機、藍牙、內存、USB、鍵盤、WIFI、聲音等驅動程序、電源管理 等驅動程序
  • HAL:硬件抽象層
  • Libraries 和 Android Runtime:  C/C++ 底層庫(SSL、OpenGL、WebKit、SQLite等);Dalvik虛擬機
  • Framework層
  • 應用層

7、aidl 是什麼?爲什麼採用 aidl ?

  • 因爲進程間不能直接通訊,所以需要相同的通訊規範,所以看到C/S兩端的aidl文件都是一樣的
  • 客戶端和服務端都認可的一個通訊方式規範。假設兩個不同國家的人,一個只會講中文(client端)、一個只會講日語(server端),他們要實現溝通,會採用英語進行交流(相同的aidl)
  • aidl 工具位於 Sdk/build-tools/版本xx.x.x/aidl.exe,可以編譯aidl文件,自動生成Stub(extends Binder)和Proxy類

8、Binder 驅動、Binder 引用、Binder 接口 是什麼?

  • 什麼是Binder 驅動?
    儘管名叫‘驅動’,實際上和硬件設備沒有任何關係,只是實現方式和設備驅動程序是一樣的:它工作於內核態,提供open(),mmap(),poll(),ioctl()等標準文件操作,代碼位於linux目錄的drivers/misc/binder.c中
     
  • 什麼是Binder 引用?
    binder 引用不是 android.os.binder 類對象,而是:被訪問服務端 在binder驅動中註冊的AIDL的唯一信息標識。
         
    如何註冊的?在Stub 的無參構造函數中,通過它的父類構造函數註冊的。
         
    java 知識點:子類執行無參構造函數,必先執行父類的構造函數,如果沒有調用super指定那個,默認執行無參構造函數
     
  • 什麼是Binder 接口?
    就是IBinder 接口,Binder類因爲在framework 層實現了這個接口,具有跨進程傳輸的能力,因爲Andorid 系統會爲所有實現了該接口的對象提供跨進程傳輸服務。

9、AIDL 和 binder的區別:

  • AIDL是一個文件格式,Android Studio 會調用本地sdk目錄的aidl.exe 程序,對這個文件生成固定格式的java 文件,這個java文件由binder 子類組成,Android 底層會利用 binder 類實現IPC.
  • Android提供了AIDL方式(實際是通過binder)實現應用進程間通信(示例代碼

 

10、Binder 使用 Pacelable 實現序列化
在Intent、Bundle 傳輸對象時,需要對它實現序列化,實現序列化有兩種方法:Serrialzable 和 Parcelable,後者由於性能好效率高,因此被用於 Binder 底層數據傳輸。原因詳見

11、句柄是什麼?
英文"handle",能夠從一個數值拎起一大堆數據的東西都可以叫做句柄。我們可以這樣理解Windows句柄:

  • 數值上,是一個32位無符號整型值(32位系統下);
  • 邏輯上,相當於指針的指針;
  • 形象理解上,是Windows中各個對象的一個唯一的、固定不變的ID;
  • 作用上,Windows使用它來標記各種對象,諸如:窗口、位圖、畫筆......可以通過句柄找到這些對象。

系統規定:任何 IPC 都必須使用句柄0來訪問,Binder驅動爲 ServiceManager 分配了 0號句柄,其餘進程句柄都是一個的大於0的值

 

 

 

 

通訊過程

主要由4部分組成:Binder驅動、ServiceManager、Server、Client。下面分別對每個模塊進行分析:

1、Binder驅動:

  1. 是所有IPC通訊的基礎。Binder 不屬於 Linux 系統內核的部分,但利用了 Linux 的動態內核可加載模塊的機制,已經把它集成在 LinuxKernal 層。
  2. 所有進程訪問binder 驅動,要先調用binder_open 函數打開binder設備驅動
    (這過程中,會創建一個binder_proc 結構體,它裏面有一個成員task_struct 結構體,用來記錄着進程的各種信息和狀態,如
    線程表、binder節點表、節點引用表)!!!!!這個很重要,下面提到的所有進程訪問都會用到這個特性!!!!!

2、服務端 註冊: Server 向 ServerManager 註冊
對應上圖 2_1:初始化時,服務端(Server)提供參數(進程的名稱),請求Binder驅動(binder.c),要求註冊到ServiceManager。binder驅動把binder_proc (見上面第2點說明,下同)的信息發送給ServiceManager
對應上圖 2_2:在ServiceManager中有一個鏈表svc list,每當向ServiceManager註冊一個服務端,就會往svc_list鏈表添加一個節點(node),節點名稱就是Server的名稱

3、客戶端 查詢 服務端
     對應上圖 3_1:客戶端(Client)根據Server服務名稱,向Binder驅動(binder.c)請求查詢ServiceManager 的鏈表。
     對應上圖 3_2:ServiceManager 遍歷svc list 鏈表,找到與服務名稱相同的節點(node)
     對應上圖 3_3:ServiceManager 把響應結果handle(整型值)回覆給Client
     對應上圖 3_4:Client 收到 handle 值

4、客戶端 調用 服務端
客戶端根據handle 值,經過binder 驅動內部的一系列轉化,將handle 的值對應到binder_pro 裏面,進而找到Server 進程。


下面簡述一下每部分的工作流程:

ServiceManager:

  • ServiceManager是由系統的 init 進程啓動的,不依賴任何Android 服務進程,在Linux系統中 init 進程是一切用戶空間進程的父進程。源碼位置:/frameworks/native/cmds/servicemanager/service_manager.c
  • ServiceManager 工作流程:從service_manager.c 的 main 函數作爲入口:
    第一步:調用函數binder_open打開設備文件/dev/binder
    第二步:調用binder_become_context_manager將自己註冊爲Binder進程間通信機制的上下文管理者;
    第三步:調用函數binder_loop開啓循環,監聽Client進程的通信要求。


    1_1)通過open函數,打開binder驅動(/dev/binder)
            使用mmap。在內核空間,創建一個內存空間,應用層需要通過mmap拿到內存首地址
    1_2)成爲管理者:告訴binder驅動,我是各種服務的管理者(handle值爲0。查詢到註冊的服務的handle值都是 >0)
    1_3)循環。一直處於工作狀態
         1)讀取數據----讀取binder驅動,獲取請求進程數據(Server 端註冊的數據 / Client 端查詢的數據)
         2)解析數據----判斷數據類型(註冊 or 查詢),如果是註冊服務,在svc_list 鏈表添加數據;如果是查詢服務,找到鏈表中內容
         3)回覆數據----回覆一個handle值
    源碼位置:service_manager.c

     

Server 流程:
2_1)open函數,打開binder驅動。/dev/dbinder
            mmap函數 在內核空間,創建一個內存空間,應用層需要通過mmap拿到內存首地址
2_2)
註冊服務---向serviceManager註冊服務
2_3)
循環
     1)讀取數據---除了讀取servicemanager返回的數據外,重點是讀取client 端數據
     2)解析數據---解析client數據,得知client 到底想要調用server的哪個函數,然後server內部執行這個函數
     3)回覆數據---把執行函數的結果,告知client


Client 流程:
3_1)open函數,打開binder驅動。/dev/dbinder
         mmap函數 在內核空間,創建一個內存空間,應用層需要通過mmap拿到內存首地址
3_2)
查詢服務---向ServiceManager查詢,得到handl整數值,
3_3)
調用服務---根據handle值,通過驅動執行server的函數

 

命令碼:
ServiceManager、Server、Client 三者之間的任意交互,要實現進程間的傳輸,都需要通過命令碼來實現:
1、數據傳輸:
     請求方 發送命令碼 BC_Transaction---> 經過binder驅動後,該命令碼會被轉換成 BR_Transaction ---> 接收方得到BT_Trasaction
2、數據回覆:
      接收方 發送命令碼 BC_Reply----> 經過binder驅動後,該命令碼會被轉換得到 BR_Reply ---> 請求方 得到Br_Reply 

 

一個不恰當的比喻:程序猿找媳婦

1)程序猿(Client),想要找媳婦(Server),要通過婚姻介紹所(ServiceManager)介紹,而婚姻介紹所(ServiceManager)要到政府機關(Binder驅動)註冊企業信息、接受行政管理。
2)程序猿(Client)想找一個稱心如意不跑路的媳婦(Server),需要按照政府部門(Binder驅動)指引,到正規婚姻介紹所(ServiceManager)提出要求即可:年齡、身高、三圍、工作、長相等等。
3)婚姻介紹所(ServiceManager)按照要求,找到了鳳姐(指定的Server),告訴程序員(Client)鳳姐的電話號碼,讓他們兩個私下聯繫。
4)程序猿(Client)跟鳳姐(Server)進行約會(傳輸通訊)
5)上述流程環節都要通過政府部門(Binder驅動)的監督、指引,在合法範圍內辦事。

 

借用張圖:

 

 

 

先來理清幾個類:

  • IBinder : IBinder 是一個接口,代表了一種跨進程通信的能力。只要實現了這個藉口,這個對象就能跨進程傳輸。
  • IInterface : IInterface 代表的就是 Server 進程對象具備什麼樣的能力(能提供哪些方法,其實對應的就是 AIDL 文件中定義的接口)
  • Binder : Java 層的 Binder 類,代表的其實就是 Binder 本地對象。
    BinderProxy 類是 Binder 類的一個內部類,它代表遠程進程的 Binder 對象的本地代理;
    這兩個類都繼承自 IBinder, 因而都具有跨進程傳輸的能力;
    實際上,在跨越進程的時候,Binder 驅動會自動完成這兩個對象的轉換。
  • Stub : AIDL 的時候,編譯工具會給我們生成一個名爲 Stub 的靜態內部類;
    這個類繼承了 Binder, 說明它是一個 Binder 本地對象,它實現了 IInterface 接口,表明它具有 Server 承諾給 Client 的能力;
    Stub 是一個抽象類,具體的 IInterface 的相關實現需要開發者自己實現。

 


每一個AIDL生成的文件,由兩部分組成:Stub(存根)和Proxy(代理),以進程A、B兩者通訊爲例:

1、請求:進程A通過Proxy通過訪問Binder驅動,Binder驅動找到進程B的Stub
   進程A的Proxy會在callMyService中調用transact,通過JNI連接到IBinder驅動,IBinder驅動會通過native代碼找到進程B的AIDL,找到後調用AIDL的Stub,回調onTransact方法
transact是連接IBinder引用後寫數據,onTransact是IBinder引用連接Stub接受數據

2、響應:進程B通過自身的Proxy訪問Binder驅動,Binder驅動把結果響應給進程A的Stub

進程A 訪問 進程B,先是A 根據自己的aidl標識,通過JNI調用NDK,找到進程B 在binder驅動程序中的 binder引用。因爲A、B進程的aidl標識都是類名、路徑那些都是一樣的,所以binder驅動可以很輕易找到進程B的信息,然後根據類名執行C/C++代碼,訪問B進程中的java層的變量和方法

 

 

爲什麼要用IPC

IPC在我們的應用開發過程中隨處可見,下面我將舉一個例子來說明他的重要性。
我們在MainActivity修改一個靜態變量,接着在另一個進程的SecondActivity中去訪問該變量,看看能否讀取已經修改過的變量。

1、新建一個Student類,並聲明一個靜態變量

public class Student {
    public static String name="BOB";
}


2、在MainActivity的onCreate方法中修改name的值,並打印log

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Student.name = "JACK";
    Log.d("MainActivity:Sname=", Student.name);
}


3、將SecondActivity設置爲新進程,並在其onCreate方法中訪問name

<!-- 在清單文件中通過android:process屬性爲SecondActivity指定特定的進程:com.bob.aidltest:second -->
<activity 
    android:name=".SecondActivity"
    android:process=":second">
</activity>
public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_activity);
        Log.d("SecondActivity:Sname=" , Student.name);
    }
}

通過log,可以看到在MainActivity中修改了name的值,但是在SecondActivity中卻無法讀取修改後的值

通過以上的實驗,大家應該明白了一點:
在不同的進程之間訪問同一個靜態變量是行不通的。其原因是:
每一個進程都分配有一個獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這就導致在不同的虛擬機上訪問同一個對象會產生多個副本。

例如我們在MainActivity中訪問的name的值只會影響當前進程,而對其他進程不會造成影響,所以在SecondActivity中訪問name時依舊只能訪問自己進程中的副本。

 

 

 

未完,待續.....

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章