跨進程通信和Binder機制

Android中進程和線程的關係和區別

  • 線程是CPU調度的最小單元,同時線程是一種有限的系統資源;而進程一般指一個執行單元,在PC和移動設備上指一個程序或者一個應用。
  • 進程有自己獨立的地址空間,而進程中的線程共享此地址空間,都可以併發執行。
  • 一般來說,一個App程序至少有一個進程,一個進程至少有一個線程(包含與被包含的關係),通俗來講就是,在App這個工廠裏面有一個進程,線程就是裏面的生產線,但主線程(即主生產線)只有一條,而子線程(即副生產線)可以有多個。

爲何需要進程間通信(IPC)?多進程通信可能會出現的問題?

因爲所有運行在不同進程的四大組件(Activity、Service、Receiver、ContentProvider)共享數據都會失敗,這是由於==Android爲每個應用分配了獨立的虛擬機,不同的虛擬機在內存分配上有不同的地址空間,這會導致在不同的虛擬機中訪問同一個類的對象會產生多份副本==。比如常用例子(通過開啓多進程獲取更大內存空間、兩個或者多個應用之間共享數據、微信全家桶)。爲了解決數據共享問題需要進行IPC。

一般來說,使用多進程通信會造成如下幾方面的問題:

  • 靜態成員和單例模式完全失效:獨立的虛擬機造成。
  • 線程同步機制完全失效:獨立的虛擬機造成。
  • SharedPreferences的可靠性下降:這是因爲Sp不支持兩個進程併發進行讀寫,有一定機率導致數據丟失。
  • Application會多次創建:Android系統在創建新的進程時會分配獨立的虛擬機,所以這個過程其實就是啓動一個應用的過程,自然也會創建新的Application。

Android中IPC方式、各種方式優缺點?

image.png

還有==廣播==

Bundle傳遞對象爲什麼需要序列化?Serialzable和Parcelable的區別?

==因爲bundle傳遞數據時只支持基本數據類型,所以在傳遞對象時需要序列化轉換成可存儲或可傳輸的本質狀態(字節流)==。序列化後的對象可以在網絡、IPC(比如啓動另一個進程的Activity、Service和Reciver)之間進行傳輸,也可以存儲到本地。
序列化實現的兩種方式:實現Serializable/Parcelable接口。不同點如圖:

image.png

AIDL以及優化多模塊使用AIDL的情況

AIDL(Android Interface Definition Language,Android接口定義語言):如果在一個進程中要調用另一個進程中對象的方法,可使用AIDL生成可序列化的參數,AIDL會生成一個服務端對象的代理類,通過它客戶端可以實現間接調用服務端對象的方法。
AIDL的本質是系統提供了一套可快速實現Binder的工具。關鍵類和方法:

  • AIDL接口:繼承IInterface。
  • Stub類:Binder的實現類,服務端通過這個類來提供服務。
  • Proxy類:服務端的本地代理,客戶端通過這個類調用服務端的方法。
  • asInterface():客戶端調用,將服務端返回的Binder對象,轉換成客戶端所需要的AIDL接口類型的對象。如果客戶端和服務端位於同一進程,則直接返回Stub對象本身,否則返回系統封裝後的Stub.proxy對象。
  • asBinder():根據當前調用情況返回代理Proxy的Binder對象。
  • onTransact():運行在服務端的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝後交由此方法來處理。
  • transact():運行在客戶端,當客戶端發起遠程請求的同時將當前線程掛起。之後調用服務端的onTransact()直到遠程請求返回,當前線程才繼續執行。

多模塊使用AIDL優化

  • 當有多個業務模塊都需要AIDL來進行IPC,此時需要爲每個模塊創建特定的aidl文件,那麼相應的Service就會很多。必然會出現系統資源耗費嚴重、應用過度重量級的問題。解決辦法是建立Binder連接池,即將每個業務模塊的Binder請求統一轉發到一個遠程Service中去執行,從而避免重複創建Service。
  • 原理:每個業務模塊創建自己的AIDL接口並實現此接口,然後向服務端提供自己的唯一標識和其對應的Binder對象。服務端只需要一個Service並提供一個queryBinder接口,它會根據業務模塊的特徵來返回相應的Binder對象,不同的業務模塊拿到所需的Binder對象後就可以進行遠程方法的調用了。
     private final BookController.Stub stub = new BookController.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return bookList;
        }

        @Override
        public void addBookInOut(Book book) throws RemoteException {
            if (book != null) {
                book.setName("服務器改了新書的名字 InOut");
                bookList.add(book);
            } else {
                Log.e(TAG, "接收到了一個空對象 InOut");
            }
        }

    };
    ...
    
    @Override
    public IBinder onBind(Intent intent) {
        String type = intent.getStringExtra("type")
        if("one".isEqual(type)){
            return stub;
        }else{
            return stub1;
        }
        ...
    }

AIDL使用

服務端

  • 創建實體類.aidl和對應的實體類.java文件。

Book.aidl

package com.wu.test.aidl;
parcelable Book;

Book.java

public class Book implements Parcelable {
//實體字段、實現序列化方法
}
  • 定義接口
interface BookController {

    List<Book> getBookList();

    void addBookInOut(inout Book book);

}

創建或修改過AIDL文件後需要clean下工程,使系統及時生成我們需要的文件

  • 創建Service供客戶端調用
public class AIDLService extends Service {
...
private final BookController.Stub stub = new BookController.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return bookList;
        }

        @Override
        public void addBookInOut(Book book) throws RemoteException {
            if (book != null) {
                book.setName("服務器改了新書的名字 InOut");
                bookList.add(book);
            } else {
                Log.e(TAG, "接收到了一個空對象 InOut");
            }
        }

    };

    @Override
    public IBinder onBind(Intent intent) {
        return stub; //返回binder對象Stub
    }
}

客戶端

  • 把服務端的AIDL文件以及Book類複製過來,將 aidl 文件夾整個複製到和Java文件夾同個層級下,不需要改動任何代碼。創建和服務端Book類所在的相同包名來存放 Book類。
  • 綁定服務,獲取服務端的binder接口對象。
...
private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookController = BookController.Stub.asInterface(service); //綁定服務後,拿到接口對象
            connected = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            connected = false;
        }
    };
    
    //綁定服務
    private void bindService() {
        Intent intent = new Intent();
        intent.setPackage("com.wu.test");
        intent.setAction("com.wu.test.action");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

aidl文件只是用來定義C/S交互的接口,Android在編譯時會自動生成相應的Java類,生成的類中包含了Stub和Proxy靜態內部類,用來封裝數據轉換的過程,實際使用時只關心具體的Java接口類即可。爲什麼Stub和Proxy是靜態內部類呢?這其實只是爲了將三個類放在一個文件中,提高代碼的聚合性。

AIDL的使用

爲何選擇Binder方式

在討論這個問題之前,我們知道Android也是基於Linux內核,Linux現有的進程通信手段有以下幾種:

  • 管道:在創建時分配一個page大小的內存,緩存區大小比較有限;
  • 消息隊列:信息複製兩次,額外的CPU消耗;不合適頻繁或信息量大的通信;
  • 共享內存:無須複製,共享緩衝區直接附加到進程虛擬地址空間,速度快;但進程間的同步問題操作系統無法實現,必須各進程利用同步工具解決;
  • 套接字:作爲更通用的接口,傳輸效率低,主要用於不同機器或跨網絡的通信;
  • 信號量:常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及同一進程內不同線程之間的同步手段。 不適用於信息交換,更適用於進程中斷控制,比如非法內存訪問,殺死某個進程等;

既然有現有的IPC方式,==爲什麼重新設計一套Binder機制呢==。主要是出於三個方面的考量:

  • 效率:傳輸效率主要影響因素是內存拷貝的次數,拷貝次數越少,傳輸速率越高。從Android進程架構角度分析:對於消息隊列、Socket和管道來說,數據先從發送方的緩存區拷貝到內核開闢的緩存區中,再從內核緩存區拷貝到接收方的緩存區,一共兩次拷貝。
    對於Binder來說,數據從發送方的緩存區拷貝到內核的緩存區,而接收方的緩存區與內核的緩存區是映射到同一塊物理地址的,節省了一次數據拷貝的過程,如圖:

  • 穩定性:共享內存不需要拷貝,Binder的性能僅次於共享內存。共享內存的性能優於Binder,那爲什麼不採用共享內存呢,因爲共享內存需要處理併發同步問題,容易出現死鎖和資源競爭,穩定性較差。Socket雖然是基於C/S架構的,但是它主要是用於網絡間的通信且傳輸效率較低。Binder基於C/S架構 ,Server端與Client端相對獨立,穩定性較好。

  • 安全性:傳統Linux IPC的接收方無法獲得對方進程可靠的UID/PID,從而無法鑑別對方身份;而Binder機制爲每個進程分配了UID/PID,且在Binder通信時會根據UID/PID進行有效性檢測。

Binder機制的作用和原理

Linux系統將一個進程分爲用戶空間和內核空間。對於進程之間來說,用戶空間的數據不可共享,內核空間的數據可共享,爲了保證安全性和獨立性,一個進程不能直接操作或者訪問另一個進程,即Android的進程是相互獨立、隔離的,這就需要跨進程之間的數據通信方式。普通的跨進程通信方式一般需要2次內存拷貝,如下圖所示

image.png

==一次完整的 Binder IPC 通信過程==通常是這樣:

  • 首先 Binder 驅動在內核空間創建一個數據接收緩存區。
  • 接着在內核空間開闢一塊內核緩存區,建立內核緩存區和內核中數據接收緩存區之間的映射關係,以及內核中數據接收緩存區和接收進程用戶空間地址的映射關係。(Binder IPC 機制中涉及到的==內存映射通過 mmap() 來實現==,mmap() 是操作系統中一種內存映射的方法。內存映射簡單的講就是將用戶空間的一塊內存區域映射到內核空間。映射關係建立後,用戶對這塊內存區域的修改可以直接反應到內核空間;反之內核空間對這段區域的修改也能直接反應到用戶空間。)
  • 發送方進程通過系統調用 copyfromuser() 將數據 copy 到內核中的內核緩存區,由於內核緩存區和接收進程的用戶空間存在內存映射,因此也就相當於把數據發送到了接收進程的用戶空間,這樣便完成了一次進程間的通信。
image.png

數據接收緩衝區映射如何實現?

==Binder驅動實現了mmap()系統調用==,這對字符設備是比較特殊的,因爲mmap()通常用在有物理存儲介質的文件系統上,而象Binder這樣沒有物理介質,純粹用來通信的字符設備沒必要支持mmap()。Binder驅動當然不是爲了在物理介質和用戶空間做映射,而是用來創建數據接收的緩存空間。先看mmap()是如何使用的:

fd = open("/dev/binder", O_RDWR);

mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);

這樣Binder的接收方就有了一片大小爲MAP_SIZE的接收緩存區。==mmap()的返回值是內存映射在用戶空間的地址==,不過這段空間是由驅動管理,用戶不必也不能直接訪問(映射類型爲PROT_READ,只讀映射)。
==mmap()分配的內存除了映射進了接收方進程裏,還映射進了內核空間==。所以調用copy_from_user()將數據拷貝進內核空間也相當於拷貝進了接收方的用戶空間,這就是Binder只需一次拷貝的‘祕密’。

Binder框架中ServiceManager的作用

Binder框架 是基於 C/S 架構的。由一系列的組件組成,包括 Client、Server、ServiceManager、Binder驅動,其中 Client、Server、Service Manager 運行在用戶空間,Binder 驅動運行在內核空間。如下圖所示:

image.png
  • Server&Client:服務器&客戶端。在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通信。
  • ServiceManager(如同DNS域名服務器)服務的管理者,==將Binder名字轉換爲Client中對該Binder的引用,使得Client可以通過Binder名字獲得Server中Binder實體的引用==。
  • Binder驅動(如同路由器):負責進程之間binder通信的建立,計數管理以及數據的傳遞交互等底層支持。

Service Manager是Binder進程間通信的核心之一,它扮演着Binder進程間通信機制上下文管理者的角色,同時負責管理系統的Service組件,並且向Client組件提供獲取Service代理對象的服務。
==Service Manager運行在一個獨立進程中,因此和Client和Service通信也是運用Binder進程間通信機制來交互的==。ServiceManager 和其他進程同樣採用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 實體,其他進程都是 Client,需要通過這個 Binder 的引用來實現 Binder 的註冊,查詢和獲取。ServiceManager 提供的 Binder 比較特殊,它沒有名字也不需要註冊。當一個進程使用 BINDERSETCONTEXT_MGR 命令將自己註冊成 ServiceManager 時 Binder 驅動會自動爲它創建 Binder 實體(這就是那隻預先造好的那隻雞)。其次這個 Binder 實體的引用在所有 Client 中都固定爲 0 而無需通過其它手段獲得。也就是說,一個 Server 想要向 ServiceManager 註冊自己的 Binder 就必須通過這個 0 號引用和 ServiceManager 的 Binder 通信。

==Service Manager主要是負責如下==:

  1. 打開和映射Binder設備文件;
  2. 註冊爲Binder上下文管理者;
  3. 循環等待Client進程請求;
  4. 管理ServiceManager代理對象;
  5. Service組件創建,同時需要在註冊ServiceManager;
  6. 管理Service代理對象;

總結圖

image.png

Binder進程間通信模型

image.png

Client進程組件、Service進程組件和ServiceManager運行在用戶空間,而Binder驅動程序運行在Linux系統內核空間,其中Client進程組件、Service進程組件和Service Manager均是通過調用Open、mmap和ioctl來訪問虛擬設備文件/dev/binder,從而實現與Binder驅動程序的交互,進而能夠間接地執行進程通信
Binder就是一種把這四個組件粘合在一起的粘結劑了,其中,核心組件便是Binder驅動程序了,Service Manager提供了輔助管理的功能,Client和Server正是在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通信。

==Binder具體是如何通信的==?

image.png

Binder實體對象、Binder引用對象、Binder本地對象和Binder代理對象交互過程:

  1. 運行在Client中Binder代理對象通過Binder驅動程序向運行在Server中的Binder本地對象發出一個進程間通信請求,Binder驅動程序接着根據Client傳遞過來的Binder代理對象的句柄值來找到對應的Binder引用對象。
  2. Binder驅動程序根據前面找到的Binder引用對象找到對應的Binder實體對象,並且創建一個事務來描述該次進程間通信過程。
  3. Binder驅動程序根據前面找到的Binder實體對象來找到運行在server中的Binder本地對象,並且將Client傳遞過來的通信數據發送給它處理。
  4. Binder本地對象處理完成Client進程的通信請求之後,就將通信結果返回給Binder驅動程序,Binder驅動程序接着找到前面創建的一個事務。
  5. Binder驅動程序根據前面找到的事務的相關屬性來找到發出通信請求的Client,並且通知Client將通信結果返回給對應的Binder代理對象處理。

Binder 通信中的代理模式

跨進程通信的過程都有 Binder 驅動的參與,因此在數據流經 Binder 驅動的時候驅動會對數據做一層轉換。==當 A 進程想要獲取 B 進程中的 object 時,驅動並不會真的把 object 返回給 A,而是返回了一個跟 object 看起來一模一樣的代理對象 objectProxy==,這個 objectProxy 具有和 object 一摸一樣的方法,但是這些方法並沒有 B 進程中 object 對象那些方法的能力,這些方法只需要把把請求參數交給驅動即可。對於 A 進程來說和直接調用 object 中的方法是一樣的。

當 Binder 驅動接收到 A 進程的消息後,發現這是個 objectProxy 就去查詢自己維護的表單,一查發現這是 B 進程 object 的代理對象。於是就會去通知 B 進程調用 object 的方法,並要求 B 進程把返回結果發給自己。當驅動拿到 B 進程的返回結果後就會轉發給 A 進程,一次通信就完成了。

image.png

Binder 的完整定義

  • 從進程間通信的角度看,Binder 是一種進程間通信的機制;
  • 從 Server 進程的角度看,Binder 指的是 Server 中的 Binder 實體對象;
  • 從 Client 進程的角度看,Binder 指的是 Binder 代理對象,是 Binder 實體對象的一個遠程代理;
  • 從傳輸過程的角度看,Binder 是一個可以跨進程傳輸的對象;Binder 驅動會對這個跨越進程邊界的對象對一點點特殊處理,自動完成代理對象和本地對象之間的轉換。

https://blog.csdn.net/AndroidStudyDay/article/details/93749470

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