深刻解析 Android 的 AIDL 介面
在Android應用框架裡,應用層級的軟體大多是Java類別,而系統層級的軟體大多是C或 C++類別。Android使用JNI介面來達成這個目標。例如,在Android裡有個MediaPlayer.cpp類別,當它搭配上JNI介面之後,在VM上執行的MediaPlayer.java就可以透過JNI介面與MediaPlayer.cpp類別溝通了。
圖1-1
基於這樣的架構,我門就可以盡量將MediaPlayer.java裡的程式邏輯移入MediaPlayer.cpp類別裡,以便加快程式的執行速度。此外,以JNI介面包裝MediaPlayer.cpp類別,並且銜接相互輝映的MediaPlayer.java類別,可以讓眾多的Java應用程式透過MediaPlayer.java來使用MediaPlayer.cpp類別的服務。這是一種非常有用的包裝技巧,藉由包裝來創造更多的應用機會。
為了進一步創造更多的應用機會,可以替MediaPlayer.java類別加上AIDL介面,讓更多的Java應用程式能與MediaPlayer.java類別進行遠距的IPC溝通。如下圖所示:
圖1-2
上圖表示出JNI在Android裡扮演的角色,以及Android框架裡Java與C/C++類別融合的基本架構。上面的图1-2是一個較為抽象的圖,凸显JNI与AIDL的相互呼應之角色。
於此,以高煥堂所寫的第2本Android書:<> 一书里的范例:HalfAdder組件為例,展示其幕后的细节架构。首先看看其JNI介面之上的細節架構,如下UML圖:
圖1-3
上圖1-2幕後的細節架構之一
上圖凸顯了AIDL介面的細節架構。下圖1-4將換個角度,從*.so開發者來看,當我們開發系統層級的C/C++類別時,也能善用JNI,創造C/C++類別的廣大商機。其細節架構如下UML圖:
圖1-4
上圖1-2幕後的細節架構之二
1.3
說明C/C++組件開發
從上圖1-4可看到此范例的C/C++組件部份。其詳細的程式碼,請閱讀高煥堂所寫的第2本Android書:<> 一書之第5~6章。
1.4
說明AIDL介面類別之開發
基於剛才所撰寫的相對應Java類別:Calculator.java,就能順利配上AIDL介面了。
1.4.1
細說AIDL介面與IBinder介面
其實,AIDL介面幕後是仰賴著IBinder介面的。所以,我們的應用程式可以選擇使用IBinder介面,也可以使用AIDL介面。如果採取IBinder介面,就不必使用aidl.exe工具去产生calInterface.java介面定义档了,其介面類別較單純一些,如下圖所示:
圖1-5
僅使用較單純的IBinder介面
由於IBinder介面只提供單一函數(即transact()函數)來進行远距沟通,呼叫起來比較不方便。例如,當Calculator類別有多個函數時,myActivity要如何呼叫它們呢?
可以呼叫IBinder介面的transact()函數,再转而呼叫Calculator的各個函數。由於它並不太方便,所以Android提供Proxy/Stub結構的AIDL介面來化解這個問題,其架構圖如下:
更方便的AIDL介面(其介面類別結構較複雜一些)在本範例裡,將採取AIDL介面,同時也介紹其幕後的IBinder介面,以及其兩這之間的密切關係。
1.4.2細說Stub類別的程式碼
這個Stub類別就是由aidl.exe所产出的;也就是在calInterface.java裡面。并再重复列出calInterface.java程式碼如下:
/*---- calInterface.java ----*/
/*
* This file is auto-generated.
DO NOT MODIFY.
* Original file: calInterface.aidl
*/
package com.misoo.gx06;
import java.lang.String;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Binder;
import android.os.Parcel;
public interface calInterface extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.misoo.gx06.calInterface
{
private static final java.lang.String DESCRIPTOR = "com.misoo.gx06.calInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an calInterface interface,
* generating a proxy if needed.
*/
public static com.misoo.gx06.calInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
com.misoo.gx06.calInterface in = (com.misoo.gx06.calInterface)obj.queryLocalInterface(DESCRIPTOR);
if ((in!=null)) {
return in;
}
return new com.misoo.gx06.calInterface.Stub.Proxy(obj);
}
public android.os.IBinder asBinder()
{
return this;
}
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_EvDigitPress:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.EvDigitPress(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_EvCPress:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.EvCPress();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_EvPlusPress:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.EvPlusPress();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_EvAssignPress:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.EvAssignPress();
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.misoo.gx06.calInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
public java.lang.String EvDigitPress(int d) 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.writeInt(d);
mRemote.transact(Stub.TRANSACTION_EvDigitPress, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public java.lang.String EvCPress() 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);
mRemote.transact(Stub.TRANSACTION_EvCPress, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public java.lang.String EvPlusPress() 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);
mRemote.transact(Stub.TRANSACTION_EvPlusPress, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public java.lang.String EvAssignPress() 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);
mRemote.transact(Stub.TRANSACTION_EvAssignPress, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_EvDigitPress = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_EvCPress = (IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_EvPlusPress = (IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_EvAssignPress = (IBinder.FIRST_CALL_TRANSACTION + 3);
}
public java.lang.String EvDigitPress(int d) throws android.os.RemoteException;
public java.lang.String EvCPress() throws android.os.RemoteException;
public java.lang.String EvPlusPress() throws android.os.RemoteException;
public java.lang.String EvAssignPress() throws android.os.RemoteException;
}
這個calInterface.java檔案是由aidl.exe工具程式所产出的。
首先,calInterface界面继承了Android提供的Iinterface界面,如下述程式碼:
public interface calInterface extends android.os.IInterface
{
………
}這個類別定義又內含有一個Stub類別,其繼承Android提供的Binder父類別,而且實作calInterface介面,如下述程式碼:
public interface calInterface extends android.os.IInterface
{
……….
public static abstract class Stub extends android.os.Binder implements
com.misoo.gx06.calInterface
{
……….
private static class Proxy implements com.misoo.gx06.calInterface
{
………
}
………..
}
由於Stub類別继承了Binder父類別,而Binder又實作了IBinder介面,所以Stub類別支持兩個介面:IBinder和calInterface。其內含的Proxy类別也支持calInterface。
圖1-7
IBinder與calInterface介面之關係
1.4.4細說CalBinder類別的程式碼
基於這個結構,就可以從Stub類別衍生(Derive)出子類別:calBinder,其程式碼為:
/*---- CalBinder.java ----*/
package com.misoo.gx06;
import android.os.RemoteException;
public class CalBinder extends calInterface.Stub{
private Calculator mCal = null;
public CalBinder(){
mCal = new Calculator();
}
@Override
public String EvAssignPress() throws RemoteException {
mCal.EvAssignPress();
return mCal.retStr;
}
@Override
public String EvCPress() throws RemoteException {
mCal.EvCPress();
return mCal.retStr;
}
@Override
public String EvDigitPress(int d) throws RemoteException {
mCal.EvDigitPress(d);
return mCal.retStr;
}
@Override
public String EvPlusPress() throws RemoteException {
mCal.EvPlusPress();
return mCal.retStr;
}
}
其中,指令:
public CalBinder(){
mCal = new Calculator();
}
誕生了一個Calculator物件,如此與Calculator類別銜接起來了。
1.4.5函數呼叫的情境
此範例程式需要用到各AIDL類別的詳細函數如下圖所示:
圖1-8AIDL各類別裡的函數及其關係
只要對物件觀念和技術較為熟悉的話,就可看出來其並不太複雜,只是基本物件觀念之應用而已。更詳細之說明,請看高煥堂所寫的第2本Android書:<> 一書。