AIDL
AIDL的核心有兩點(diǎn)
- AIDL是一種跨進(jìn)程通訊方式
這種方式是基于Binder機(jī)制來(lái)進(jìn)行的投慈,Binder本質(zhì)上是基于C/S架構(gòu),Service提供服務(wù)(方法)妈倔,Client使用服務(wù)(方法調(diào)用) - AIDL本質(zhì)上是一種代碼生成的方式
從這一點(diǎn)上來(lái)說(shuō)它和apt代碼生成方式?jīng)]有本質(zhì)區(qū)別博投,AIDL的特殊之處在于它生成的代碼是Binder進(jìn)程間通訊的最外層封裝。
AIDL特點(diǎn)
AIDL的語(yǔ)法和Java基本類似盯蝴,但是也有不同點(diǎn)毅哗,主要在以下幾點(diǎn)
- 支持的類型
- Parcelable和AIDL接口都需要導(dǎo)包
- 沒有public等關(guān)鍵字
- 所有非基本類型的都需要添加in、out捧挺、inout關(guān)鍵字虑绵,用于指明數(shù)據(jù)流向,默認(rèn)為in
支持的類型
默認(rèn)情況下闽烙,AIDL 支持下列數(shù)據(jù)類型:
- Java 編程語(yǔ)言中的所有原語(yǔ)類型(如 int翅睛、long、char黑竞、boolean 等等)
- String
- CharSequence
- Parcelable Parcelable必須顯示加入一個(gè) import 語(yǔ)句捕发,即使這些類型是在與接口相同的軟件包中定義。
- List List 中的所有元素都必須是列表中支持的數(shù)據(jù)類型很魂、其他 AIDL 生成的接口或Parcelable類型扎酷。 可選擇將 List 用作“通用”類(例如,List<String>)遏匆。另一端實(shí)際接收的具體類始終是 ArrayList法挨,但生成的方法使用的是 List 接口。
- Map
Map 中的所有元素都必須是列表中支持的數(shù)據(jù)類型幅聘、其他 AIDL 生成的接口或Parcelable類型凡纳。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)。 另一端實(shí)際接收的具體類始終是 HashMap喊暖,但生成的方法使用的是 Map 接口惫企。 - AIDL對(duì)應(yīng)的接口
AIDL生成文件解析
AIDL最終會(huì)生成一個(gè)繼承自IInterface的接口,先簡(jiǎn)單看下這個(gè)類的結(jié)構(gòu)。這里我做了一些結(jié)構(gòu)優(yōu)化狞尔,看上去更方便一些
public interface IMusicControler extends IInterface{
public static abstract class Stub extends Binder implements IMusicControler{
private static final String DESCRIPTOR = "site.yihome.ipc.IMusicControler";
public static IMusicControler asInterface(IBinder obj){
......
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
......
return super.onTransact(code, data, reply, flags);
}
public static class Proxy implements IMusicControler{
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder()
{
return mRemote;
}
@Override
public void startMusic(Music music) throws RemoteException {
......
}
@Override
public void stopMusic() throws RemoteException {
......
}
@Override
public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException {
......
}
}
}
public void startMusic(Music music) throws RemoteException;
public void stopMusic() throws RemoteException;
public void setMusicStateCallBack(IMusicStateCallBack cb) throws RemoteException;
}
從上面的代碼中丛版,我們可以明顯的看到IMusicControler中以包名定義一個(gè)標(biāo)示DESCRIPTOR,這個(gè)標(biāo)示貫穿整個(gè)通訊過(guò)程偏序,同時(shí)定義一個(gè)三層結(jié)構(gòu)
- 最外層就是我們AIDL中定義的最原始的接口類型IMusicControler页畦,和它定義的方法
- 第二層是一個(gè)抽象類Stub,他是三層中唯一的Binder對(duì)象研儒,用于和Binder交互完成真正的進(jìn)程間通訊
- 第三層是一個(gè)IMusicControler的實(shí)現(xiàn)類Proxy豫缨,和名字一樣是Service端的代理類,進(jìn)程間通訊時(shí)client首先調(diào)用的就是這個(gè)類的方法端朵,然后在通過(guò)Binder調(diào)用遠(yuǎn)程對(duì)象好芭。
如下圖所示,Stub和Proxy本質(zhì)是就是Binder兩端的數(shù)據(jù)封裝和解析層
Proxy主要完成Client調(diào)用時(shí)參數(shù)的轉(zhuǎn)化冲呢,它把方法的參數(shù)轉(zhuǎn)換成Parcel類型舍败,用于Binder通信時(shí)傳輸數(shù)據(jù),同時(shí)也把Binder傳回的Parcel類型的返回值轉(zhuǎn)換成對(duì)應(yīng)我們需要的類型敬拓。
Stub和Proxy類似邻薯,但是Stub主要完成的是Server端的數(shù)據(jù)轉(zhuǎn)化。具體的代碼細(xì)節(jié)我們借助下面AIDL調(diào)用的全過(guò)程來(lái)講解
AIDL調(diào)用全過(guò)程
Client調(diào)用過(guò)程
AIDL的調(diào)用過(guò)程乘凸,我們從ServiceConnection
的onServiceConnected
方法開始
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
musicControler = IMusicControler.Stub.asInterface(service)
......
}
一般來(lái)說(shuō)厕诡,bindService
后在onServiceConnected
的回掉中,我們都會(huì)通過(guò)Stub的asInterface
方法獲取可以操作的IInterface對(duì)象营勤,asInterface
具體細(xì)節(jié)如下
public static IMusicControler asInterface(IBinder obj){
if(obj==null){
return null;
}
IInterface iIn = obj.queryLocalInterface(DESCRIPTOR);
if(iIn!=null&&(iIn instanceof IMusicControler)){
return (IMusicControler) iIn;
}
return new Proxy(obj);
}
在asInterface
中灵嫌,首先通過(guò)DESCRIPTOR去查詢本地接口(即非跨進(jìn)程),如果是進(jìn)程間通訊則會(huì)用這個(gè)IBinder對(duì)象去new一個(gè)Proxy對(duì)象冀偶。也就是說(shuō)我們?cè)贑lient中拿到的IMusicControler事實(shí)上就是Proxy醒第。
Proxy(IBinder remote) {
mRemote = remote;
}
如果這時(shí)調(diào)用startMusic(Music music)
去調(diào)用遠(yuǎn)程服務(wù),事實(shí)上是調(diào)用的Proxy的方法
public void startMusic(Music music) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((music!=null)) {
_data.writeInt(1);
music.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(TRANSACTION_startMusic, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
可以看到Proxy事實(shí)上只是把參數(shù)封裝到了Parcel對(duì)象中进鸠,同時(shí)會(huì)去獲取reply中的數(shù)據(jù)。同時(shí)會(huì)遠(yuǎn)程服務(wù)IBinder的transact
形病,這個(gè)IBinder事實(shí)上是一個(gè)BinderProxy
對(duì)象客年。這里就會(huì)調(diào)用BinderProxy的transact
方法
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
......
try {
return transactNative(code, data, reply, flags);
} finally {
......
}
}
可以看到這個(gè)transact
方法最終會(huì)調(diào)用native方法通過(guò)Binder完成真正的跨進(jìn)程。Client的流程到此結(jié)束漠吻。
Server端調(diào)用過(guò)程
在IPC的Service中量瓜,我們需要在onBind方法中需要返回一個(gè)Stub的實(shí)現(xiàn)類
return new IMusicControler.Stub() {......};
這個(gè)對(duì)象就是Server端的入口類,Client中transactNative
方法最終會(huì)調(diào)用Stub的onTransact
這個(gè)由它自己實(shí)現(xiàn)的方法
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code){
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSACTION_startMusic:
data.enforceInterface(DESCRIPTOR);
Music music;
if ((0!=data.readInt())) {
music = Music.CREATOR.createFromParcel(data);
}else {
music = null;
}
this.startMusic(music);
reply.writeNoException();
break;
......
}
return super.onTransact(code, data, reply, flags);
}
這個(gè)方法會(huì)對(duì)在transact
傳入的code進(jìn)行不同的處理途乃,同時(shí)從Parcel中獲取實(shí)際參數(shù)绍傲,最后調(diào)用Service中的對(duì)應(yīng)方法。
AIDL異步調(diào)用
默認(rèn)情況下AIDL的調(diào)用是同步的,即必須等到Server端執(zhí)行完成后才會(huì)返回烫饼。如何實(shí)現(xiàn)異步調(diào)用呢猎塞,可以看到在前面的transact
中有四個(gè)參數(shù),我們用了其中三個(gè)杠纵,而第四個(gè)參數(shù)flag正是用來(lái)標(biāo)示調(diào)用方式的荠耽,默認(rèn)0標(biāo)示同步調(diào)用,如果需要異步調(diào)用比藻,則需要把flag設(shè)置為FLAG_ONEWAY(這個(gè)過(guò)程想要用AIDL文件進(jìn)行的話只需要在對(duì)應(yīng)的方法前添加oneway關(guān)鍵字)
但是通過(guò)異步調(diào)用铝量,我們是獲取不到返回值的,因此需要接口回掉银亲,但是普通的接口回掉無(wú)法滿足跨進(jìn)程通訊的需求慢叨,這種情況怎么處理呢?
還是Binder务蝠,前面一般情況下Service是充當(dāng)Server端提供方法插爹,Activity作為Client調(diào)用方法,Binder完成Client對(duì)Server的調(diào)用请梢,當(dāng)需要接口回掉時(shí)赠尾,兩者身份就會(huì)反轉(zhuǎn),Activity作為Service提供IBinder對(duì)象毅弧,供Service調(diào)用气嫁,而這個(gè)IBinder對(duì)象則調(diào)用Service的方法通過(guò)參數(shù)傳遞(前面也說(shuō)過(guò),AIDL是支持這種IBinder對(duì)象傳遞的)
Binder的回掉方法是執(zhí)行在子線程中的够坐,這一點(diǎn)可能和Binder的機(jī)制有關(guān)寸宵,有待進(jìn)一步研究
RemoteCallbackList
上面這一種異步回掉只是一種簡(jiǎn)單的情況,當(dāng)一個(gè)Service需要對(duì)很多Client進(jìn)行回掉時(shí)元咙,就需要對(duì)注冊(cè)的回掉進(jìn)行一個(gè)系統(tǒng)的管理了梯影,RemoteCallbackList正是用來(lái)處理這種情況的,它內(nèi)部使用了一個(gè)ArrayMap來(lái)存儲(chǔ)所有的回掉庶香,同時(shí)會(huì)在需要的時(shí)候進(jìn)行調(diào)用甲棍,核心的代碼如下
public class RemoteCallbackList<E extends IInterface> {
ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
public boolean register(E callback) {
return register(callback, null);
}
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
// Flag unusual case that could be caused by a leak. b/36778087
logExcessiveCallbacks();
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
}
public boolean unregister(E callback) {
synchronized (mCallbacks) {
Callback cb = mCallbacks.remove(callback.asBinder());
if (cb != null) {
cb.mCallback.asBinder().unlinkToDeath(cb, 0);
return true;
}
return false;
}
}
public void broadcast(Consumer<E> action) {
int itemCount = beginBroadcast();
try {
for (int i = 0; i < itemCount; i++) {
action.accept(getBroadcastItem(i));
}
} finally {
finishBroadcast();
}
}
}
這個(gè)類并不復(fù)雜,它主要封裝了對(duì)callback常見的操作赶掖,同時(shí)幫助我們做了很多線程同步上的工作感猛。
下步目標(biāo)
了解Binder通訊原理