- 1 AIDL簡介
- 2 為什么要設置AIDL
- 3 AIDL的注意事項
- 4 AIDL的使用
- 5 源碼跟蹤
- 6 AIDL的設計給我們的思考
- 7 總結
1 AIDL簡介
- AIDL是一個縮寫,全程是Android Interface Definition Language,也是android接口定義語言。
- 準確的來說,它是用于定義客戶端/服務器通信接口的一種描述語言。它其實一種IDL語言,可以拿來生成用于IPC的代碼。從某種意義上說它其實是一個模板。
2 為什么要設置AIDL
2.1 IPC的角度
- 設計這門語言的目的是為了實現進程間通信,尤其是在涉及多進程并發情況的下的進程間通信IPC。
- 通過AIDL,可以讓本地調用遠程服務器的接口就像調用本地接口那么簡單,讓用戶無需關注內部細節,只需要實現自己的業務邏輯接口,內部復雜的參數序列化發送、接收、客戶端調用服務端的邏輯,你都不需要去關心了。
2.2 方便角度
在Android process 之間不能用通常的方式去訪問彼此的內存數據。他們把需要傳遞的數據解析成基礎對象,使得系統能夠識別并處理這些對象。因為這個處理過程很難寫,所以Android使用AIDL來解決這個問題
3 使用場景
- 多個客戶端,多個線程并發的情況下要使用AIDL
- 如果你的IPC不需要適用多個客戶端,就用Binder。
- 如果你想要IPC,但是不需要多線程,那就選擇Messager
4 AIDL支持以下數據類型
- Java基本類型,即int、long、char等;
- String;
- CharSequence;
- List
- List中的所有元素都必須是AIDL支持的數據類型、其他AIDL接口或你之前聲明的Parcelable實現類。
- Map
- Map中的所有元素都必須是AIDL支持的數據類型、其他AIDL接口或你之前聲明的Parcelable實現類。
- 其他類型,必須要有import語句,即使它跟.aidl是同一個包下。
- 關于復雜對象
- 傳遞的復雜對象必須要實現Parcelable接口,這是因為Parcelable允許Android系統將復雜對象分解成基本類型以便在進程間傳輸.
- 若客戶端組件和服務分開在不同APP,必須要把該Parcelable實現類.java文件拷貝到客戶端所在的APP,包路徑要一致。
- 另外,需要為這個Parcelable實現類定義一個相應的.aidl文件。與AIDL服務接口.aidl同理,客戶端所在APP的/src/<SourceSet>/aidl目錄下也要有這份副本。
5 AIDL中的方法和變量
- 方法可有零、一或多個參數,可有返回值或void。
- 所有非基本類型的參數都需要標簽來表明這個數據的去向:
- in,表示此變量由客戶端設置;
- out,表示此變量由服務端設置;
- inout,表示此變量可由客戶端和服務端設置;
- 基本類型只能是in。
- AIDL中的定向tag表示在跨進程通信中數據 流向
- in表示數據只能由客戶端流向服務端,out表示數據只能由服務端流行客戶端,而inout則表示數據可在服務端與客戶端之間雙向流通。
- 其中,數據流向是針對客戶端中的那個傳入方法的對象而言。
- in為定向tag的話,表現為服務端將會接受到一個那個對象的完整數據,但是客戶端的那個對象不會因為服務端傳參修改而發生變動;
- out的話表現為服務端將會接收到那個對象的參數為空的對象,但是在服務端對接收到的空對象有任何修改之后客戶端將會同步變動;
- inout為定向tag的情況下,服務端將會接收到客戶端傳來對象的完整信息,并且客戶端將會同步服務對該對象的任何變動。
6 AIDL的使用(復雜對象為例)
6.1 服務端
我們以在Android Studio為例進行講解
6.1.1 創建復雜對象
public class MessageData implements Parcelable {
public long id; //消息的id
public String content; //消息的內容
public long time; //時間
@Override
public String toString() {
return "Message{" +
"id=" + id +
", content='" + content + '\'' +
", time=" + time +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(content);
dest.writeLong(time);
}
public MessageData(Parcel source) {
id = source.readLong();
content = source.readString();
time = source.readLong();
}
public MessageData() {
}
public void readFromParcel(Parcel in) {
id = in.readLong();
content = in.readString();
time = in.readLong();
}
public static final Creator<MessageData> CREATOR = new Creator<MessageData>() {
@Override
public MessageData createFromParcel(Parcel source) {
return new MessageData(source);
}
@Override
public MessageData[] newArray(int size) {
return new MessageData[size];
}
};
}
6.1.2 創建AIDL文件夾
6.1.3 創建AIDL文件(復雜對象及服務)
復雜對象
[MessageData.aidl]
// MessageData.aidl
package com.kai.ling.myapplication;
// Declare any non-default types here with import statements
parcelable MessageData;
服務
// IMyAidlInterface.aidl
package com.kai.ling.myapplication;
// Declare any non-default types here with import statements
import com.kai.ling.myapplication.MessageData;
interface IMyAidlInterface {
/**
* 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 connect();
void sendMessage(inout MessageData message);
}
編譯后會生成對應的aidl文件
6.1.4 書寫服務端server繼承IMyAidlInterface.Stub
public class MyServer extends IMyAidlInterface.Stub {
private static final String TAG = "GEBILAOLITOU";
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void connect() throws RemoteException {
Log.i(TAG, "connect");
}
@Override
public void sendMessage(MessageData message) throws RemoteException {
Log.i(TAG, "MyServer ** sendInMessage **" + message.toString());
}
}
6.1.5 service中返回server 并設置多進程
public class PushService extends Service {
private MyServer myServer=new MyServer();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myServer;
}
}
<service
android:name=".server.PushService"
android:enabled="true"
android:exported="true"
android:process=":push"/>
6.2 客戶端
管理類
public class PushManager {
private static final String TAG = "GEBILAOLITOU";
private PushManager() {
}
private IMyAidlInterface iMyAidlInterface;
private static PushManager instance = new PushManager();
//單例
public static PushManager getInstance() {
return instance;
}
//綁定服務
public void init(Context context) {
//定義intent
Intent intent = new Intent(context, PushService.class);
//綁定服務
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//成功連接
Log.d(TAG, "pushManager ***************成功連接***************");
//通過asInterface獲取
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//斷開連接調用
Log.d(TAG, "pushManager ***************連接已經斷開***************");
}
};
//遠程調用connect()方法
public void connect() {
try {
Log.d(TAG, "pushManager ***************start Remote***************");
iMyAidlInterface.connect();
} catch (RemoteException e) {
e.printStackTrace();
}
}
//遠程調用sendMessage()方法
public void sendMessage(String content) {
MessageData messageData = new MessageData();
messageData.content = content;
try {
iMyAidlInterface.sendMessage(messageData);
} catch (RemoteException e) {
e.printStackTrace();
Log.d(TAG, "pushManager ***************RemoteException***************");
}
}
}
public class MainActivity extends AppCompatActivity {
private boolean isConnected;
private EditText content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化
PushManager.getInstance().init(this);
content = findViewById(R.id.content);
findViewById(R.id.connect).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PushManager.getInstance().connect();
isConnected = true;
}
});
findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isConnected) {
Toast.makeText(MainActivity.this, "請連接", Toast.LENGTH_LONG).show();
}
if (TextUtils.isEmpty(content.getText().toString().trim())) {
Toast.makeText(MainActivity.this, "請輸入", Toast.LENGTH_LONG).show();
}
PushManager.getInstance().sendMessage(content.getText().toString().trim());
}
});
}
}
7 源碼跟蹤
- 在寫完AIDL文件后,編譯器會幫我們自動生成一個同名的.java文件。
- 在我們實際編寫客戶端和服務端代碼的過程中,真正協助我們工作的其實就是這個文件,而.aidl文件從頭到尾都沒有出現過。
- 這個.aidl文件就是為了生成這個對應的.java文件。事實上,就算我們不寫AIDL文件,直接按照它生成的.java文件這樣寫一個.java文件出來。在服務端和客戶端也可以照常使用這個.java類進行跨進程通信。
- AIDL語言只是在簡化我們寫這個.java文件而已,而要研究AIDL是符合幫助我們進行跨境進程通信的,其實就是研究這個生成的.java文件是如何工作的
7.1 .java文件位置
- 它的完整路徑是:app->build->generated->source->aidl->debug->com->gebilaolitou->android->aidl->IMyAidlInterface.java(其中 com.gebilaolitou.android.aidl
是包名,相對應的AIDL文件為 IMyAidlInterface.aidl )。
7.2 IMyAidlInterface .java類分析
結構圖
- 編譯的后IMyAidlInterface.java文件是一個接口,繼承自android.os.IInterface
- IMyAidlInterface內部代碼主要分成兩部分,一個是抽象類Stub 和 原來aidl聲明的basicTypes()、connect()和sendInMessage()方法
7.3 Stub類分析
- Stub類基本結構
- 靜態方法 asInterface(android.os.IBinder obj)
- 靜態內部類 Proxy
- 方法 onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
- 方法 asBinder()
- private的String類型常量DESCRIPTOR
- private的int類型常量TRANSACTION_connect
- private的int類型常量TRANSACTION_sendInMessage
7.3.1 靜態方法 asInterface(android.os.IBinder obj)
public static com.kai.ling.myapplication.IMyAidlInterface asInterface(android.os.IBinder obj) {
//非空判斷
if ((obj == null)) {
return null;
}
// DESCRIPTOR是常量為"com.gebilaolitou.android.aidl.IMyAidlInterface"
// queryLocalInterface是Binder的方法,搜索本地是否有可用的對象
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果有,則強制類型轉換并返回
if (((iin != null) && (iin instanceof com.kai.ling.myapplication.IMyAidlInterface))) {
return ((com.kai.ling.myapplication.IMyAidlInterface) iin);
}
//如果沒有,則構造一個IMyAidlInterface.Stub.Proxy對象
return new com.kai.ling.myapplication.IMyAidlInterface.Stub.Proxy(obj);
}
-
主要的作用就是根據傳入的Binder對象轉換成客戶端需要的IMyAidlInterface接口。
- 如果客戶端和服務端處于同一進程,那么queryLocalInterface()方法返回就是服務端Stub對象本身;
- 如果是跨進程,則返回一個封裝過的Stub.Proxy,也是一個代理類,在這個代理中實現跨進程通信。那么讓我們來看下Stub.Proxy類
7.3.2 onTransact()方法解析
onTransact()方法是根據code參數來處理,這里面會調用真正的業務實現類
- 在onTransact()方法中,根據傳入的code值回去執行服務端相應的方法。其中常量TRANSACTION_connect和常量TRANSACTION_sendInMessage就是code值(在AIDL文件中聲明了多少個方法就有多少個對應的code)。
- 其中data就是服務端方法需要的的參數,執行完,最后把方法的返回結果放入reply中傳遞給客戶端。如果該方法返回false,那么客戶端請求失敗。
7.3.3 靜態類Stub.Proxy
private static class Proxy implements com.kai.ling.myapplication.IMyAidlInterface {
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);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void connect() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_connect, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
//發送消息 客戶端——> 服務器
@Override
public void sendMessage(com.kai.ling.myapplication.MessageData message) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((message != null)) {
_data.writeInt(1);
message.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_sendMessage, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
message.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
}
- 1、Proxy 實現了 com.gebilaolitou.android.aidl.IMyAidlInterfac接口,所以他內部有IMyAidlInterface接口的兩個抽象方法
- 2、Proxy的asBinder()方法返回的mRemote,而這個mRemote是什么時候被賦值的?是在構造函數里面被賦值的。
7.3.3.1 靜態類Stub.Proxy的connect()方法
- 1、 通過閱讀靜態類Stub.Proxy的connect()方法,我們容易分析出來里面的兩個android.os.Parcel_data和_reply是用來進行跨進程傳輸的"載體"。而且通過字面的意思,很容易猜到,*_data用來存儲 客戶端流向服務端 的數據,_reply用來存儲 服務端流向客戶端 的數據。
- 2、通過mRemote. transact()方法,將_data和_reply傳過去
- 3、通過_reply.readException()來讀取服務端執行方法的結果。
- 4、最后通過finally回收l_data和_reply
7.3.3.2 connect() 相關參數
- 關于 transact()方法:這是客戶端和和服務端通信的核心方法,也是IMyAidlInterface.Stub繼承android.os.Binder而重寫的一個方法。
- 調起這個方法之后,客戶端將會掛起當前線程,等候服務端執行完相關任務后,通知并接收返回的_reply數據流。
- 1 方法ID:transact()方法第一個參數是一個方法ID,這個是客戶端和服務端約定好的給方法的編碼,彼此一一對應。在AIDL文件轉話為.java時候,系統會自動給AIDL里面的每一個方法自動分配一個方法ID。而這個ID就是咱們說的常量TRANSACTION_connect和TRANSACTION_sendInMessage這些常量生成了遞增的ID,是根據你在aidl文件的方法順序而來,然后在IMyAidlInterface.Stub中的onTransact()方法里面switch根據第一個參數code即我們說的ID而來。
- 2最后的一個參數:transact()方法最后一個參數是一個int值,代表是單向的還是雙向的。具體大家請參考我們前面的文章Android跨進程通信IPC之8——Binder的三大接口中關于IBinder部分。我這里直接說結論:0表示雙向流通,即_reply可以正常的攜帶數據回來。如果為1的話,那么數據只能單向流程,從服務端回來的數據_reply不攜帶任何數據。注意:AIDL生成的.java文件,這個參數均為0
7.3.4 asBinder()方法
該方法就是返回當前的Binder方法
8 IMyAidlInterface .java流程分析
以上面的例子為例,那么先從客戶端開始
8.1 客戶端
8.1.1 獲取IMyAidlInterface對象
public void init(Context context){
//定義intent
Intent intent = new Intent(context,PushService.class);
//綁定服務
context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//成功連接
Log.i(TAG,"PushManager ***************成功連接***************");
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//斷開連接調用
Log.i(TAG,"PushManager ***************連接已經斷開***************");
}
};
- 客戶端中通過Intent去綁定一個服務端的Service。
- 在onServiceConnected(ComponentName name, IBinder service)方法中通過返回service可以得到AIDL接口的實例。
- 這是調用了asInterface(android.os.IBinder) 方法完成的。在asInterface(android.os.IBinder)我們知道是調用的new com.gebilaolitou.android.aidl.IMyAidlInterface.Stub.Proxy(obj)構造的一個Proxy對象。
- 所以可以這么說在PushManager中的變量IMyAidlInterface其實是一個IMyAidlInterface.Stub.Proxy對象。
8.1.2 調用connect()方法
PushManager類中的iMyAidlInterface其實IMyAidlInterface.Stub.Proxy對象,所以調用connect()方法其實是IMyAidlInterface.Stub.Proxy的connect()方法。
- 1、這里面主要是生成了_data和_reply數據流,并向_data中存入客戶端的數據。
- 2、通過 transact()方法將他們傳遞給服務端,并請求服務指定的方法
- 3、接收_reply數據,并且從中讀取服務端傳回的數據。
通過上面客戶端的所有行為,我們會發現,其實通過ServiceConnection類中onServiceConnected(ComponentName name, IBinder service)中第二個參數service很重要,因為我們最后是用它的transact() 方法,將客戶端的數據和請求發送給服務端去。從這個角度來看,這個service就像是服務端在客戶端的代理一樣,而IMyAidlInterface.Stub.Proxy對象更像一個二級代理,我們在外部通過調用這個二級代理來間接調用service這個一級代理
8.2 服務端流程
在前面幾篇文章中我們知道Binder傳輸中,客戶端調用transact()對應的是服務端的onTransact()函數,我們在IMyAidlInterface.java中看到
查看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_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;
}
case TRANSACTION_connect: {
data.enforceInterface(DESCRIPTOR);
this.connect();
reply.writeNoException();
return true;
}
case TRANSACTION_sendMessage: {
data.enforceInterface(DESCRIPTOR);
com.kai.ling.myapplication.MessageData _arg0;
if ((0 != data.readInt())) {
_arg0 = com.kai.ling.myapplication.MessageData.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.sendMessage(_arg0);
reply.writeNoException();
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
在收到客戶端的 transact()方法后,直接調用了switch選擇,根據ID執行不同操作,因為我們知道是調用的connect()方法,所以對應的code是TRANSACTION_connect,所以我們下case TRANSACTION_connect:的內容,如下:
case TRANSACTION_connect: {
data.enforceInterface(DESCRIPTOR);
this.connect();
reply.writeNoException();
return true;
}
這里面十分簡單了,就是直接調用服務端的connect()方法。