AIDL(Android 介面定義語言),可以使用它定義客戶端與伺服器進程間通信(IPC)的程式介面,在 Android 中,進程之間無法共享記憶體(使用者空間),不同進程之間的通信一般使用 AIDL 來處理。
主要流程就是在 .aidl 文件中定義 AIDL 介面,並將其添加到應用工程的 src 目錄下,創建完成之後 rebuild,Android SDK 工具會自動生成基於該 .aidl 文件的 IBinder
介面,具體的業務物件實現這個介面,這個具體的業務物件也是 IBinder
物件,當綁定服務的時候會根據實際情況返回具體的通信物件(本地還是代理),最後將客戶端綁定到該服務上,之後就可以調用 IBinder
中的方法來進行進程間通信(IPC),下面將從以下幾個方面學習 AIDL 的使用:
- 創建 .aidl 文件
- 具體的業務物件實現基於 .aidl 文件生成的介面
- 向客戶端公開介面
- 客戶端遠程調用
- 驗證 AIDL
創建 .aidl 文件#
在 AIDL 中可以通過可帶參數以及返回值的一個或多個方法來聲明介面,參數和返回值可以是任意類型,AIDL 中支持的數據類型如下:
- java 的 8 種數據類型:byte、short、int、long、float、double、boolean、char
- 除此之外支持 String、charSequence、List、Map
- 自定義數據類型
如果業務方法中參數或返回值類型為 List 或 Map 時:
List 中的所有元素都必須是 AIDL 支持的數據類型、其他 AIDL 生成的介面或自己聲明的可打包類型。可選擇將 List 用作 “通用” 類(例如,List)。另一端實際接收的具體類始終是 ArrayList,但生成的方法使用的是 List 介面。
Map 中的所有元素都必須是 AIDL 支持的數據類型、其他 AIDL 生成的介面或您聲明的可打包類型。不支持通用 Map(如 Map<String,Integer> 形式的 Map)。另一端實際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 介面。
當然,AIDL 也支持自定義數據類型,會在下文中介紹。
首先,在工程的 src 目錄下創建 .aidl 文件,具體如下圖所示:
然後,在 .aidl 文件中添加具體的業務方法,文件內容如下:
// IPersonAidlInterface.aidl
package com.manu.aidldemo;
// Declare any non-default types here with import statements
interface IPersonAidlInterface {
//具體的業務
void setName(String name);
void setAge(int age);
String getInfo();
/**
* 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);
}
然後,重新 rebuild project,Android SDK 工具會在相應的目錄生成對應的與 .aidl 文件同名的 .java 介面文件,具體目錄如下:
具體的業務物件實現基於 .aidl 文件生成的介面#
上一步只是使用 AIDL 定義了基本的業務操作,rebuild 之後會生成與 .aidl 相同文件名的 .java 文件,生成的這個介面文件中有一個名稱為 Stub 的子類,這個子類也是其父介面的抽象實現,主要用於生成 .aidl 文件中的所有方法,Stub 類聲明具體如下:
// Stub
public static abstract class Stub extends android.os.Binder implements com.manu.aidldemo.IPersonAidlInterface
顯然,Stub 實現了本地介面且繼承了 Binder 物件,介於 Binder 物件在系統底層的支持下,Stub 物件就具有了遠程傳輸數據的能力,在生成 Stub 物件的時候會調用 asInterface 方法,具體如下:
// asInterface
public static com.manu.aidldemo.IPersonAidlInterface asInterface(android.os.IBinder obj){
if ((obj==null)) {
return null;
}
// 檢索 Binder 物件是否是本地介面的實現
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.manu.aidldemo.IPersonAidlInterface))) {
return ((com.manu.aidldemo.IPersonAidlInterface)iin);
}
return new com.manu.aidldemo.IPersonAidlInterface.Stub.Proxy(obj);
}
asInterface 這個方法在 Stub 創建的時候會調用,主要功能就是檢索 Binder 物件是否是本地介面的實現,根據 queryLocalInterface () 方法返回值判斷是否使用代理物件,這個檢索過程應該由系統底層支持,如果返回為 null,則創建 Stub 的代理物件,反之使用本地物件來傳輸數據,下面看一下 Binder 為什麼具有遠程通信的能力,因為 Stub 繼承了 Binder 類,Binder 類具體如下:
// Binder
public class Binder implements IBinder {
//...
}
下面是官網對 IBinder 介面的描述:
遠程物件的基礎介面,輕量級遠程過程調用機制的核心部分,專為執行進程內和跨進程調用時的高性能而設計。該介面描述了與可遠程物件交互的抽象協議。不要直接實現這個介面,而是從 Binder 擴展。
這裡我們知道 Binder 實現了 IBinder 介面,也就是說 Binder 具備了遠程通信的能力,當不同進程之間(遠程)之間通信時,顯然使用的是 Stub 的代理物件,這個代理類裡面具體的業務處理邏輯,如下面這個方法,具體如下:
//具體的業務
@Override
public void setName(java.lang.String name) 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.writeString(name);
// 這個方法會最終調用 onTransact 方法
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
這裡主要就是將數據序列化,然後在系統跨進程支持下最終調用 onTransact () 方法,下面是 onTransact () 方法,具體如下:
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{
switch (code){
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_setName:
{
//...
// 最終調用了 Stub 裡面的業務方法
this.setName(_arg0);
//...
}
}
}
顯然,這個方法在當系統回調給開發者的時候,傳遞回來的 code 是一個常量,在跨進程時,每個具體的服務(方法)都會對應一個編號,然後根據這個編號來執行相應的服務(業務),這裡說到了最後要執行的具體業務,那麼這個業務要體現在什麼地方呢,從上面可知 Stub 是一個抽象類,那麼它所提供的具體業務必然需要一個具體的實現類來完成,下面實現這個具體的業務類,具體如下:
/**
* Created by jzman
* Powered by 2018/3/8 0008.
*/
public class IPersonImpl extends IPersonAidlInterface.Stub {
private String name;
private int age;
@Override
public void setName(String name) throws RemoteException {
this.name = name;
}
@Override
public void setAge(int age) throws RemoteException {
this.age = age;
}
@Override
public String getInfo() throws RemoteException {
return "My name is "+name+", age is "+age+"!";
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
}
這個類就是對外提供的具體業務類,同時其實例也是一個 Binder 物件。
向客戶端公開介面#
創建一個 Service 以便對外提供具體的業務,具體如下:
// Service
public class PersonService extends Service {
public PersonService() {
}
@Override
public IBinder onBind(Intent intent) {
return new IPersonImpl();
}
}
當外部調用 bindService () 方法綁定服務時,就會調用 onBind () 方法返回 IBinder 物件,這個 IBinder 物件也是具體的業務物件,如這裡的 onBind () 方法返回的也是具體的業務物件,兩者是統一的。此外,創建的 Service 要在 AndroidManifest.xml 文件中聲明,具體如下:
<service
android:name=".PersonService"
android:enabled="true"
android:exported="true"
android:process=":remote">
</service>
其中使用 process 關鍵字表示為該服務開啟一個獨立的進程,remote 可任意,表示進程名稱,":" 將會在主進程(進程名為包名)添加新名稱作為新進程的名稱,如 com.manu.study 將會變成 com.manu.study。
客戶端遠程調用#
通過上面幾步完成了服務的搭建,並將服務運行在獨立進程中,下面主要就是客戶端的具體調用了,具體實現參考如下:
// Client
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private IPersonAidlInterface iPersonAidlInterface;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void bindServiceClick(View view) {
Log.i(TAG,"綁定服務...");
Intent intent = new Intent(this,PersonService.class);
// 綁定服務時自動創建服務
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
public void unbindServiceClick(View view) {
Log.i(TAG,"解除綁定服務...");
unbindService(conn);
}
public void callRemoteClick(View view) {
Log.i(TAG,"遠程調用具體服務...");
try {
iPersonAidlInterface.setName("Tom");
iPersonAidlInterface.setAge(10);
String info = iPersonAidlInterface.getInfo();
System.out.println("這是遠程調用的服務信息:"+info);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 根據實際情況返回 IBinder 的本地物件或其代理物件
iPersonAidlInterface = IPersonAidlInterface.Stub.asInterface(service);
System.out.println("具體的業務物件:"+iPersonAidlInterface);
}
@Override
public void onServiceDisconnected(ComponentName name) {
// Service 意外中斷時調用
}
};
}
上述代碼是客戶端調用具體服務的過程。
驗證 AIDL#
通過前面幾步,服務端與客戶端已經完成,下面來驗證能否調用具體的業務,這裡分兩種情況:
相同進程#
創建 Service 的時候不要在 AndroidManifest.xml 文件中不要使用 process 開啟獨立進程即可,此時服務進程默認與客戶端屬於統一進程,結果如下:
不同進程#
創建 Service 的時候在 AndroidManifest.xml 文件中使用 process 開啟獨立進程即可,這個在上文中提到過,此時,服務進程與客戶端進程位於不同進程,結果如下:
顯然,如果服務與客戶端處於不同進程,也就是常常說的進程間通信,具體是由 IBinder 物件的代理物件完成,反之,使用本地物件,也就是本地的 IBinder 物件,具體就是實現 AIDL 的業務類所生成的物件來完成。
自定義類型#
如何在 AIDL 中使用自定義類型,具體如下:
創建自定義類型#
自定義類型傳輸的就是一個實體物件,這個實體類必須實現 Parcelable 介面,具體如下:
// 自定義類型
public class Work implements Parcelable {
private String title;
private String content;
// getter、setter、Parcelable 省略
}
聲明自定義類型#
創建一個 .aidl 文件聲明剛才定義的類型,注意與具體業務 .aidl 文件的不同,聲明具體如下:
// 在.aidl文件中聲明自定義類型
package com.manu.aidldemo;
parcelable Work;
定義與自定義類型相關的業務#
在定義具體業務的 .aidl 文件中定義與自定義類型相關的業務,具體如下:
// Declare any non-default types here with import statements
import com.manu.aidldemo.Work; //注意
interface IPersonAidlInterface {
//定義與自定義類型相關的業務
Work getWorkInfo();
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
注意:導入自定義類型,否則不識別。
重寫業務實體類#
由於新增了具體業務,也就是 getWorkInfo (),需要具體業務類裡面實現具體的業務,具體如下:
//新增自定義類型相關的業務
@Override
public Work getWorkInfo() throws RemoteException {
Work work = new Work();
work.setTitle("學習AIDL");
work.setContent("Android developer");
return work;
}
遠程調用#
綁定 Service 成功之後會得到 IBinder 物件,然後通過 asInterface () 獲取到 IPersonAidlInterface 物件就可以調用了,具體如下:
public void callRemoteClick(View view) {
Log.i(TAG,"遠程調用具體服務...");
try {
//遠程調用
Work work = iPersonAidlInterface.getWorkInfo();
System.out.println("這是遠程調用的服務信息:title="+work.getTitle()+",content="+work.getContent());
} catch (RemoteException e) {
e.printStackTrace();
}
}
日誌運行截圖如下:
AIDL 的介紹到此結束。