我們都知道荞雏,在Android中胎撤,系統(tǒng)會為每個進程分配對應的內(nèi)存空間伊脓,這部分內(nèi)存是彼此間相互獨立府寒,不可直接交互的,這樣的設(shè)計是處于安全性以及系統(tǒng)穩(wěn)定性方面考慮的,比如當我們的App奔潰時椰棘,不至于導致其他App無法運行纺棺,甚至死機等情況。那么邪狞,Android中是否就無法實現(xiàn)進程間通信呢祷蝌?答案當然是否定的。Android中進程通信的方式有很多帆卓,比如AIDL就可以實現(xiàn)這樣子的需求巨朦。
1.AIDL
? AIDL(Android Interface Define Language)是一種IPC通信方式,我們可以利用它來定義兩個進程相互通信的接口剑令。他是基于Service實現(xiàn)的一種線程間通信機制糊啡。它的本質(zhì)是C/S架構(gòu)的,需要一個服務器端吁津,一個客戶端棚蓄。
2.AIDL的使用
2.1創(chuàng)建aidl
? 首先我們在AndroidStudio中創(chuàng)建一個Andorid工程,
? 隨后添加一個module碍脏,作為aidl的服務端
? 在aidlserver中創(chuàng)建aild目錄梭依, 同時創(chuàng)建一個aidl文件
// IMyAidlInterface.aidl
package com.yunzhou.aidlserver;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* 自己添加的方法
*/
int add(int value1, int value2);
}
? 這邊可以看到aidl的語法跟JAVA是一樣的,聲明了一個接口典尾,里面定義了aidl服務器端暴露給客戶端調(diào)用的方法役拴。
? 完成這部分操作之后還沒有結(jié)束,我們需要手動編譯程序钾埂,生成aidl對應的Java代碼
2.2實現(xiàn)接口河闰,并向客戶端放開接口
public class MyAidlService extends Service {
public MyAidlService() {
}
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
private IBinder iBinder = new IMyAidlInterface.Stub(){
@Override
public int add(int value1, int value2) throws RemoteException {
return value1 + value2;
}
};
}
? 我們創(chuàng)建了一個service,并在service內(nèi)部聲明了一個IBinder對象褥紫,它是一個匿名實現(xiàn)的IMyAidlInterface.Stub的實例(這部分我們后面講)姜性,同時我們在發(fā)現(xiàn)IMyAidlInterface.Stub實例實現(xiàn)了add方法,這個方法正是我們在aidl中聲明的供客戶端調(diào)用的方法髓考。
2.3客戶端調(diào)用aidl
? 首先在客戶端跟服務器一樣污抬,新建aidl目錄,將服務器端的aidl拷貝到客戶端绳军,這邊特別要注意印机,拷貝后的客戶端的aidl文件包目錄必須與服務器端保持一致,拷貝完后同樣時編譯工程门驾,讓客戶端也生成對應的java文件
? 其次就是在Activity的onCreate中綁定服務
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//綁定服務成功回調(diào)
aidl = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//服務斷開時回調(diào)
aidl = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//do something
bindService();
}
private void bindService(){
Intent intent = new Intent();
//Android 5.0開始射赛,啟動服務必須使用顯示的,不能用隱式的
intent.setComponent(new ComponentName("com.yunzhou.aidlserver", "com.yunzhou.aidlserver.MyAidlService"));
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
? 綁定完服務奶是,就是進行調(diào)用了楣责,具體的頁面細節(jié)這邊不做展示竣灌,就是在Activity中放了一個按鈕,點擊按鈕進行遠程調(diào)用
int result = aidl.add(12, 12);
Log.e(TAG, "遠程回調(diào)結(jié)果:" + result);
? 可以看到秆麸,logcat打印出來結(jié)果初嘹,說明遠程調(diào)用成功了,至此aidl的整個流程就走完了沮趣。
3.AIDL可使用的參數(shù)類型
3.1基本數(shù)據(jù)類型
我們都知道Java有8中基本數(shù)據(jù)類型屯烦,分別為byte,char,short,int,long,float,double,boolean,那這8中數(shù)據(jù)類型是否都能作為aidl的參數(shù)進行傳遞呢?我們可以在aild中嘗試下房铭,并編譯驻龟,看看有沒有錯
void basicTypes(byte aByte, char aChar, short aShort, int anInt, long aLong, float aFloat,
double aDouble, boolean aBoolean);
發(fā)現(xiàn)這樣子定義,無法成功編譯缸匪,經(jīng)過篩查發(fā)現(xiàn)aidl并不能支持short基本數(shù)據(jù)類型翁狐,至于為什么呢,可以看一看Android中的Parcel凌蔬,Parcel是不支持short的露懒,這應該是考慮到兼容性問題吧。
所以基本數(shù)據(jù)類型支持:byte,char,int,long,float,double,boolean
3.2引用數(shù)據(jù)類型
引用數(shù)據(jù)類型根據(jù)官方介紹砂心,可以使用String,CharSequence,List,Map懈词,當然,我們也可以使用自定義數(shù)據(jù)類型计贰。
3.3自定義數(shù)據(jù)類型
自定義數(shù)據(jù)類型钦睡,用于進程間通信的話蒂窒,必須實現(xiàn)Parcelable接口躁倒,Parcelable是類似于Java中的Serializable,Android中定義了Parcelable洒琢,用于進程間數(shù)據(jù)傳遞秧秉,對傳輸數(shù)據(jù)進行分解,編組的工作衰抑,相對于Serializable象迎,他對于進程間通信更加高效。
我們來看下下面的例子
public class User implements Parcelable {
private int id;
private String name;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public User(Parcel in){
//注意順序G河弧@省!注意順序L吠M舫!注意順序S湓瘛=俾摇!
this.id = in.readInt();
this.name = in.readString();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//注意順序!G肓铡稠通!注意順序!V掣尽刁笙!注意順序!@弧采盒!
dest.writeInt(id);
dest.writeString(name);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>(){
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
? 這邊我們定義了一個User類,實現(xiàn)了Parcelable接口蔚润,大致的類結(jié)構(gòu)就是這個樣子的磅氨,需要注意的一點是,Parcelable對數(shù)據(jù)進行分解/編組的時候必須使用相同的順序嫡纠,字段以什么順序分解的烦租,編組時就以什么順序讀取數(shù)據(jù),不然會有問題除盏!
? 創(chuàng)建完實體后叉橱,我們需要創(chuàng)建一個aidl文件,來定義一下我們的User者蠕,否則User在aidl中無法識別
// IMyAidlInterface.aidl
package com.yunzhou.aidlserver;
parcelable User;
? 并在之前的服務器端aidl中新增方法
interface IMyAidlInterface {
int add(int value1, int value2);
List<User> addUser(in User user);
}
? 在service中實現(xiàn)新增的addUser方法
private ArrayList users;
@Override
public IBinder onBind(Intent intent) {
users = new ArrayList<User>();
return iBinder;
}
private IBinder iBinder = new IMyAidlInterface.Stub(){
@Override
public int add(int value1, int value2) throws RemoteException {
return value1 + value2;
}
@Override
public List<User> addUser(User user) throws RemoteException {
users.add(user);
return users;
}
};
? 此時窃祝,server端的目錄就夠如下
? 服務器端一切準備就緒后,我們對客戶端進行操作踱侣,首先粪小,我們將服務端的兩個aidl文件復制到客戶端,包結(jié)構(gòu)必須一致抡句,aidl文件發(fā)生變化不要忘記重新編譯代碼探膊。
? 然后,將User實體也復制到客戶端待榔,并且包結(jié)構(gòu)一致逞壁。
? 最后,在客戶端進行addUser的操作(這邊只是添加了一個按鈕锐锣,每點擊一次就調(diào)用一次addUser)
try {
ArrayList<User> users = (ArrayList<User>) aidl.addUser(new User(12, "demaxiya"));
Log.e(TAG, "遠程回調(diào)結(jié)果:" + users.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
? 此時客戶端的目錄結(jié)構(gòu)如下:
? 運行服務端與客戶端App腌闯,點擊addUser,輸出日下日志雕憔,說明調(diào)用成功
4.AIDL原理
? 要了解aidl原理姿骏,我們需要看一下根據(jù)aidl生成的對應的java代碼了,
public interface IMyAidlInterface extends android.os.IInterface{
public static abstract class Stub extends android.os.Binder implements com.yunzhou.aidlserver.IMyAidlInterface{...}
public int add(int value1, int value2) throws android.os.RemoteException;
public java.util.List<com.yunzhou.aidlserver.User> addUser(com.yunzhou.aidlserver.User user) throws android.os.RemoteException;
}
? 我們可以看到橘茉,生成的代碼結(jié)構(gòu)很簡單工腋,一個靜態(tài)抽象類Stub姨丈,以及aidl中定義的方法,其中Stub肯定時核心擅腰,我們深入閱讀
? Stub的目錄結(jié)構(gòu)也不復雜蟋恬,一個構(gòu)造函數(shù),一個asInterface方法趁冈,一個asBinder方法歼争,一個onTransact方法,一個Proxy代理類渗勘,這邊Proxy與Stub同時實現(xiàn)了我們定義的aidl沐绒,且Proxy中實現(xiàn)了我們在aidl中定義的add/addUser方法。下面兩個int為告訴系統(tǒng)的方法Id
? 在客戶端旺坠,我們綁定服務的時候通過Stub.asInterface()回去aidl對象乔遮,查看asInterface源碼,我們不難發(fā)現(xiàn)取刃,客戶端獲取到的其實時Stub.Proxy,一個遠程服務的代理蹋肮。
//客戶端獲取aidl
aidl = IMyAidlInterface.Stub.asInterface(service);
//Stub的asInterface
public static com.yunzhou.aidlserver.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.yunzhou.aidlserver.IMyAidlInterface))) {
return ((com.yunzhou.aidlserver.IMyAidlInterface)iin);
}
return new com.yunzhou.aidlserver.IMyAidlInterface.Stub.Proxy(obj);
}
? 所以客戶端調(diào)用add/addUser方法其實調(diào)用的時Stub.Proxy中實現(xiàn)的add/addUser,在Stub.Proxy中我們可以看到一句核心代碼
//add
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
//addUser
mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
? 追溯源頭,mRemote其實就是IMyAidlInterface.Stub璧疗,隨意mRemote.transact傳遞到了IMyAidlInterface.Stub.OnTransact, onTransact中執(zhí)行了add/addUser,回調(diào)到了我們在服務端定義Service中聲明IBidner時重寫的add/addUser,這就是AIDL的整個流程坯辩。
private IBinder iBinder = new IMyAidlInterface.Stub(){
@Override
public int add(int value1, int value2) throws RemoteException {
return value1 + value2;
}
@Override
public List<User> addUser(User user) throws RemoteException {
users.add(user);
return users;
}
};
? 下面用一張圖來總結(jié)aidl原理,這邊特別感謝imooc