安卓中進程間通信大家可能都非常了解胞谭,我們知道service可以使用aidl方法贞瞒,遠程調(diào)用服務(wù)實現(xiàn)兩個不同app的通信。根據(jù)文檔也能很快實現(xiàn)功能仇哆,但具體是怎么跨越兩個進程進行通信的靶衍,現(xiàn)在跟著我的腳步分析分析吧灾炭。
首先提出產(chǎn)生困惑的幾個問題:
- 哪些代碼是在哪些進程或線程中運行的。
- 是如何傳遞數(shù)據(jù)的颅眶。
- 是如何傳遞方法蜈出。
代碼角度分析
server端
首先service通過binder機制的通信是屬于server-client模式的,server端為Service類涛酗,在Service中需要實現(xiàn)onBind方法铡原,該方法返回實現(xiàn)了IBinder的實例,這里類實例也通常是在Service中實現(xiàn)的商叹。
@Override
public IBinder onBind(Intent intent) {
initSum = intent.getIntExtra("initSum", -1);
Log.d("jw", "onBind: Thread:"+Thread.currentThread().getId());
return remoteService;
}
IBinder remoteService = new IMyAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int add(int a, int b) throws RemoteException {
Log.d("jw", "add thread: "+Thread.currentThread().getId());
return a+b+initSum;
}
}
client端
在client端燕刻,示例代碼如下:
ServiceConnection mAddServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
addService = IAddAidlInterface.Stub.asInterface(service);
serviceConnected = true;
tv.setText("bind success");
}
@Override
public void onServiceDisconnected(ComponentName name) {
serviceConnected =false;
}
};
public void bindAddService(View view) {
Intent intent = new Intent();
intent.setClassName("com.yglx.learnservice", "com.yglx.learnservice.MyService");
bindService(intent, mAddServiceConnection, BIND_AUTO_CREATE);
Log.d("jw", "bindAddService thread: "+Thread.currentThread().getId());
}
客戶端在和服務(wù)器連接成功后,拿到了一個實現(xiàn)了某些接口(即客戶端和服務(wù)器端端aidl生成的
接口)的實例剖笙,之后只需要用這個實例就可以訪問相應(yīng)的接口了卵洗。
交互
可以看出直接使用不是很難,咋一看就是這么一回事的:
1弥咪, 客戶端bindservice过蹂。
2, 神秘的力量判斷這個service是否有binder聚至,如果沒有,代碼執(zhí)行3酷勺。如果有binder直接運行4。
3扳躬, 服務(wù)端運行onbind返回一個binder脆诉。
4勋功, 又一股神秘的力量,把binder拿過來库说,讓代碼運行5。
5片择, 客戶端運行ServiceConnection中的回調(diào)函數(shù)潜的,把binder給到客戶端。
6字管, 客戶端愉快的開始通過那個binder和服務(wù)端通信了啰挪。
7, 客戶端運行binder的相關(guān)方法嘲叔,實際應(yīng)該是一個寫入虛擬內(nèi)存的過程亡呵。
8, 內(nèi)核的神秘力量又弄出一個線程來硫戈,運行服務(wù)端端代碼锰什,即告訴服務(wù)端可以讀虛擬內(nèi)存的數(shù)
據(jù)了,然后返回到內(nèi)核丁逝。
9汁胆, 內(nèi)核的神秘力量又把給到的數(shù)據(jù),讓剛才客戶端的線程繼續(xù)運行下去霜幼。
10嫩码,客戶端和服務(wù)器就圓滿完成了一次跨進程通信了。
以上流程基本就說明了罪既,在應(yīng)用層進程間是如何通信的铸题。
進程間都有哪些線程
先講我們最熟悉的主線程。(這里為了讓大家看清都涉及到的線程琢感,所以假設(shè)服務(wù)端和客戶端都在代碼上都沒有顯示的開啟線程)
- 當客戶端調(diào)用binderSevice方法時丢间,這個時候處于客戶端主線程。
- 如果service沒有啟動猩谊,則在service的oncreate生命周期中為服務(wù)端主線程千劈。
- 如果未有binder綁定過,則服務(wù)端回調(diào)生命周期方法onbind為服務(wù)端主線程牌捷。
- 客戶端回調(diào)ServiceConnection的方法墙牌,這個也是客戶端主線程。
- 客戶端發(fā)起binder的相關(guān)方法暗甥,這個時候在客戶端主線程喜滨。
- 這時服務(wù)端要開始運行協(xié)議中的指定的binder方法了,這里要分兩種情況撤防,一種是如果客戶端和服務(wù)端在同一個進程虽风,則此時運行的協(xié)議方法也是和客戶端一樣的線程;如果客戶端和服務(wù)
端不在同一個進程,則服務(wù)端將會有一個新的線程來執(zhí)行協(xié)議的指定方法辜膝。 - 客戶端得到binder相關(guān)方法端返回值无牵,如果有返回值的話,這里是同步進行的厂抖,即客戶端將阻塞直到服務(wù)端返回數(shù)據(jù)茎毁,這里還是客戶端的主線程(所以客戶端運行遠程調(diào)用方法時,不應(yīng)該在主線程中執(zhí)行)忱辅。
到這里七蜘,第一個問題解決。
傳遞數(shù)據(jù)
說傳遞數(shù)據(jù)的時候墙懂,先簡單說一下自動生成的aidl文件橡卤。
接口文件中有一個繼承了binder的抽象類Stub,Stub里有一個實現(xiàn)了接口的Proxy代理類损搬。這里我注意到一點碧库,其實我們不管在客戶端還是服務(wù)端都用的同一個aidl文件生成的,并且放在aidl文件的包必須一致才不會出錯巧勤。在我的測試代碼中谈为,為了在接口文件中能獲取一些日志信息,所以我直接把在服務(wù)端生成的接口文件拷貝到測試的應(yīng)用工程中踢关。在對日志分析時伞鲫,注意到,這個文件其實是有兩個功能的签舞,一個是用于服務(wù)端的主要是抽象類Stub秕脓,另一個是在客戶端應(yīng)用中使用的Proxy代理類。所以在服務(wù)器端自己不會作為客戶端的話儒搭,可以把Proxy類去除吠架。如果客戶端不會用作服務(wù)器的話,也完全可以把Stub去除搂鲫,把Proxy類抽離出來就可以傍药。
數(shù)據(jù)傳輸中,并不是所有數(shù)據(jù)都能傳輸?shù)幕耆裕嘶绢愋屯夤樟桑瑒t是需要實現(xiàn)了parcel的可序列化的類才能傳遞,和Intent傳輸?shù)臄?shù)據(jù)類似(Intent其實也是一種進程間通信數(shù)據(jù)傳輸?shù)妮d體)擦酌。
- 客戶端調(diào)用binder的協(xié)議方法時俱诸,運行Proxy類中的方法。
比如demo中的add方法:
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
流程是把數(shù)據(jù)的寫入和讀出都是通過Parcel赊舶,這也就是類需要實現(xiàn)Parcel的原因睁搭。
- 客戶端調(diào)用mRetome.transact之后赶诊,經(jīng)過底層binder的神秘力量,將會調(diào)用服務(wù)端transact方法园骆,即服務(wù)端的Stub里的onTransact方法:
case TRANSACTION_add: {
data.enforceInterface(DESCRIPTOR);
Log.d("jw", "aidl stub onTransact: Thread:"+Thread.currentThread().getId());
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
可以看到是先從data中讀取底層傳輸過來的數(shù)據(jù)舔痪,然后取出對應(yīng)的數(shù)據(jù),調(diào)用相應(yīng)的服務(wù)端已經(jīng)實現(xiàn)了的this.add方法锌唾,用reply把數(shù)據(jù)寫回底層binder辙喂。
好了,數(shù)據(jù)傳輸流程就這樣的鸠珠。
傳遞方法
傳遞方法即傳遞回調(diào)方法(本質(zhì)上傳的是一個binder,如同binderService返回一個binder)秋麸,>即有的時候需要服務(wù)端主動發(fā)消息渐排,能讓客戶端收到。這個時候就可以通過客戶端把回調(diào)接口注冊到服務(wù)端中灸蟆,服務(wù)端通過RemoteCallbackList把這些接口(binder)保存起來驯耻,當需要向客戶端發(fā)送消息的時候即可遍歷里面的接口把數(shù)據(jù)通知過去。在回調(diào)的時候其實就相當于客戶端這個時候充當了服務(wù)端炒考。
客戶端把binder寫入到data中
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((messageReceiver != null)) ? (messageReceiver.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_registerReceiver, _data, _reply, 0);
_reply.readException();
在服務(wù)端注冊時返回接口可缚,如同binderService獲取到Ibinder一樣。
case TRANSACTION_registerReceiver: {
data.enforceInterface(DESCRIPTOR);
com.yglx.learnservice.MessageReceiver _arg0;
_arg0 = com.yglx.learnservice.MessageReceiver.Stub.asInterface(data.readStrongBinder());
this.registerReceiver(_arg0);
reply.writeNoException();
return true;
}
總結(jié)
好了一開始提出的三個問題也都解決了斋枢。本文主要也是在應(yīng)用層角度對android進程間通信通過service的一些記錄帘靡,特別在說到binder底層機制上,只用了神秘力量來解釋也是為了不偏離應(yīng)用層這個高度瓤帚。能夠清楚應(yīng)用層的這些情況描姚,就應(yīng)該知道在service時,需要注意一些不要在主線程操作戈次,以及時刻注意處理并發(fā)問題轩勘。
最后附上分析時用的代碼,讀者可以通過拷貝build里aidl生成的接口文件怯邪,然后添加日志的方式绊寻,查看線程的情況,當然筆者代碼也有部分日志輸出代碼悬秉,記得在選中l(wèi)og輸出時對應(yīng)用選擇no-filters澄步。