日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

跨进程通信机制

發布時間:2024/1/18 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 跨进程通信机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

跨進程通信機制


1. Binder

1.1. Binder定義

Binder是Android系統中進程間通訊(IPC)的一種方式,也是Android系統中最重要的特性之一。Android中的四大組件Activity,Service,Broadcast,ContentProvider,不同的App等都運行在不同的進程中,它是這些進程間通訊的橋梁。

1.2. Binder架構

Binder跨進程通信機制模型基于Client - Server 模式,其在 framework 層進行了封裝,通過 JNI 技術調用 Native(C/C++)層的 Binder 架構。而在Native層,Binder 則以 ioctl 的方式與 Binder 驅動進行通訊,其架構圖如下:

Binder架構中的組件主要包括 ClientServerServiceManager 以及 Binder 驅動四種,各角色作用如下:

角色作用
ClientAndroid客戶端,使用服務的進程
Server服務器端,提供服務的進程
ServiceManager管理Service的注冊與查詢,將字符形式的Binder名字轉化成Client中對該Binder的代理
Binder驅動負責進程之間Binder通信的建立,Binder在進程之間的傳遞,Binder引用計數管理,數據包在進程之間的傳遞和交互等一系列底層支持

1.3. Binder機制

Binder機制如下圖所示:

  • 首先需要注冊Server端,只有注冊了Server端,Client端才有通訊的目標,Server端通過 ServiceManager 注冊服務,注冊的過程就是向 Binder 驅動的全局鏈表 binder_procs 中插入Server端的信息(binder_proc 是結構體,每個 binder_proc 結構體中都有 todo 任務隊列),然后向 ServiceManager 的 svcinfo 列表中緩存注冊的服務;
  • 在Server端注冊完成后,Client端就可以與其進行通訊了。在通訊之前Client端需要先獲取服務,即拿到服務的代理,也可以理解為引用。獲取Server端的方式就是通過 ServiceManager 到 svcinfo 列表中查詢所需服務并返回Server端的代理,svcinfo 列表就是所有已注冊服務的通訊錄,保存了所有已注冊服務的信息;
  • 在有了Server端的代理之后即可向Server端發送請求。Client端通過 BinderProxy 將請求參數發送給 ServiceManager,通過共享內存的方式使用內核方法 copy_from_user() 將請求參數先拷貝到內核空間,此時Client端進入等待狀態,然后 Binder 驅動向Server端的 todo 隊列里面插入一條事務,執行完成之后通過 copy_to_user() 將內核的執行結果拷貝到用戶空間(這里只執行拷貝命令,并沒有拷貝數據),喚醒等待的客戶端并把結果返回,即完成了一次通訊。

1.4. Binder驅動

Linux 內核的運行空間與用戶程序的運行空間是相互隔離的,即使用戶程序崩潰了,內核也不受影響。內核空間與用戶空間的交互過程如下圖所示:

內核空間可以執行任意命令,調用系統的一切資源,而用戶空間只能執行簡單的運算,不能直接調用系統資源。雖然從邏輯上抽離出內核空間和用戶空間,但是不可避免的的是,總有那么一些用戶空間需要訪問內核的資源。而用戶空間訪問內核空間的唯一方式就是系統調用(System call),通過這個統一的入口接口,所有的資源訪問都是在內核的控制下執行,以免導致用戶程序對系統資源的越權訪問,從而保障了系統的安全和穩定。

當一個任務(進程)執行系統調用而陷入內核代碼中執行時,就稱進程處于內核運行態(或簡稱為內核態),此時處理器處于特權級最高的(0級)內核代碼中執行,處理器在特權等級高的時候才能執行特權CPU指令。當進程在執行用戶自己的代碼時,則稱其處于用戶運行態(用戶態),即此時處理器在特權級最低的(3級)用戶代碼中運行。

通過系統調用,用戶空間可以訪問內核空間,而當一個用戶空間想與另外一個用戶空間進行通信時就需要讓操作系統內核添加支持,如Socket、管道等都是內核支持的。但 Binder 并不是 Linux 內核的一部分,而是 Linux 的動態可加載內核模塊(Loadable Kernel Module,LKM)。Binder是具有獨立功能的程序,可以被單獨編譯,但不能獨立運行。它在運行時被鏈接到內核作為內核的一部分在內核空間運行。因此,通過添加一個運行在內核空間的內核模塊,作為用戶進程之間通信的橋梁,從而實現進程間通信。在 Android 系統中,這個運行在內核空間的,負責各個用戶進程通過 Binder 通信的內核模塊叫做 Binder 驅動;

在上圖中,用戶空間中 binder_open(), binder_mmap(), binder_ioctl()方法通過 System call 來調用內核空間 Binder 驅動中相對應的方法。內核空間與用戶空間的共享內存通過 copy_from_user()和 copy_to_user()內核方法來完成用戶空間與內核空間的數據傳輸。此外,Binder 驅動中有一個全局的 binder_procs 鏈表,用來保存Server端的進程信息。

1.5. Binder進程與線程

對于底層Binder驅動而言,通過 binder_procs 鏈表記錄所有創建的 binder_proc 結構體,Binder 驅動中的每一個 binder_proc 結構體都與用戶空間中的一個用 Binder 通信的進程相對應,且每個進程有且只有一個 ProcessState 對象,通過單例模式實現。在每個進程中可以有多個線程,每個線程對應一個 IPCThreadState 對象,IPCThreadState 對象也是通過單例模式實現,在 Binder 驅動層也有與之相對應的結構,即Binder_thread 結構體。在 binder_proc 結構體中通過成員變量 rb_root threads來記錄當前進程內所有的 binder_thread。

每個 Server 進程在啟動時創建一個 Binder 線程池,并向其中注冊一個 Binder 線程,之后 Server 進程也可以向 binder 線程池注冊新的線程。當 Binder 驅動在探測到沒有空閑 binder 線程時,也可以主動向 Server 進程池注冊新的的 binder 線程。對于一個 Server 進程有一個最大 Binder 線程數限制,默認為16個 Binder 線程。對于所有 Client 端進程的 Binder 請求都是交由 Server 端進程的 Binder 線程來處理的。

1.6. ServiceManager啟動

ServiceManager提供注冊服務與查詢服務的功能,其啟動如下圖所示:

ServiceManager 分為 framework 層和 native 層,framework 層只是對 native 層進行了封裝方便調用,圖上展示的是 native 層的 ServiceManager 的啟動過程。

ServiceManager 的啟動是系統在開機時,init 進程解析 init.rc 文件并調用 service_manager.c 中的 main() 方法啟動的。 native 層的 binder.c 封裝了一些與 Binder 驅動交互的方法。

ServiceManager 的啟動分為三步:首先打開驅動創建全局鏈表 binder_procs;然后將自己當前進程信息保存到 binder_procs 鏈表;最后開啟 loop 不斷的處理共享內存中的數據,并處理 BR_xxx 命令(ioctl 的命令)。

1.7. ServiceManager 注冊服務

Service組件運行在Server進程中,首先要將Service注冊到Service Manager中,再啟動一個Binder線程池來等待和處理Client端的通信請求。

注冊過程(addService)的核心工作是在服務所在進程創建binder_node,在ServiceManager進程創建binder_ref。

以Media服務為例,注冊的過程涉及到MediaPlayerService(作為Client進程)和Service Manager(作為Service進程),通信流程圖如下所示:

  • 注冊 MediaPlayerService 服務端,通過 ServiceManager 的 addService() 方法來注冊服務;
  • 首先 ServiceManager 向 Binder 驅動發送 BC_TRANSACTION 命令,并攜帶 ADD_SERVICE_TRANSACTION 命令,同時注冊服務的線程進入等待狀態 waitForResponse()。 Binder 驅動收到請求命令向 ServiceManager 的 todo 隊列里面添加一條注冊服務的事務。事務的任務就是創建服務端進程 binder_node 信息并插入到 binder_procs 鏈表中;
  • 事務處理完之后發送 BR_TRANSACTION 命令,ServiceManager 收到命令后向 svcinfo 列表中添加已經注冊的服務。最后發送 BR_REPLY 命令喚醒等待的線程,通知注冊成功。

1.8. ServiceManager 獲取服務

請求服務過程,就是向serviceManager進程查詢指定服務,當執行binder_transaction()時,會區分請求服務所屬進程情況:

  • 當請求服務的進程與服務屬于不同進程,則為請求服務所在進程創建binder_ref對象,指向服務進程中的binder_node,即返回請求服務的一個代理對象;
  • 當請求服務的進程與服務屬于同一進程,則不再創建新對象,而是返回的對象的真實子類;

ServiceManager 獲取服務的流程如下圖所示:

  • 獲取服務的過程與注冊類似,相反的過程。通過 ServiceManager 的 getService() 方法來注冊服務;
  • 首先 ServiceManager 向 Binder 驅動發送 BC_TRANSACTION 命令,并攜帶 CHECK_SERVICE_TRANSACTION 命令,同時獲取服務的線程進入等待狀態 waitForResponse();
  • Binder 驅動收到請求命令向 ServiceManager 的發送 BC_TRANSACTION 查詢已注冊的服務,若查詢到所需服務則直接響應 BR_REPLY 并喚醒等待的線程,否則將與 binder_procs 鏈表中的服務進行一次通訊再響應。

1.9. 進行一次完整通訊

進行一次完整通訊的流程如下圖所示:

  • 服務注冊完成;
  • 首先通過 ServiceManager 獲取到服務端的 BinderProxy 代理對象,通過調用 BinderProxy 將參數,方法標識傳給 ServiceManager,同時客戶端線程進入等待狀態;
  • ServiceManager 將用戶空間的參數等請求數據復制到內核空間,并向服務端插入一條執行方法的事務。事務執行完通知 ServiceManager 將執行結果從內核空間復制到用戶空間,并喚醒等待的線程,響應結果,通訊結束。

2. AIDL

2.1. 概述

AIDL,全稱是 “Android Interface Definition Language”,也就是 “Android接口定義語言”。設計這門語言的目的是為了實現進程間通信,尤其是在涉及多進程并發情況下的進程間通信。

在 Android 中,一個進程通常無法訪問另一個進程的內存。因此,為進行通信,進程需將其對象分解成可供操作系統理解的原語,并將其編組為可供操作的對象。而通過 AIDL 即可定義客戶端與服務端均認可的編程接口,以便二者之間實現通信。

2.2. AID語法

AIDL 的語法基本上和 Java 是一樣的,只是在一些細微處有些許差別,差別之處主要如下所示:

  • 文件類型:用 AIDL 書寫的文件的后綴是**.aidl**,而不是 .java。

  • 數據類型:AIDL 默認支持一些數據類型,在使用這些數據類型的時候是不需要導包的。但是除了這些類型之外的數據類型,在使用之前必須導包,即使目標文件與當前正在編寫的 .aidl 文件在同一個包下也是需要導包的(在 Java 中,這種情況是不需要導包的)。

    • 默認支持的數據類型包括:
      • Java中的八種基本數據類型:booleanbytecharshortintfloatdoublelong
      • String 類型;
      • CharSequence 類型;
      • List 類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable,List可以使用泛型;
      • Map 類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable,Map是不支持泛型的。
  • 定向tag:AIDL 中的定向 tag 表示了在跨進程通信中數據的流向,其中 in 表示數據只能由客戶端流向服務端; out 表示數據只能由服務端流向客戶端;而 inout 則表示數據可在服務端與客戶端之間雙向流通。其中,數據流向是針對在客戶端中的那個傳入方法的對象而言的。in 為定向 tag 的話表現為服務端將會接收到一個客戶端中傳入方法的對象的完整數據,但是客戶端該對象不會因為服務端對傳參的修改而發生變動;out 的話表現為服務端將會接收到那個對象的的空對象,但是在服務端對接收到的空對象有任何修改之后客戶端將會同步變動;inout 為定向 tag 的情況下,服務端將會接收到客戶端傳來對象的完整信息,并且客戶端將會同步服務端對該對象的任何變動。

    注:Java 中的基本類型和 String ,CharSequence 的定向 tag 默認且只能是 in

  • 兩類AIDL文件:第一類是用來定義 parcelable 對象,以供其他 AIDL 文件使用 AIDL 中非默認支持的數據類型的。第二類是用來定義方法接口,以供系統使用來完成跨進程通信的。兩類文件都是在“定義”,而不涉及具體的實現,這就是為什么它叫做“Android接口定義語言”。
    注:所有的非默認支持數據類型必須通過第一類AIDL文件定義 parcelable 對象才能被使用。

舉例如下:

目錄結構如圖所示:

其中,Pet.java、Person.java分別與Pet.aidl、Person.aidl對應,對應的java文件與aidl文件的包名需一致(自動編譯)。

Pet.aidl、Person.aidl以及Ipet.aidl文件代碼如下:

package com.example.aidlparcelableservertest;// Pet.aidl // 第一類AIDL文件 // 這個文件的作用是引入一個序列化對象 Pet 供其他的AIDL文件使用 // Pet.aidl 與 Pet.java的包名應該是一樣的 parcelable Pet; package com.example.aidlparcelableservertest;// Person.aidl // 第一類AIDL文件 // 這個文件的作用是引入一個序列化對象 Person 供其他的AIDL文件使用 // Person.aidl 與 Person.java的包名應該時一樣的 parcelable Person; package com.example.aidlparcelableservertest;// Ipet.aidl // 第二類AIDL文件// Pet和Person不是默認支持的類型 // 即使Ipet.aidl與Pet.aidl、Person.aidl在同一包內,也需要導入 import com.example.aidlparcelableservertest.Pet; import com.example.aidlparcelableservertest.Person;interface IPet {// 無論是什么類型,所有的返回值前都不需要加任何東西// 定義一個Person對象作為傳入參數List<Pet> getPets(in Person owner);// 傳參時除Java基本類型void addPetIn(in Pet pet);void addPetOut(out Pet pet);void addPetInout(inout Pet pet);}

2.3. 如使用AIDL實現跨進程通信

在進行跨進程通信時,AIDL 中定義的方法里包含非默認支持的數據類型與否,要進行的操作是不一樣的。如果不包含,則只需要編寫一個.aidl 文件(第二類),如果包含,那么我們通常需要寫 n+1 個 .aidl 文件( n 為非默認支持的數據類型的種類數)。所以接下來以 AIDL 文件中包含非默認支持的數據類型為例進行介紹。

在構建每個包含.aidl文件的應用時,Android SDK 工具會生成基于該.aidl 文件的IBinder接口,并將其保存到項目的 app/build/generated/aidl_source_output_dir 目錄中。服務必須視情況實現IBinder 接口。然后,客戶端應用便可綁定到該服務,并調用IBinder 中的方法來執行 IPC。

使用 AIDL 創建綁定服務的步驟如下:

  • 創建.aidl文件:
    • 此文件定義帶有方法簽名的編程接口。
  • 實現接口:
    • Android SDK 工具基于.aidl 文件,使用 Java 編程語言生成接口。此接口擁有一個名為Stub 的內部抽象類,用于擴展Binder 類并實現 AIDL 接口中的方法,必須擴展該Stub類并實現這些方法。
  • 向客戶端公開接口:
    • 實現Service 并重寫 onBind(),從而返回Stub 類的實現。

2.3.1. 創建 .aidl文件

創建Pet.aidl、Person.aidl文件,代碼如下:

package com.example.aidlparcelableservertest;parcelable Pet; package com.example.aidlparcelableservertest;parcelable Person;

定義接口服務時注意:

  • 方法可帶零個或多個參數,返回值或空值;
  • 所有非默認支持的數據類型的參數均需要指示數據流向的方向標記:in、out 或 inout。默認支持的數據類型的參數只能為in;
  • 可以在 ADL 接口中定義 String 常量和 int 字符串常量,如:const int VERSION = 1;

2.3.2. 實現 Parcelable 接口

可以通過 IPC 接口,將某個類從一個進程發送至另一個進程。但必須確保 IPC 通道的另一端可使用該類的代碼,并且該類必須支持 Parcelable 接口。

實現 Parcelable 接口相當于 Android 提供的一種自定義序列化機制,不僅要求實現Parcelable接口中定義的方法,而且要求在實現類中定義一個名為CREATOR、類型為Parcelable.Creator的靜態常量。

  • 創建與Pet.aidl、Person.aidl對應的文件Pet.java、Person.java,對應的java文件與aidl文件的包名一致。Pet.aidl、Person.aidl中的內容分別如下所示:
package com.example.aidlparcelableservertest;import android.os.Parcel; import android.os.Parcelable;public class Pet implements Parcelable {private String name;private double weight;public Pet() {}public Pet(String name, double weight) {super();this.name = name;this.weight = weight;}public String getName() {return name;}public void setName(String name) {this.name = name;}public double getWeight() {return weight;}public void setWeight(double weight) {this.weight = weight;}// 實現Parcelable接口必須實現的方法@Overridepublic int describeContents() {return 0;}// 實現Parcelable接口必須實現的方法// 將Pet對象的數據寫入到parcel中@Overridepublic void writeToParcel(Parcel parcel, int i) {parcel.writeString(name);parcel.writeDouble(weight);}// 添加一個靜態成員CREATOR,該對象實現了Parcelable.Creator接口// 該靜態常量的值負責從parcel數據包中回復Pet對象public static final Creator<Pet> CREATOR = new Creator<Pet>() {@Overridepublic Pet createFromParcel(Parcel parcel) {// 從Parcel中讀取數據,返回Pet對象return new Pet(parcel.readString(), parcel.readDouble());}@Overridepublic Pet[] newArray(int i) {return new Pet[i];}};@Overridepublic String toString() {return "Pet{" + "name='" + name + ", weight=" + weight + '}';} } package com.example.aidlparcelableservertest;import android.os.Parcel; import android.os.Parcelable;import java.util.Objects;public class Person implements Parcelable {private int id;private String name;private String pass;public Person() {}public Person(Integer id, String name, String pass) {super();this.id = id;this.name = name;this.pass = pass;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPass() {return pass;}public void setPass(String pass) {this.pass = pass;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return Objects.equals(name, person.name) && Objects.equals(pass, person.pass);}@Overridepublic int hashCode() {return Objects.hash(id, name, pass);}// 實現Parcelable接口必須實現describeContents@Overridepublic int describeContents() {return 0;}// 實現Parcelable接口必須實現describeContents// 將Person對象的數據寫入到parcel中@Overridepublic void writeToParcel(Parcel parcel, int i) {// 把該對象所包含的數據寫到Parcel中parcel.writeInt(id);parcel.writeString(name);parcel.writeString(pass);}// 添加一個靜態成員CREATOR,該對象實現了Parcelable.Creator接口// 該靜態常量的值負責從parcel數據包中回復Person對象public static final Creator<Person> CREATOR = new Creator<Person>() {@Overridepublic Person createFromParcel(Parcel parcel) {// 從Parcel中讀取數據,返回Person對象return new Person(parcel.readInt(), parcel.readString(), parcel.readString());}@Overridepublic Person[] newArray(int i) {return new Person[i];}}; }

注:若AIDL文件中涉及到的所有數據類型均為默認支持的數據類型,則無此步驟。因為默認支持的數據類型都是可序列化的。

  • 創建通信接口的Ipet.aidl 文件,代碼如下:
package com.example.aidlparcelableservertest;import com.example.aidlparcelableservertest.Pet; import com.example.aidlparcelableservertest.Person;interface IPet {List<Pet> getPets(in Person owner);}

在定義完該通信接口的 AIDL 文件之后,在項目的 app/build/generated/aidl_source_output_dir 目錄中會生成基于該.aidl 文件的IBinder接口(未生成則點擊Make Project)。接口文件分析見后文。

2.3.3. 向客戶端公開接口

  • 實現ParcelableService類并重寫onBind(),從而返回Stub 類的實現,代碼如下:
package com.example.aidlparcelableservertest;import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException;import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;public class ParcelableService extends Service {private static final String TAG = "ParcelableService";private PetBinder petBinder;private static Map<Person, List<Pet>> pets = new HashMap<>();static {// 初始化pets Map集合List<Pet> list1 = new ArrayList<>();list1.add(new Pet("旺財", 4.3));list1.add(new Pet("來福", 5.8));pets.put(new Person(1, "sun", "1234"), list1);List<Pet> list2 = new ArrayList<>();list2.add(new Pet("kitty", 6.6));list2.add(new Pet("bobby", 8.8));pets.put(new Person(2, "moon", "4321"), list2);}// 繼承Stub,也就是實現了IPet接口,并實現了IBinder接口class PetBinder extends IPet.Stub {// getPets()方法的實現@Overridepublic List<Pet> getPets(Person owner) throws RemoteException {return pets.get(owner);}}@Overridepublic void onCreate() {super.onCreate();petBinder = new PetBinder();}@Overridepublic IBinder onBind(Intent intent) {/*** 返回petBinder對象* 在綁定本地Service的情況下,該petBinder對象會直接傳給客戶端的ServiceConnection對象的onServiceConnected方法的第二個參數* 在綁定遠程Service的情況下,只將petBinder對象的代理傳給客戶端的ServiceConnection對象的onServiceConnected方法的第二個參數*/return petBinder;} }
  • 創建客戶端進程MainActivity.java,代碼如下。客戶端必須擁有接口類的訪問權限,因此如果客戶端和服務在不同應用內,則客戶端應用的 src/ 目錄內必須包含 .aidl 文件(該文件會生成 android.os.Binder 接口,進而為客戶端提供 AIDL 方法的訪問權限)的副本,即將Pet與Person類的java文件和AIDL文件以及IPet.aidl復制到客戶端中。注意,復制時需要保持包名不變。

package com.example.aidlparcelableclienttest;import androidx.appcompat.app.AppCompatActivity;import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.ListView;// 引入IPet.aidl及Pet.java、Person.java import com.example.aidlparcelableservertest.IPet; import com.example.aidlparcelableservertest.Pet; import com.example.aidlparcelableservertest.Person;import java.util.List;public class MainActivity extends AppCompatActivity {private IPet petService;private ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {// 獲取遠程Service的onBind方法所返回的對象的代理petService = IPet.Stub.asInterface(iBinder);}@Overridepublic void onServiceDisconnected(ComponentName componentName) {petService = null;}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);EditText personView = findViewById(R.id.person);ListView showView = findViewById(R.id.show);Button getBtn = findViewById(R.id.get);Intent intent = new Intent();intent.setAction("PARCELABLE_SERVICE");intent.setPackage("com.example.aidlparcelableservertest");bindService(intent, conn, Service.BIND_AUTO_CREATE);getBtn.setOnClickListener(view -> {String personName = personView.getText().toString();try {// 調用遠程Service方法List<Pet> pets = petService.getPets(new Person(1, personName, personName));// 將程序返回的list包裝成ArrayAdapterArrayAdapter<Pet> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, pets);showView.setAdapter(adapter);} catch (RemoteException e) {e.printStackTrace();}});}@Overrideprotected void onDestroy() {super.onDestroy();this.unbindService(conn);} }
  • activity_main.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent"android:orientation="vertical"><Buttonandroid:id="@+id/get"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="@string/get" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/person" /><EditTextandroid:id="@+id/person"android:layout_width="fill_parent"android:layout_height="wrap_content"android:hint="@string/person"/><ListViewandroid:id="@+id/show"android:layout_width="fill_parent"android:layout_height="fill_parent"android:cacheColorHint="#00000000"android:textColor="#ffffff" /> </LinearLayout>

注:當應用的版本是Android 11(API 級別 30)或更高版本時,需要在AndroidManifest.xml文件中添加<queries>,該標簽的具體應用可見<queries>標簽解析章節,如下所示:

<queries><package android:name="com.example.aidlparcelableservertest"/></queries>

或:

<queries><intent><action android:name="PARCELABLE_SERVICE"/></intent></queries>
  • 運行程序:

2.4. 接口文件分析

接口中內容如下:

/** This file is auto-generated. DO NOT MODIFY.*/ package com.example.aidlparcelableservertest;public interface IPet extends android.os.IInterface {/*** Default implementation for IPet.*/public static class Default implements com.example.aidlparcelableservertest.IPet {// 定義一個Person對象作為傳入參數@Overridepublic java.util.List<com.example.aidlparcelableservertest.Pet> getPets(com.example.aidlparcelableservertest.Person owner) throws android.os.RemoteException {return null;}@Overridepublic android.os.IBinder asBinder() {return null;}}/*** Local-side IPC implementation stub class.*/public static abstract class Stub extends android.os.Binder implements com.example.aidlparcelableservertest.IPet {// binder唯一標識// 不同的進程之間,通過序列化傳遞DESCRIPTOR來找到對應的Binder// 相同進程,也需要DESCRIPTOR才能找到對應的Binderprivate static final java.lang.String DESCRIPTOR = "com.example.aidlparcelableservertest.IPet";/*** Construct the stub at attach it to the interface.*/// 將interface提供出去,這樣當同一進程其他位置執行IBinder.queryLocalInterface的時候就可以獲取到這個Binderpublic Stub() {// 初始化時調用attachInterface(),相同進程調用queryLocalInterface()時才能找到該Binderthis.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.example.aidlparcelableservertest.IPet interface,* generating a proxy if needed.*/// 接收服務端的IBinder(通常是傳遞給客戶端onServiceConnected() 回調方法的參數),并返回Stub接口的實例。public static com.example.aidlparcelableservertest.IPet asInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}// 調用attachInterface()方法,返回該Binderandroid.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);// server和client同一進程,直接通過queryLocalInterface(DESCRIPTOR)找到Binder返回Stub對象if (((iin != null) && (iin instanceof com.example.aidlparcelableservertest.IPet))) {return ((com.example.aidlparcelableservertest.IPet) iin);}// server和client不同進程,返回一個封裝后的Proxy對象return new com.example.aidlparcelableservertest.IPet.Stub.Proxy(obj);}@Overridespublic android.os.IBinder asBinder() {// 返回stub的Binder對象return this;}// 服務端// 運行在服務器端中 Binder 線程池中,客戶端發起跨進程請求時,遠程請求會通過系統底層封裝后交給此方法來處理// 方法調用由 onTransact() 代碼分派,該代碼通常基于接口中的方法索引。@Overridepublic boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {java.lang.String descriptor = DESCRIPTOR;switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(descriptor);return true;}case TRANSACTION_getPets: {// 需要傳遞DESCRIPTOR,到另一個進程可以找到對應的Binderdata.enforceInterface(descriptor);com.example.aidlparcelableservertest.Person _arg0;if ((0 != data.readInt())) {// 讀取CREATOR中參數_arg0 = com.example.aidlparcelableservertest.Person.CREATOR.createFromParcel(data);} else {_arg0 = null;}// 運行getPets()方法獲取結果java.util.List<com.example.aidlparcelableservertest.Pet> _result = this.getPets(_arg0);reply.writeNoException();// 寫入返回值reply.writeTypedList(_result);// 在執行完 return true 之后系統將會把 reply 流傳回客戶端,只是過程被隱藏了return true;}default: {return super.onTransact(code, data, reply, flags);}}}// 客戶端private static class Proxy implements com.example.aidlparcelableservertest.IPet {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {// Proxy在初始化時引用server的IBindermRemote = remote;}@Overridepublic android.os.IBinder asBinder() {// 返回當前Proxy的Binderreturn mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}// 定義一個Person對象作為傳入參數@Overridepublic java.util.List<com.example.aidlparcelableservertest.Pet> getPets(com.example.aidlparcelableservertest.Person owner) throws android.os.RemoteException {// _data存儲流向服務端的數據流android.os.Parcel _data = android.os.Parcel.obtain();// _reply存儲流回客戶端的數據流android.os.Parcel _reply = android.os.Parcel.obtain();java.util.List<com.example.aidlparcelableservertest.Pet> _result;try {// 需要傳遞DESCRIPTOR,到另一個進程可以找到對應的Binder_data.writeInterfaceToken(DESCRIPTOR);if ((owner != null)) {_data.writeInt(1);owner.writeToParcel(_data, 0);} else {_data.writeInt(0);}// 通過調用mRemote.transact()來觸發遠端Stub的onTransact() // 0:數據雙向流通,1:數據從服務端流向客戶端boolean _status = mRemote.transact(Stub.TRANSACTION_getPets, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().getPets(owner);}_reply.readException();// 從_reply中取出服務端執行方法的結果_result = _reply.createTypedArrayList(com.example.aidlparcelableservertest.Pet.CREATOR);} finally {_reply.recycle();_data.recycle();}// 將結果返回return _result;}public static com.example.aidlparcelableservertest.IPet sDefaultImpl;}// getPets的調用id 調用IPC方法的唯一id// Stub.onTransact()中會通過TRANSACTION_getPets來對應執行getPets()方法的邏輯。// Proxy調用transact的時候,也是通過傳遞TRANSACTION_getPets,來標識自己想要執行的邏輯。static final int TRANSACTION_getPets = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);public static boolean setDefaultImpl(com.example.aidlparcelableservertest.IPet impl) {// Only one user of this interface can use this function// at a time. This is a heuristic to detect if two different// users in the same process use this function.if (Stub.Proxy.sDefaultImpl != null) {throw new IllegalStateException("setDefaultImpl() called twice");}if (impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.example.aidlparcelableservertest.IPet getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}// 定義一個Person對象作為傳入參數public java.util.List<com.example.aidlparcelableservertest.Pet> getPets(com.example.aidlparcelableservertest.Person owner) throws android.os.RemoteException; }
  • DESCRIPTOR:Binder 中唯一的標識,自動生成時用當前 Binder 類名表示;
  • TRANSACTION_getPets:聲明的整型的 id 用于標識在 transact 過程中客戶端中請求的到底是自身還是代理的getPets()方法;
  • asInterface(android.os.IBinder obj) {...}:將服務端的 Binder 對象按是否同一進程轉換成客戶端所需 AIDL 接口類型的對象, 客戶端和服務器在同一進程中,返回的是服務端 Stub 本身,否則就返回系統封裝后的 Stub.proxy 對象;
  • onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) {...}:運行在服務器端中 Binder 線程池中,客戶端發起跨進程請求時,遠程請求會通過系統底層封裝后交給此方法來處理:通過 code 確定客戶端的方法,再從 data 中取得參數 (如果存在參數的話),然后執行在服務端的目標方法,執行完成之后,向 reply 中寫入返回值 (如果客戶端中需要返回值的話);
  • Proxy.getPets(com.example.aidlparcelableservertest.Person owner){...} :在客戶端中運行,當客戶端遠程調用此方法時,內部實現方法如下:先創建所需的輸入型 Parcel 對象_data, 輸出型 Parcel 對象 _replay 和返回值對象 _result。先將需求參數寫入data 中,接著調用 transact() 方法發起遠程調用 (RPC) 請求,同時當前線程會掛起,然后服務端的 onTransact() 方法會被調用,當 RPC 方法結束返回后,當前線程從掛起狀態變成重新運行狀態,并從reply中取出 RPC 過程的返回結果,最后返回 _reply 中的數據。

基本步驟如下:

  • Client通過ServiceConnection()獲取到Server的Binder,并且封裝成一個Proxy;
  • 通過Proxy來同步調用IPC方法。同時通過Parcel將參數傳給Binder,最終觸發Binder的transact()方法;
  • Binder的transact()方法最終會觸發到Server上Stub的onTransact()方法;
  • Server上Stub的onTransact()方法中,會先從Parcel中解析中參數,然后將參數帶入真正的方法中執行,然后將結果寫入Parcel后傳回;
  • Client的IPC方法中,執行Binder的transact()時,是阻塞等待的。一直到Server邏輯執行結束后才會繼續執行;
  • 當Server返回結果后,Client從Parcel中取出返回值,于是實現了一次IPC調用。

bindService()->onBind()->onServiceConnected()->asInterface()->getPets()->transact()(Stub本身直接觸發該方法,代理是在Proxy.getPets()方法中觸發)->onTransact()->返回結果

3. **<queries>**標簽解析

當創建的應用以 Android 11(API 級別 30)或更高版本為目標平臺時,在默認情況下,系統只會讓部分應用可見,而隱藏其他應用,以鼓勵最小權限原則并保障用戶的隱私安全。但是,應用的可見與否會影響到提供其他應用相關信息的方法的返回結果,如:queryIntentActivities()。此外,還會影響與其他應用的顯式交互,例如啟動另一個應用的服務。

3.1. 自動可見的應用

在 Android 11及以上版本,無需聲明\<queries>便可進行交互的應用如下:

  • 我們自己的應用;
  • 實現 Android 核心功能的某些系統軟件包;
  • 安裝了我們自己應用的應用;
  • 使用startActivityForResult()方法啟動Activity的任何應用;
  • 啟動或者綁定到我們自己的應用中的某項服務的任何應用;
  • 訪問我們自己的應用中的ContentProvider的任何應用;
  • 具有ContentProvider的任何應用,其中我們自己的應用已被授予URI權限來訪問該ContentProvider;
    • 讀取權限: FLAG_GRANT_READ_URI_PERMISSION
    • 寫入權限: FLAG_GRANT_WRITE_URI_PERMISSION
  • 我們自己的應用作為輸入法應用提供輸入,接收該輸入的任何應用;

此外,無論某一應用對我們自己的應用是否可見,我們都可以使用隱式或者顯示的intent來啟動該應用的 activity。

如需查看特定設備的完整軟件包列表,在該設備的終端中運行adb shell dumpsys package queries命令。在命令輸出中,找到forceQueryable部分,即包含該設備上對我們自己的應用可見的軟件包列表。如下圖所示:

3.2. 查詢應用

當創建的應用以 Android 11(API 級別 30)或更高版本為目標平臺,并且需要與非自動可見的應用進行交互,則需要在該應用的AndroidManifest.xml清單文件中添加<queries>。在<queries>中,可以通過軟件包名稱package、intent簽名或提供程序授權provide來查詢特定軟件包并與之交互。

3.2.1. 按軟件包名稱查詢特定軟件包及與之交互

當知道要查詢或與之交互的一組特定應用時,可以將其軟件包名稱添加到<queries>內的一組<package>元素中,如下所示:

<manifest package="com.example.game"><queries><package android:name="com.example.store" /><package android:name="com.example.services" /></queries>... </manifest>

3.2.2. 按 intent 過濾器查詢應用及與之交互

當需要查詢一組具有特定用途的應用但卻并不知道其具體的軟件包名稱時,可以在<queries>中按intent過濾器進行查詢,使創建的應用能夠查詢到匹配<intent-filter>的應用,如下所示:

<manifest package="com.example.game"><queries><intent><action android:name="android.intent.action.SEND" /><data android:mimeType="image/jpeg" /></intent></queries>... </manifest>

<intent>元素的限制:

  • 至少要有一個<action>或者一個<data>元素,各屬性至多只有一個;
  • <data>中不能使用 path、pathPrefix、pathPattern 以及 port 屬性,否則會被當作通用通配符*;
  • <data>中不能使用<mimeGroup>屬性;
  • 在單個<intent>的<data>中,mimeType、scheme以及host最多使用一次,但可以在多個<data>之間分配這些屬性,也可以在單個<data>中使用這些屬性。

<intent> 支持通用通配符*作為以下屬性的值:

  • <action>元素的 name 屬性;
  • <data>元素的 mimeType 屬性的子類型 (image/*);
  • <data>元素的 mimeType 屬性的類型和子類型 (*/*);
  • <data>元素的 scheme 屬性;
  • <data>元素的 host 屬性。

除非前面列表中另有說明,否則系統不支持混合使用文本和通配符,如 prefix*。

3.2.3. 在給定提供程序授權的情況下查詢應用及與之交互

當需要查詢ContentProvider但不知道具體的軟件包名時,可以在 <provider> 元素中聲明該提供程序的授權,如下所示:

<manifest package="com.example.suite.enterprise"><queries><provider android:authorities="com.example.settings.files" /></queries>... </manifest>

可以在單個 <queries> 元素中聲明多項提供程序授權:

  • 在單個 <provider> 元素中,聲明以英文分號分隔的授權列表;
  • 在同一個<queries>元素中添加多個 <provider>元素 ,在每個 <provider>元素中,聲明單項授權或以英文分號分隔的授權列表。

3.2.4. 查詢所有應用及與之交互

在極少數情況下,我們創建的應用可能需要查詢設備上的所有已安裝的應用。此時,我們可以通過QUERY_ALL_PACKAGES權限以查詢其他所有已安裝應用,如下所示:

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>

3.2.5. parseQueries方法解析

/*** 根據<queries>標簽中的內容進行解析** @param input:與輸入類型無關的,用來被轉換為ParseResult輸出類型的輸入源參數* @param pkg:被解析的包* @param res:用于訪問應用程序資源的類* @param parser:返回讀取的XML資源* @return ParseResult:ParserInput的輸出端*/ private static ParseResult<ParsingPackage> parseQueries(ParseInput input, ParsingPackage pkg,Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException {final int depth = parser.getDepth();int type;// 判斷是否還可以讀取parser中的資源while ((type = parser.next()) != XmlPullParser.END_DOCUMENT&& (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) {// 初始時刻 type = START_TAGif (type != XmlPullParser.START_TAG) {continue;}// 當queries中標簽使用intent時if (parser.getName().equals("intent")) {// 對Intent中的action、category以及data信息進行解析,解析結果存放在result中ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(null,pkg, res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/, input);if (result.isError()) {return input.error(result);}// 獲取含有解析的action、category以及data等信息的ParsedIntentInfo對象intentInfoParsedIntentInfo intentInfo = result.getResult();Uri data = null;String dataType = null;String host = null;// 分別獲取action、data scheme(為空返回0)、data types(為空返回0)、Host的長度final int numActions = intentInfo.countActions();final int numSchemes = intentInfo.countDataSchemes();final int numTypes = intentInfo.countDataTypes();final int numHosts = intentInfo.getHosts().length;// 每個intent至少要有一個action或者一個data,各屬性至多只有一個if ((numSchemes == 0 && numTypes == 0 && numActions == 0)) {return input.error("intent tags must contain either an action or data.");}if (numActions > 1) {return input.error("intent tag may have at most one action.");}if (numTypes > 1) {return input.error("intent tag may have at most one data type.");}if (numSchemes > 1) {return input.error("intent tag may have at most one data scheme.");}if (numHosts > 1) {return input.error("intent tag may have at most one data host.");}Intent intent = new Intent();// private ArrayList<String> mCategories = null;// 將intentInfo獲取的category屬性添加到intent對象中for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {intent.addCategory(intentInfo.getCategory(i));}// host屬性賦值if (numHosts == 1) {host = intentInfo.getHosts()[0];}// data屬性賦值if (numSchemes == 1) {data = new Uri.Builder().scheme(intentInfo.getDataScheme(0)).authority(host)// /*.path(IntentFilter.WILDCARD_PATH).build();}// dataType屬性賦值if (numTypes == 1) {dataType = intentInfo.getDataType(0);// The dataType may have had the '/' removed for the dynamic mimeType feature.// If we detect that case, we add the * back.if (!dataType.contains("/")) {dataType = dataType + "/*";}// data設置默認值if (data == null) {data = new Uri.Builder().scheme("content")// */*.authority(IntentFilter.WILDCARD).path(IntentFilter.WILDCARD_PATH).build();}}intent.setDataAndType(data, dataType);if (numActions == 1) {intent.setAction(intentInfo.getAction(0));}// 將intent添加到空List<Intent> queriesIntents中pkg.addQueriesIntent(intent);} else if (parser.getName().equals("package")) { // 當queries中標簽使用package時// 獲取包名final TypedArray sa = res.obtainAttributes(parser,R.styleable.AndroidManifestQueriesPackage);final String packageName = sa.getNonConfigurationString(R.styleable.AndroidManifestQueriesPackage_name, 0);if (TextUtils.isEmpty(packageName)) {return input.error("Package name is missing from package tag.");}// 將packageName添加到空List<String> queriesPackages中pkg.addQueriesPackage(packageName.intern());} else if (parser.getName().equals("provider")) { // 當queries中標簽使用provider時final TypedArray sa = res.obtainAttributes(parser,R.styleable.AndroidManifestQueriesProvider);try {// 獲取authorities屬性final String authorities = sa.getNonConfigurationString(R.styleable.AndroidManifestQueriesProvider_authorities, 0);if (TextUtils.isEmpty(authorities)) {return input.error(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,"Authority missing from provider tag.");}// 將多個authorities以";"拆分并加入到pkg中StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";");while (authoritiesTokenizer.hasMoreElements()) {// 將authorities添加到空Set<String> queriesProviders中pkg.addQueriesProvider(authoritiesTokenizer.nextToken());}} finally {sa.recycle();}}}return input.success(pkg); }

總結

以上是生活随笔為你收集整理的跨进程通信机制的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。