跨进程通信机制
跨進程通信機制
1. Binder
1.1. Binder定義
Binder是Android系統(tǒng)中進程間通訊(IPC)的一種方式,也是Android系統(tǒng)中最重要的特性之一。Android中的四大組件Activity,Service,Broadcast,ContentProvider,不同的App等都運行在不同的進程中,它是這些進程間通訊的橋梁。
1.2. Binder架構(gòu)
Binder跨進程通信機制模型基于Client - Server 模式,其在 framework 層進行了封裝,通過 JNI 技術調(diào)用 Native(C/C++)層的 Binder 架構(gòu)。而在Native層,Binder 則以 ioctl 的方式與 Binder 驅(qū)動進行通訊,其架構(gòu)圖如下:
Binder架構(gòu)中的組件主要包括 Client、 Server、 ServiceManager 以及 Binder 驅(qū)動四種,各角色作用如下:
| Client | Android客戶端,使用服務的進程 |
| Server | 服務器端,提供服務的進程 |
| ServiceManager | 管理Service的注冊與查詢,將字符形式的Binder名字轉(zhuǎn)化成Client中對該Binder的代理 |
| Binder驅(qū)動 | 負責進程之間Binder通信的建立,Binder在進程之間的傳遞,Binder引用計數(shù)管理,數(shù)據(jù)包在進程之間的傳遞和交互等一系列底層支持 |
1.3. Binder機制
Binder機制如下圖所示:
- 首先需要注冊Server端,只有注冊了Server端,Client端才有通訊的目標,Server端通過 ServiceManager 注冊服務,注冊的過程就是向 Binder 驅(qū)動的全局鏈表 binder_procs 中插入Server端的信息(binder_proc 是結(jié)構(gòu)體,每個 binder_proc 結(jié)構(gòu)體中都有 todo 任務隊列),然后向 ServiceManager 的 svcinfo 列表中緩存注冊的服務;
- 在Server端注冊完成后,Client端就可以與其進行通訊了。在通訊之前Client端需要先獲取服務,即拿到服務的代理,也可以理解為引用。獲取Server端的方式就是通過 ServiceManager 到 svcinfo 列表中查詢所需服務并返回Server端的代理,svcinfo 列表就是所有已注冊服務的通訊錄,保存了所有已注冊服務的信息;
- 在有了Server端的代理之后即可向Server端發(fā)送請求。Client端通過 BinderProxy 將請求參數(shù)發(fā)送給 ServiceManager,通過共享內(nèi)存的方式使用內(nèi)核方法 copy_from_user() 將請求參數(shù)先拷貝到內(nèi)核空間,此時Client端進入等待狀態(tài),然后 Binder 驅(qū)動向Server端的 todo 隊列里面插入一條事務,執(zhí)行完成之后通過 copy_to_user() 將內(nèi)核的執(zhí)行結(jié)果拷貝到用戶空間(這里只執(zhí)行拷貝命令,并沒有拷貝數(shù)據(jù)),喚醒等待的客戶端并把結(jié)果返回,即完成了一次通訊。
1.4. Binder驅(qū)動
Linux 內(nèi)核的運行空間與用戶程序的運行空間是相互隔離的,即使用戶程序崩潰了,內(nèi)核也不受影響。內(nèi)核空間與用戶空間的交互過程如下圖所示:
內(nèi)核空間可以執(zhí)行任意命令,調(diào)用系統(tǒng)的一切資源,而用戶空間只能執(zhí)行簡單的運算,不能直接調(diào)用系統(tǒng)資源。雖然從邏輯上抽離出內(nèi)核空間和用戶空間,但是不可避免的的是,總有那么一些用戶空間需要訪問內(nèi)核的資源。而用戶空間訪問內(nèi)核空間的唯一方式就是系統(tǒng)調(diào)用(System call),通過這個統(tǒng)一的入口接口,所有的資源訪問都是在內(nèi)核的控制下執(zhí)行,以免導致用戶程序?qū)ο到y(tǒng)資源的越權(quán)訪問,從而保障了系統(tǒng)的安全和穩(wěn)定。
當一個任務(進程)執(zhí)行系統(tǒng)調(diào)用而陷入內(nèi)核代碼中執(zhí)行時,就稱進程處于內(nèi)核運行態(tài)(或簡稱為內(nèi)核態(tài)),此時處理器處于特權(quán)級最高的(0級)內(nèi)核代碼中執(zhí)行,處理器在特權(quán)等級高的時候才能執(zhí)行特權(quán)CPU指令。當進程在執(zhí)行用戶自己的代碼時,則稱其處于用戶運行態(tài)(用戶態(tài)),即此時處理器在特權(quán)級最低的(3級)用戶代碼中運行。
通過系統(tǒng)調(diào)用,用戶空間可以訪問內(nèi)核空間,而當一個用戶空間想與另外一個用戶空間進行通信時就需要讓操作系統(tǒng)內(nèi)核添加支持,如Socket、管道等都是內(nèi)核支持的。但 Binder 并不是 Linux 內(nèi)核的一部分,而是 Linux 的動態(tài)可加載內(nèi)核模塊(Loadable Kernel Module,LKM)。Binder是具有獨立功能的程序,可以被單獨編譯,但不能獨立運行。它在運行時被鏈接到內(nèi)核作為內(nèi)核的一部分在內(nèi)核空間運行。因此,通過添加一個運行在內(nèi)核空間的內(nèi)核模塊,作為用戶進程之間通信的橋梁,從而實現(xiàn)進程間通信。在 Android 系統(tǒng)中,這個運行在內(nèi)核空間的,負責各個用戶進程通過 Binder 通信的內(nèi)核模塊叫做 Binder 驅(qū)動;
在上圖中,用戶空間中 binder_open(), binder_mmap(), binder_ioctl()方法通過 System call 來調(diào)用內(nèi)核空間 Binder 驅(qū)動中相對應的方法。內(nèi)核空間與用戶空間的共享內(nèi)存通過 copy_from_user()和 copy_to_user()內(nèi)核方法來完成用戶空間與內(nèi)核空間的數(shù)據(jù)傳輸。此外,Binder 驅(qū)動中有一個全局的 binder_procs 鏈表,用來保存Server端的進程信息。
1.5. Binder進程與線程
對于底層Binder驅(qū)動而言,通過 binder_procs 鏈表記錄所有創(chuàng)建的 binder_proc 結(jié)構(gòu)體,Binder 驅(qū)動中的每一個 binder_proc 結(jié)構(gòu)體都與用戶空間中的一個用 Binder 通信的進程相對應,且每個進程有且只有一個 ProcessState 對象,通過單例模式實現(xiàn)。在每個進程中可以有多個線程,每個線程對應一個 IPCThreadState 對象,IPCThreadState 對象也是通過單例模式實現(xiàn),在 Binder 驅(qū)動層也有與之相對應的結(jié)構(gòu),即Binder_thread 結(jié)構(gòu)體。在 binder_proc 結(jié)構(gòu)體中通過成員變量 rb_root threads來記錄當前進程內(nèi)所有的 binder_thread。
每個 Server 進程在啟動時創(chuàng)建一個 Binder 線程池,并向其中注冊一個 Binder 線程,之后 Server 進程也可以向 binder 線程池注冊新的線程。當 Binder 驅(qū)動在探測到?jīng)]有空閑 binder 線程時,也可以主動向 Server 進程池注冊新的的 binder 線程。對于一個 Server 進程有一個最大 Binder 線程數(shù)限制,默認為16個 Binder 線程。對于所有 Client 端進程的 Binder 請求都是交由 Server 端進程的 Binder 線程來處理的。
1.6. ServiceManager啟動
ServiceManager提供注冊服務與查詢服務的功能,其啟動如下圖所示:
ServiceManager 分為 framework 層和 native 層,framework 層只是對 native 層進行了封裝方便調(diào)用,圖上展示的是 native 層的 ServiceManager 的啟動過程。
ServiceManager 的啟動是系統(tǒng)在開機時,init 進程解析 init.rc 文件并調(diào)用 service_manager.c 中的 main() 方法啟動的。 native 層的 binder.c 封裝了一些與 Binder 驅(qū)動交互的方法。
ServiceManager 的啟動分為三步:首先打開驅(qū)動創(chuàng)建全局鏈表 binder_procs;然后將自己當前進程信息保存到 binder_procs 鏈表;最后開啟 loop 不斷的處理共享內(nèi)存中的數(shù)據(jù),并處理 BR_xxx 命令(ioctl 的命令)。
1.7. ServiceManager 注冊服務
Service組件運行在Server進程中,首先要將Service注冊到Service Manager中,再啟動一個Binder線程池來等待和處理Client端的通信請求。
注冊過程(addService)的核心工作是在服務所在進程創(chuàng)建binder_node,在ServiceManager進程創(chuàng)建binder_ref。
以Media服務為例,注冊的過程涉及到MediaPlayerService(作為Client進程)和Service Manager(作為Service進程),通信流程圖如下所示:
- 注冊 MediaPlayerService 服務端,通過 ServiceManager 的 addService() 方法來注冊服務;
- 首先 ServiceManager 向 Binder 驅(qū)動發(fā)送 BC_TRANSACTION 命令,并攜帶 ADD_SERVICE_TRANSACTION 命令,同時注冊服務的線程進入等待狀態(tài) waitForResponse()。 Binder 驅(qū)動收到請求命令向 ServiceManager 的 todo 隊列里面添加一條注冊服務的事務。事務的任務就是創(chuàng)建服務端進程 binder_node 信息并插入到 binder_procs 鏈表中;
- 事務處理完之后發(fā)送 BR_TRANSACTION 命令,ServiceManager 收到命令后向 svcinfo 列表中添加已經(jīng)注冊的服務。最后發(fā)送 BR_REPLY 命令喚醒等待的線程,通知注冊成功。
1.8. ServiceManager 獲取服務
請求服務過程,就是向serviceManager進程查詢指定服務,當執(zhí)行binder_transaction()時,會區(qū)分請求服務所屬進程情況:
- 當請求服務的進程與服務屬于不同進程,則為請求服務所在進程創(chuàng)建binder_ref對象,指向服務進程中的binder_node,即返回請求服務的一個代理對象;
- 當請求服務的進程與服務屬于同一進程,則不再創(chuàng)建新對象,而是返回的對象的真實子類;
ServiceManager 獲取服務的流程如下圖所示:
- 獲取服務的過程與注冊類似,相反的過程。通過 ServiceManager 的 getService() 方法來注冊服務;
- 首先 ServiceManager 向 Binder 驅(qū)動發(fā)送 BC_TRANSACTION 命令,并攜帶 CHECK_SERVICE_TRANSACTION 命令,同時獲取服務的線程進入等待狀態(tài) waitForResponse();
- Binder 驅(qū)動收到請求命令向 ServiceManager 的發(fā)送 BC_TRANSACTION 查詢已注冊的服務,若查詢到所需服務則直接響應 BR_REPLY 并喚醒等待的線程,否則將與 binder_procs 鏈表中的服務進行一次通訊再響應。
1.9. 進行一次完整通訊
進行一次完整通訊的流程如下圖所示:
- 服務注冊完成;
- 首先通過 ServiceManager 獲取到服務端的 BinderProxy 代理對象,通過調(diào)用 BinderProxy 將參數(shù),方法標識傳給 ServiceManager,同時客戶端線程進入等待狀態(tài);
- ServiceManager 將用戶空間的參數(shù)等請求數(shù)據(jù)復制到內(nèi)核空間,并向服務端插入一條執(zhí)行方法的事務。事務執(zhí)行完通知 ServiceManager 將執(zhí)行結(jié)果從內(nèi)核空間復制到用戶空間,并喚醒等待的線程,響應結(jié)果,通訊結(jié)束。
2. AIDL
2.1. 概述
AIDL,全稱是 “Android Interface Definition Language”,也就是 “Android接口定義語言”。設計這門語言的目的是為了實現(xiàn)進程間通信,尤其是在涉及多進程并發(fā)情況下的進程間通信。
在 Android 中,一個進程通常無法訪問另一個進程的內(nèi)存。因此,為進行通信,進程需將其對象分解成可供操作系統(tǒng)理解的原語,并將其編組為可供操作的對象。而通過 AIDL 即可定義客戶端與服務端均認可的編程接口,以便二者之間實現(xiàn)通信。
2.2. AID語法
AIDL 的語法基本上和 Java 是一樣的,只是在一些細微處有些許差別,差別之處主要如下所示:
-
文件類型:用 AIDL 書寫的文件的后綴是**.aidl**,而不是 .java。
-
數(shù)據(jù)類型:AIDL 默認支持一些數(shù)據(jù)類型,在使用這些數(shù)據(jù)類型的時候是不需要導包的。但是除了這些類型之外的數(shù)據(jù)類型,在使用之前必須導包,即使目標文件與當前正在編寫的 .aidl 文件在同一個包下也是需要導包的(在 Java 中,這種情況是不需要導包的)。
- 默認支持的數(shù)據(jù)類型包括:
- Java中的八種基本數(shù)據(jù)類型:boolean,byte,char,short,int,float,double,long;
- String 類型;
- CharSequence 類型;
- List 類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable,List可以使用泛型;
- Map 類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable,Map是不支持泛型的。
- 默認支持的數(shù)據(jù)類型包括:
-
定向tag:AIDL 中的定向 tag 表示了在跨進程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務端; out 表示數(shù)據(jù)只能由服務端流向客戶端;而 inout 則表示數(shù)據(jù)可在服務端與客戶端之間雙向流通。其中,數(shù)據(jù)流向是針對在客戶端中的那個傳入方法的對象而言的。in 為定向 tag 的話表現(xiàn)為服務端將會接收到一個客戶端中傳入方法的對象的完整數(shù)據(jù),但是客戶端該對象不會因為服務端對傳參的修改而發(fā)生變動;out 的話表現(xiàn)為服務端將會接收到那個對象的的空對象,但是在服務端對接收到的空對象有任何修改之后客戶端將會同步變動;inout 為定向 tag 的情況下,服務端將會接收到客戶端傳來對象的完整信息,并且客戶端將會同步服務端對該對象的任何變動。
注:Java 中的基本類型和 String ,CharSequence 的定向 tag 默認且只能是 in。
-
兩類AIDL文件:第一類是用來定義 parcelable 對象,以供其他 AIDL 文件使用 AIDL 中非默認支持的數(shù)據(jù)類型的。第二類是用來定義方法接口,以供系統(tǒng)使用來完成跨進程通信的。兩類文件都是在“定義”,而不涉及具體的實現(xiàn),這就是為什么它叫做“Android接口定義語言”。
注:所有的非默認支持數(shù)據(jù)類型必須通過第一類AIDL文件定義 parcelable 對象才能被使用。
舉例如下:
目錄結(jié)構(gòu)如圖所示:
其中,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在同一包內(nèi),也需要導入 import com.example.aidlparcelableservertest.Pet; import com.example.aidlparcelableservertest.Person;interface IPet {// 無論是什么類型,所有的返回值前都不需要加任何東西// 定義一個Person對象作為傳入?yún)?shù)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實現(xiàn)跨進程通信
在進行跨進程通信時,AIDL 中定義的方法里包含非默認支持的數(shù)據(jù)類型與否,要進行的操作是不一樣的。如果不包含,則只需要編寫一個.aidl 文件(第二類),如果包含,那么我們通常需要寫 n+1 個 .aidl 文件( n 為非默認支持的數(shù)據(jù)類型的種類數(shù))。所以接下來以 AIDL 文件中包含非默認支持的數(shù)據(jù)類型為例進行介紹。
在構(gòu)建每個包含.aidl文件的應用時,Android SDK 工具會生成基于該.aidl 文件的IBinder接口,并將其保存到項目的 app/build/generated/aidl_source_output_dir 目錄中。服務必須視情況實現(xiàn)IBinder 接口。然后,客戶端應用便可綁定到該服務,并調(diào)用IBinder 中的方法來執(zhí)行 IPC。
使用 AIDL 創(chuàng)建綁定服務的步驟如下:
- 創(chuàng)建.aidl文件:
- 此文件定義帶有方法簽名的編程接口。
- 實現(xiàn)接口:
- Android SDK 工具基于.aidl 文件,使用 Java 編程語言生成接口。此接口擁有一個名為Stub 的內(nèi)部抽象類,用于擴展Binder 類并實現(xiàn) AIDL 接口中的方法,必須擴展該Stub類并實現(xiàn)這些方法。
- 向客戶端公開接口:
- 實現(xiàn)Service 并重寫 onBind(),從而返回Stub 類的實現(xiàn)。
2.3.1. 創(chuàng)建 .aidl文件
創(chuàng)建Pet.aidl、Person.aidl文件,代碼如下:
package com.example.aidlparcelableservertest;parcelable Pet; package com.example.aidlparcelableservertest;parcelable Person;定義接口服務時注意:
- 方法可帶零個或多個參數(shù),返回值或空值;
- 所有非默認支持的數(shù)據(jù)類型的參數(shù)均需要指示數(shù)據(jù)流向的方向標記:in、out 或 inout。默認支持的數(shù)據(jù)類型的參數(shù)只能為in;
- 可以在 ADL 接口中定義 String 常量和 int 字符串常量,如:const int VERSION = 1;
2.3.2. 實現(xiàn) Parcelable 接口
可以通過 IPC 接口,將某個類從一個進程發(fā)送至另一個進程。但必須確保 IPC 通道的另一端可使用該類的代碼,并且該類必須支持 Parcelable 接口。
實現(xiàn) Parcelable 接口相當于 Android 提供的一種自定義序列化機制,不僅要求實現(xiàn)Parcelable接口中定義的方法,而且要求在實現(xiàn)類中定義一個名為CREATOR、類型為Parcelable.Creator的靜態(tài)常量。
- 創(chuàng)建與Pet.aidl、Person.aidl對應的文件Pet.java、Person.java,對應的java文件與aidl文件的包名一致。Pet.aidl、Person.aidl中的內(nèi)容分別如下所示:
注:若AIDL文件中涉及到的所有數(shù)據(jù)類型均為默認支持的數(shù)據(jù)類型,則無此步驟。因為默認支持的數(shù)據(jù)類型都是可序列化的。
- 創(chuàng)建通信接口的Ipet.aidl 文件,代碼如下:
在定義完該通信接口的 AIDL 文件之后,在項目的 app/build/generated/aidl_source_output_dir 目錄中會生成基于該.aidl 文件的IBinder接口(未生成則點擊Make Project)。接口文件分析見后文。
2.3.3. 向客戶端公開接口
- 實現(xiàn)ParcelableService類并重寫onBind(),從而返回Stub 類的實現(xiàn),代碼如下:
- 創(chuàng)建客戶端進程MainActivity.java,代碼如下。客戶端必須擁有接口類的訪問權(quán)限,因此如果客戶端和服務在不同應用內(nèi),則客戶端應用的 src/ 目錄內(nèi)必須包含 .aidl 文件(該文件會生成 android.os.Binder 接口,進而為客戶端提供 AIDL 方法的訪問權(quán)限)的副本,即將Pet與Person類的java文件和AIDL文件以及IPet.aidl復制到客戶端中。注意,復制時需要保持包名不變。
- activity_main.xml:
注:當應用的版本是Android 11(API 級別 30)或更高版本時,需要在AndroidManifest.xml文件中添加<queries>,該標簽的具體應用可見<queries>標簽解析章節(jié),如下所示:
<queries><package android:name="com.example.aidlparcelableservertest"/></queries>或:
<queries><intent><action android:name="PARCELABLE_SERVICE"/></intent></queries>- 運行程序:
2.4. 接口文件分析
接口中內(nèi)容如下:
/** 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對象作為傳入?yún)?shù)@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提供出去,這樣當同一進程其他位置執(zhí)行IBinder.queryLocalInterface的時候就可以獲取到這個Binderpublic Stub() {// 初始化時調(diào)用attachInterface(),相同進程調(diào)用queryLocalInterface()時才能找到該Binderthis.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.example.aidlparcelableservertest.IPet interface,* generating a proxy if needed.*/// 接收服務端的IBinder(通常是傳遞給客戶端onServiceConnected() 回調(diào)方法的參數(shù)),并返回Stub接口的實例。public static com.example.aidlparcelableservertest.IPet asInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}// 調(diào)用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 線程池中,客戶端發(fā)起跨進程請求時,遠程請求會通過系統(tǒng)底層封裝后交給此方法來處理// 方法調(diào)用由 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中參數(shù)_arg0 = com.example.aidlparcelableservertest.Person.CREATOR.createFromParcel(data);} else {_arg0 = null;}// 運行getPets()方法獲取結(jié)果java.util.List<com.example.aidlparcelableservertest.Pet> _result = this.getPets(_arg0);reply.writeNoException();// 寫入返回值reply.writeTypedList(_result);// 在執(zhí)行完 return true 之后系統(tǒng)將會把 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對象作為傳入?yún)?shù)@Overridepublic java.util.List<com.example.aidlparcelableservertest.Pet> getPets(com.example.aidlparcelableservertest.Person owner) throws android.os.RemoteException {// _data存儲流向服務端的數(shù)據(jù)流android.os.Parcel _data = android.os.Parcel.obtain();// _reply存儲流回客戶端的數(shù)據(jù)流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);}// 通過調(diào)用mRemote.transact()來觸發(fā)遠端Stub的onTransact() // 0:數(shù)據(jù)雙向流通,1:數(shù)據(jù)從服務端流向客戶端boolean _status = mRemote.transact(Stub.TRANSACTION_getPets, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().getPets(owner);}_reply.readException();// 從_reply中取出服務端執(zhí)行方法的結(jié)果_result = _reply.createTypedArrayList(com.example.aidlparcelableservertest.Pet.CREATOR);} finally {_reply.recycle();_data.recycle();}// 將結(jié)果返回return _result;}public static com.example.aidlparcelableservertest.IPet sDefaultImpl;}// getPets的調(diào)用id 調(diào)用IPC方法的唯一id// Stub.onTransact()中會通過TRANSACTION_getPets來對應執(zhí)行getPets()方法的邏輯。// Proxy調(diào)用transact的時候,也是通過傳遞TRANSACTION_getPets,來標識自己想要執(zhí)行的邏輯。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對象作為傳入?yún)?shù)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 對象按是否同一進程轉(zhuǎn)換成客戶端所需 AIDL 接口類型的對象, 客戶端和服務器在同一進程中,返回的是服務端 Stub 本身,否則就返回系統(tǒng)封裝后的 Stub.proxy 對象;
- onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) {...}:運行在服務器端中 Binder 線程池中,客戶端發(fā)起跨進程請求時,遠程請求會通過系統(tǒng)底層封裝后交給此方法來處理:通過 code 確定客戶端的方法,再從 data 中取得參數(shù) (如果存在參數(shù)的話),然后執(zhí)行在服務端的目標方法,執(zhí)行完成之后,向 reply 中寫入返回值 (如果客戶端中需要返回值的話);
- Proxy.getPets(com.example.aidlparcelableservertest.Person owner){...} :在客戶端中運行,當客戶端遠程調(diào)用此方法時,內(nèi)部實現(xiàn)方法如下:先創(chuàng)建所需的輸入型 Parcel 對象_data, 輸出型 Parcel 對象 _replay 和返回值對象 _result。先將需求參數(shù)寫入data 中,接著調(diào)用 transact() 方法發(fā)起遠程調(diào)用 (RPC) 請求,同時當前線程會掛起,然后服務端的 onTransact() 方法會被調(diào)用,當 RPC 方法結(jié)束返回后,當前線程從掛起狀態(tài)變成重新運行狀態(tài),并從reply中取出 RPC 過程的返回結(jié)果,最后返回 _reply 中的數(shù)據(jù)。
基本步驟如下:
- Client通過ServiceConnection()獲取到Server的Binder,并且封裝成一個Proxy;
- 通過Proxy來同步調(diào)用IPC方法。同時通過Parcel將參數(shù)傳給Binder,最終觸發(fā)Binder的transact()方法;
- Binder的transact()方法最終會觸發(fā)到Server上Stub的onTransact()方法;
- Server上Stub的onTransact()方法中,會先從Parcel中解析中參數(shù),然后將參數(shù)帶入真正的方法中執(zhí)行,然后將結(jié)果寫入Parcel后傳回;
- Client的IPC方法中,執(zhí)行Binder的transact()時,是阻塞等待的。一直到Server邏輯執(zhí)行結(jié)束后才會繼續(xù)執(zhí)行;
- 當Server返回結(jié)果后,Client從Parcel中取出返回值,于是實現(xiàn)了一次IPC調(diào)用。
bindService()->onBind()->onServiceConnected()->asInterface()->getPets()->transact()(Stub本身直接觸發(fā)該方法,代理是在Proxy.getPets()方法中觸發(fā))->onTransact()->返回結(jié)果
3. **<queries>**標簽解析
當創(chuàng)建的應用以 Android 11(API 級別 30)或更高版本為目標平臺時,在默認情況下,系統(tǒng)只會讓部分應用可見,而隱藏其他應用,以鼓勵最小權(quán)限原則并保障用戶的隱私安全。但是,應用的可見與否會影響到提供其他應用相關信息的方法的返回結(jié)果,如:queryIntentActivities()。此外,還會影響與其他應用的顯式交互,例如啟動另一個應用的服務。
3.1. 自動可見的應用
在 Android 11及以上版本,無需聲明\<queries>便可進行交互的應用如下:
- 我們自己的應用;
- 實現(xiàn) Android 核心功能的某些系統(tǒng)軟件包;
- 安裝了我們自己應用的應用;
- 使用startActivityForResult()方法啟動Activity的任何應用;
- 啟動或者綁定到我們自己的應用中的某項服務的任何應用;
- 訪問我們自己的應用中的ContentProvider的任何應用;
- 具有ContentProvider的任何應用,其中我們自己的應用已被授予URI權(quán)限來訪問該ContentProvider;
- 讀取權(quán)限: FLAG_GRANT_READ_URI_PERMISSION
- 寫入權(quán)限: FLAG_GRANT_WRITE_URI_PERMISSION
- 我們自己的應用作為輸入法應用提供輸入,接收該輸入的任何應用;
此外,無論某一應用對我們自己的應用是否可見,我們都可以使用隱式或者顯示的intent來啟動該應用的 activity。
如需查看特定設備的完整軟件包列表,在該設備的終端中運行adb shell dumpsys package queries命令。在命令輸出中,找到forceQueryable部分,即包含該設備上對我們自己的應用可見的軟件包列表。如下圖所示:
3.2. 查詢應用
當創(chuàng)建的應用以 Android 11(API 級別 30)或更高版本為目標平臺,并且需要與非自動可見的應用進行交互,則需要在該應用的AndroidManifest.xml清單文件中添加<queries>。在<queries>中,可以通過軟件包名稱package、intent簽名或提供程序授權(quán)provide來查詢特定軟件包并與之交互。
3.2.1. 按軟件包名稱查詢特定軟件包及與之交互
當知道要查詢或與之交互的一組特定應用時,可以將其軟件包名稱添加到<queries>內(nèi)的一組<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過濾器進行查詢,使創(chuàng)建的應用能夠查詢到匹配<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 屬性。
除非前面列表中另有說明,否則系統(tǒng)不支持混合使用文本和通配符,如 prefix*。
3.2.3. 在給定提供程序授權(quán)的情況下查詢應用及與之交互
當需要查詢ContentProvider但不知道具體的軟件包名時,可以在 <provider> 元素中聲明該提供程序的授權(quán),如下所示:
<manifest package="com.example.suite.enterprise"><queries><provider android:authorities="com.example.settings.files" /></queries>... </manifest>可以在單個 <queries> 元素中聲明多項提供程序授權(quán):
- 在單個 <provider> 元素中,聲明以英文分號分隔的授權(quán)列表;
- 在同一個<queries>元素中添加多個 <provider>元素 ,在每個 <provider>元素中,聲明單項授權(quán)或以英文分號分隔的授權(quán)列表。
3.2.4. 查詢所有應用及與之交互
在極少數(shù)情況下,我們創(chuàng)建的應用可能需要查詢設備上的所有已安裝的應用。此時,我們可以通過QUERY_ALL_PACKAGES權(quán)限以查詢其他所有已安裝應用,如下所示:
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>3.2.5. parseQueries方法解析
/*** 根據(jù)<queries>標簽中的內(nèi)容進行解析** @param input:與輸入類型無關的,用來被轉(zhuǎn)換為ParseResult輸出類型的輸入源參數(shù)* @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信息進行解析,解析結(jié)果存放在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); }總結(jié)
- 上一篇: 基于LED的温度检测(目标检测到--OC
- 下一篇: unity打开Excel/Word/PP