Android IPC入門——AIDL
Android 是一個基於 Linux 的操作系統,保留了多進程的特性。在 Android 中IPC
是一個老生常談的話題,每個進程都是一個單獨的JVM虛擬機,它們是相互獨立的,擁有自己特定的內存地址和空間,無法直接進行聯繫,而AIDL
就像一座橋樑連接不同進程,橋制定了規則,規定人怎麼來往,哪些人可以來往。
AIDL(AndroidInterfaceDefinition Language),是一種IDL語言,定義Android中兩個進程間通信的規則。其底層採用的是Binder機制,可以看做是Binder的官方封裝框架。
語法
AIDL語法和Java大致相似,但是也有着些許區別:
-
AIDL文件大概分爲兩類
- 一類是定義parcelable對象,給其它AIDL文件使用AIDL支持的默認數據類型外的數據
- 一類是用來定義接口的
-
文件後綴名是
.aidl
-
支持的數據類型
- Java八種基本數據類型(byte/short/int/long/float/double/boolean/char)
- String 與 CharSequence 類型
- List類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是實現parcelable接口的
- Map類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是實現parcelable接口的,Map是不支持泛型的。
- 實現parcelable接口的對象
-
目標文件即使與你正在編寫的文件在同一個包下,也需要導包
-
AIDL中的定向 tag 表示了在跨進程通信中數據的流向
- in 表示數據只能由客戶端流向服務端,表現爲服務端將會接收到一個那個對象的完整數據,但是客戶端的那個對象不會因爲服務端對傳參的修改而發生變動
- out 表示數據只能由服務端流向客戶端,表現爲服務端將會接收到那個對象的的空對象,但是在服務端對接收到的空對象有任何修改,之後客戶端將會同步變動
- inout 則表示數據可在服務端與客戶端之間雙向流通,表現爲服務端將會接收到客戶端傳來對象的完整信息,並且客戶端將會同步服務端對該對象的任何變動
使用步驟
聲明文件
創建AIDL文件,用來聲明需要傳輸的數據,傳輸調用的接口,創建好後系統會自動生成一個默認示例方法,該方法展示了所支持的基本數據類型,一般直接刪除然後聲明自己需要的方法即可。
// Declare any non-default types here with import statements
interface IAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
void log(String msg);
}
編譯AIDL
我們所創建的AIDL文件其實並不是他最終的樣子,使用Make Project
會將其編譯成對應的 Java 文件。
public interface IAidlInterface extends android.os.IInterface
{
/** Default implementation for IAidlInterface. */
public static class Default implements com.whr.aidldemo.IAidlInterface
{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.whr.aidldemo.IAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.whr.aidldemo.IAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.whr.aidldemo.IAidlInterface interface,
* generating a proxy if needed.
*/
public static com.whr.aidldemo.IAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.whr.aidldemo.IAidlInterface))) {
return ((com.whr.aidldemo.IAidlInterface)iin);
}
return new com.whr.aidldemo.IAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public 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_basicTypes:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.whr.aidldemo.IAidlInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.whr.aidldemo.IAidlInterface sDefaultImpl;
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.whr.aidldemo.IAidlInterface impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.whr.aidldemo.IAidlInterface getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}
該類比較複雜,僅簡單講解,包含以下成員:
- 靜態匿名內部類 Default,是 AIDL 所定義接口的默認實現類。
- 抽象靜態匿名內部類 Stub,繼承自 Binder,實現 AIDL 定義接口。該類是 AIDL 最核心的一個類,AIDL 底層實現是基於 Binder,而具體的 Binder 實現代碼就是在該類中被編譯器自動補全的。
聲明服務
AIDL 本質上只是一個接口,用於定義規則,而具體的實現就需要依賴於四大組件的Service
了。由於是多進程交互,我們需要在Manifest
中對該 Service 聲明 process
屬性,以此在別的進程中創建該 Service,該屬性有兩種聲明方式,一種是以:
開頭連接子進程名,這樣聲明出來的進程前綴會和應用包名保持一致。另一種則是完全重寫,這樣聲明出來的進程名就是所聲明的名字。
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.whr.aidldemo.RemoteService" />
</intent-filter>
</service>
另外這裏還加了一個intent-filter:這是爲了隱式啓動遠程服務所用。
講到intent-filter就得講還有一個屬性android:exported:代表是否能被其他應用隱式調用,其默認值是由service中有無intent-filter決定的,如果有intent-filter,默認值爲true,否則爲false。手動設置false的情況下,即使有intent-filter匹配,也無法打開,即無法被其他應用隱式調用。
創建服務
Service
是抽象類,當實現類繼承它的時候需要實現其onBind()
方法,該方法返回IBinder
對象,用於和綁定的Activity
進行交互,通常在非IPC
場景下隨便聲明一個Binder
的實現類就可以了,而在這裏,則需要用到之前聲明的AIDL
接口。
由於需要的是 IBinder
對象,這樣顯然是不能講 AIDL 定義的接口直接進行實現後返回的,那要怎麼辦呢,這就涉及到 AIDL 文件自動編譯後生成的抽象靜態內部類 Stub
了,對於該類的作用這裏不過多介紹。總之該類繼承自Binder
並且要求實現 AIDL 定義好的接口,正好符合這裏的需要。
public class RemoteService extends Service {
private Binder stub = new com.whr.aidldemo.IMyAidlInterface.Stub() {
@Override
public void log(String msg) throws RemoteException {
Log.i("common", "pid:" + Process.myPid() + "msg:" + msg);
}
};
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
綁定服務
客戶端綁定遠程Service,並獲得遠程服務的代理對象,即獲取遠程Binder對象的接口的本地實現。
客戶端便可以通過接口調用遠程Service提供的方法。
因爲不處於一個進程,需要隱式啓動,在android5.0以前,我們只要設置Action就行了,但是之後需要把包名也一併設置,從源碼可以看出如果啓動service的intent的component和package都爲空並且版本大於LOLLIPOP(5.0)的時候,直接拋出異常。
private void initAIDL() {
Intent intent = new Intent();
intent.setAction("com.whr.aidldemo.RemoteService");
intent.setPackage(getPackageName());
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//TODO
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}, Context.BIND_AUTO_CREATE);
}
通過實現回調 ServiceConnection
可以獲得遠程服務的IBinder
對象,從而調用我們定義的方法,但是這裏返回的引用是個頂層接口,無法知道我們定義的具體的能力,通常我們會想到用強轉,這裏其實已經爲我們封裝好了專門的轉換方法。
com.whr.aidldemo.IMyAidlInterface myAidlInterface = com.whr.aidldemo.IMyAidlInterface.Stub.asInterface(iBinder);
然後就可以調用我們定義好的方法進行跨進程交互了。
findViewById(R.id.log).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
myAidlInterface.log("click");
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
打印出來的效果如下:
com.whr.aidldemo:remote I/common: pid:4021 msg:click
寫在最後
總結一下,使用 AIDL 可以讓我們非常方便的實現本來複雜的進程間通訊, AIDL 可以看成一個爲了簡化 IPC ,官方基於 Binder 機制封裝的一套框架。
而這其中的難點其實也就是集中在系統編譯 AIDL 之後,爲我們自動實現的 Binder 那部分邏輯裏面了,當然其實不用太過深究也能滿足大部分簡單的操作。
遇到的坑
原本在一開始時筆者是準備用 kotlin 進行所以代碼的編寫的,但是寫完在編譯時發生了一個問題:kotlin 代碼中的引用 AIDL 時文件資源找不到。
經過反覆 clean 、build、清理緩存後依舊存在這個問題,去搜索了一波好像也沒有別人遇到過這個問題,姑且就沒有過多研究。如果有碰到過的童鞋希望能告知一下。