一、 摘要
本文介紹Android中的IPC方式之一——AIDL。
二、 關於AIDL
AIDL:Android Interface Definition Language,即Android接口定義語言。
Android系統中的進程之間不能共享內存,因此,需要提供一些機制在不同進程之間進行數據通信。
爲了使其他的應用程序也可以訪問本應用程序提供的服務,Android系統採用了遠程過程調用(Remote Procedure Call,RPC)方式來實現。與很多其他的基於RPC的解決方案一樣,Android使用一種接口定義語言(Interface Definition Language,IDL)來公開服務的接口。我們知道4個Android應用程序組件中的3個(Activity、BroadcastReceiver和ContentProvider)都可以進行跨進程訪問,另外一個Android應用程序組件Service同樣可以。因此,可以將這種可以跨進程訪問的服務稱爲AIDL(Android Interface Definition Language)服務。
三、 編寫一個Demo
1. AIDL接口
以Android Studio爲例,鼠標在module上點擊右鍵 - new - AIDL - AIDL File,之後,會在該module下自動創建一個名叫aidl的和src同級的文件夾,其中包含一個和src下同包名的aidl文件,這就是我們的服務接口。
我們編寫完接口後,需要對module重新build一次,然後會自動生成和該aidl接口同名的java接口文件。關於aidl中接口的規範和約束,建議讀者學習這篇文章:你真的理解AIDL中的in,out,inout麼?
這個自動生成的同名java文件,請不要修改它,爲了便於觀察,我將其格式化,然後在註釋中講解:
package com.zengyu.aidldemo;
public interface IDemo extends android.os.IInterface {
/**
* 繼承Binder,這個很關鍵,Android中的IPC,底層都是基於Binder實現,
* 這個靜態內部類,實現了外部類的接口,但是由於是抽象類,所以沒有真正實現,
* 因此後面當我們使用這個Stub時,需要自己去實現外部類的接口,也就是aidl文件中的服務接口
*/
public static abstract class Stub extends android.os.Binder implements com.zengyu.aidldemo.IDemo {
private static final java.lang.String DESCRIPTOR = "com.zengyu.aidldemo.IDemo";
/**
* Stub主要做三件事:
* 1. 將自身與Binder進行關聯;
* 2. 將方法名以字符串形式進行映射(因此aidl不支持方法的不同入參的重載);
* 3. 實現每個服務接口包裹化的讀寫
* 這些都在自動生成時替我們完成了,我們無需關注具體內容
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* 從Binder到aidl接口的轉換
*/
public static com.zengyu.aidldemo.IDemo asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.zengyu.aidldemo.IDemo))) {
return ((com.zengyu.aidldemo.IDemo) iin);
}
return new com.zengyu.aidldemo.IDemo.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_seyHello: {
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _result = this.seyHello(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
/**
* 服務接口包裹化讀寫實現
*/
private static class Proxy implements com.zengyu.aidldemo.IDemo {
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;
}
@Override
public java.lang.String seyHello(java.lang.String from) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(from);
mRemote.transact(Stub.TRANSACTION_seyHello, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_seyHello = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
/**
* 需要我們之後使用時去實現的服務接口
*/
public java.lang.String seyHello(java.lang.String from) throws android.os.RemoteException;
}
2. 服務端
我們在Service中實例化一個Stub(兩種啓動方式的重載方法由讀者自己實現,此處不展示):
public class AIDLService extends Service {
private final IDemo.Stub mBinder = new IDemo.Stub() {
@Override
public String seyHello(String from) throws RemoteException {
return "Hello client, I receive your msg: " + from;
}
};
public AIDLService() {
}
@Override
public IBinder onBind(Intent intent) {
/**
* Binder是IPC的基礎,我們可以在各處見到它
*/
return mBinder;
}
}
別忘了在AndroidManifest中註冊:
<service
android:name=".AIDLService"
<!--表示允許其他應用調用我們這個Service-->
android:exported="true">
</service>
3. 客戶端
客戶端需要包含相同的aidl文件,也就是說,在客戶端的module中,同樣需要創建一個aidl文件,其路徑、文件名、內容,應該與使用到的服務端的aidl保持一致。
然後我們在一個Activity中綁定遠程的服務端Service:
public class MainActivity extends AppCompatActivity {
private IDemo iDemo = null;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/**
* 將服務端的Binder實例轉換爲客戶端的aidl接口
*/
iDemo = IDemo.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iDemo = null;
}
};
/**
* 我們需要在Activity創建時綁定Service,通常在onCreate中
*/
private void bindService() {
Intent intent = new Intent();
// 如果我們給Service添加了用於啓動的filter:
intent.setAction("啓動的filter");
// TODO 使用setComponent或者setPackage來指定Service所在的應用
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
private void testSending() {
try {
iDemo.seyHello("MainActivity");
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
同樣,別忘了檢查我們的Activity是否在AndroidManifest中註冊了。