前言
IPC 系列文章:
建議按順序閱讀。
Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎(chǔ)
Android IPC 之Binder應(yīng)用
Android IPC 之AIDL應(yīng)用(上)
Android IPC 之AIDL應(yīng)用(下)
Android IPC 之Messenger 原理及應(yīng)用
Android IPC 之服務(wù)端回調(diào)
Android IPC 之獲取服務(wù)(IBinder)
Android Binder 原理換個(gè)姿勢就頓悟了(圖文版)
上篇文章分析了Binder作為IPC中的一種在Android里發(fā)揮著重要的作用,本篇將從代碼的角度分析如何使用Binder進(jìn)行進(jìn)程間通信。
通過本篇文章,你將了解到:
1、IPC 基礎(chǔ)
2、IBinder/Binder 簡介
3、編寫跨進(jìn)程的Service
4、Binder 通信Demo
5、AIDL的引入
1、IPC 基礎(chǔ)
網(wǎng)上文章在分析了Binder之后立即拋出AIDL概念,然后一堆代碼,初看讓人比較迷惑,再看也無法完全理解AIDL存在的意義。這里套用一個(gè)比較俗的說法:存在即合理(一個(gè)東西存在有它存在的理由)。同樣適用于程序設(shè)計(jì),為什么要用AIDL?它不會憑空想當(dāng)然的出現(xiàn),一定是它解決了某些問題。接下來我們一步步分析,自然過渡到使用AIDL。
同一進(jìn)程里的訪問
在Java的世界里,一切都是對象,拿到對象后就可以訪問對象的屬性和方法。
對象存在于內(nèi)存的某個(gè)區(qū)域,怎樣拿到對象呢?答案是:引用。
class Student {
private int age;
private String name;
}
private Student getStudent() {
//student 是引用
//該引用指向了堆里面的Student對象
Student student = new Student();
return student;
}
只要拿到了Student引用,就可以操作Student對象。
不同進(jìn)程里的訪問
同一進(jìn)程里的訪問是我們所熟悉的方式,那么不同進(jìn)程間的訪問呢?
假若Student對象在進(jìn)程B里構(gòu)造,而想在進(jìn)程A里使用它。通過上篇文章可知無法直接引用Student,然而可以借助于Binder。

由圖可知:
1、進(jìn)程A調(diào)用Binder提供的接口
2、Binder調(diào)用進(jìn)程B的接口
3、通過Binder的連接,A就可以調(diào)用B
Binder(驅(qū)動)對于上層來說是透明的,這么看起來就像是A直接調(diào)用了B的接口。
2、IBinder/Binder 簡介
既然Binder機(jī)制要為上層提供接口,那么就需要將接口/類暴露出來,比較重要的接口/類是:IBinder/Binder。
IBinder.java:接口
public interface IBinder {
...
//code : 要執(zhí)行動作的標(biāo)示
//data : 從客戶端往服務(wù)端傳遞的序列化后的數(shù)據(jù),不能為空
//reply : 從服務(wù)端返回的序列化后的數(shù)據(jù),可能為空
//附加操作標(biāo)記:0-->表示阻塞等待該方法調(diào)用結(jié)束 1-->表示執(zhí)行該方法后立即返回
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
throws RemoteException {
...
}
}
Binder.java 抽象類
Binder實(shí)現(xiàn)了IBinder:
public class Binder implements android.os.IBinder {
...
//code : 要執(zhí)行動作的標(biāo)示
//data : 從客戶端往服務(wù)端傳遞的序列化后的數(shù)據(jù),不能為空
//reply : 從服務(wù)端返回的序列化后的數(shù)據(jù),可能為空
//附加操作標(biāo)記:0-->表示阻塞等待該方法調(diào)用結(jié)束 1-->表示執(zhí)行該方法后立即返回
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
...
}
...
}
可以看出,transact(xx)和onTransact(xx)參數(shù)很像,類似于layout(xx)-->onLayout(xx)、draw(xx)-->onDraw(xx) 調(diào)用方式,猜測transact(xx)里最終調(diào)用了onTransact(xx)。
整理出兩者有如下聯(lián)系:
1、進(jìn)程B實(shí)現(xiàn)了Binder里的onTransact(xx)方法,并掛出IBiner接口,告訴外界可以調(diào)用這個(gè)接口來獲取B提供的服務(wù)。
2、進(jìn)程A獲取了IBinder接口,并調(diào)用transact(xx),傳遞消息給B。
3、通過Binder驅(qū)動的中轉(zhuǎn),找到該IBinder是進(jìn)程B放出來的,于是調(diào)用onTransact(xx)。
至此,進(jìn)程A就可以發(fā)送消息給進(jìn)程B了。
大致流程如下:

3、編寫跨進(jìn)程的Service
上面問題的關(guān)鍵是:進(jìn)程A如何獲取進(jìn)程B提供的IBinder接口?
明顯的這勢必又是一次IPC過程,恰好可以借助四大組件之一的Service來完成。
在AndroidMenifest.xml里聲明Service的時(shí)候:
<service android:name=".MyService"></service>
默認(rèn)表示該Service與當(dāng)前App運(yùn)行在同一進(jìn)程里。
若要想Service運(yùn)行在單獨(dú)的進(jìn)程里,可增加配置如下:
<service android:name=".MyService" android:process=".hello"></service>
或者
<service android:name=".MyService" android:process=":hello"></service>
其中:
- .hello表示Service運(yùn)行在名為.hello的進(jìn)程里
- :hello表示Service運(yùn)行在名為當(dāng)前進(jìn)程名+hello的進(jìn)程里,舉個(gè)例子當(dāng)前進(jìn)程名為:com.example.myapplication,那么Service運(yùn)行的進(jìn)程名為:com.example.myapplication:hello
配置好如上信息之后,開啟(顯示開啟/綁定開啟)Service之后,Service就運(yùn)行在單獨(dú)的進(jìn)程里了:

4、Binder 通信Demo
基本的東西準(zhǔn)備好了,接著來看看具體的業(yè)務(wù)。
編寫Server端業(yè)務(wù)
1、聲明接口
既然進(jìn)程B要提供給外界服務(wù),那么最好聲明一個(gè)接口:
public interface IMyServer{
//提供給外界調(diào)用
public void say(String word);
}
2、定義Binder子類
為了獲取Binder驅(qū)動發(fā)過來的消息,需要聲明一個(gè)Binder類。
public class MyServerBinder extends Binder {
//匿名內(nèi)部類實(shí)現(xiàn)接口
IMyServer iMyServer = new IMyServer() {
@Override
public void say(String word) {
//為方便起見,僅僅打印客戶端傳遞過來的消息
Log.d("IPC", word + " in process:" + SystemUitl.getAppName(null));
}
};
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
//從序列化后的內(nèi)容里讀取對應(yīng)的字段值
String word = data.readString();
//收到Client發(fā)過來的消息
iMyServer.say(word);
//回復(fù)消息給Client
String replyWord = "I'm fine, and you?";
Log.d("IPC", replyWord + " in process:" + SystemUitl.getAppName(null));
reply.writeString(replyWord);
return true;
}
}
繼承自Binder,并重寫onTransact(xx),該方法里獲取了來自客戶端(進(jìn)程A)的消息,將消息提取出來并交給業(yè)務(wù)接口IMyServer調(diào)用。
同時(shí)將消息回傳給客戶端。
SystemUitl.getAppName(null)工具為獲取當(dāng)前運(yùn)行的進(jìn)程名。
3、編寫Service
Binder準(zhǔn)備好之后,需要將Binder傳遞給客戶端,傳遞的方法是通過Service傳遞。
public class MyService extends Service {
private MyServerBinder myServerBinder = new MyServerBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
//返回IBinder引用給調(diào)用者
return myServerBinder;
}
}
在Service里構(gòu)造了MyServerBinder對象,并在onBind(xx)里將引用傳遞出去。
當(dāng)客戶端綁定這個(gè)Service的時(shí)候就能拿到該IBinder引用(注意:此處客戶端拿到的引用與服務(wù)端不是同一個(gè)引用,后續(xù)會分析此流程)。
編寫Client端業(yè)務(wù)
此時(shí),Server端已經(jīng)編寫完畢,接著來看客戶端如何獲取IBinder引用。
1、實(shí)現(xiàn)ServiceConnection接口
首先實(shí)現(xiàn)一個(gè)ServiceConnection接口,用以當(dāng)綁定關(guān)系確立后回調(diào)該類里的方法:
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//service 為 Server傳遞過來的IBinder引用
//構(gòu)造序列化對象
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
//寫入String
String word = "hello server, how are you ?";
Log.d("IPC", word + " in process:" + SystemUitl.getAppName(null));
data.writeString(word);
try {
//傳遞消息給Server
service.transact(2, data, reply, 0);
//收到Server的回復(fù)消息
String responseWord = reply.readString();
Log.d("IPC", responseWord + " in process:" + SystemUitl.getAppName(null));
} catch (Exception e) {
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
在onServiceConnected(xx)拿到IBinder引用:service。
先構(gòu)造待發(fā)送的數(shù)據(jù),然后調(diào)用IBinder transact(xx)發(fā)送數(shù)據(jù)給服務(wù)端。
2、綁定服務(wù)端的Service
客戶端通過綁定服務(wù)端的Service來建立綁定關(guān)系。
private void bindService() {
Intent intent = new Intent(MainActivity.this, MyService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
用圖來梳理以上流程:

最后來看看通信結(jié)果:

從日志結(jié)果看:Client與Server分別打印了兩條日志,說明通信成功了。
5、AIDL的引入
以上是借助Binder來進(jìn)行兩個(gè)進(jìn)程間簡單通信,功能是實(shí)現(xiàn)了,但是你可能發(fā)現(xiàn)了一些端倪:
1、編寫冗余
在onTransact(xx)里只調(diào)用了say(xx)一個(gè)方法,如果需要調(diào)用另一個(gè)方法,那么就要對code進(jìn)行區(qū)分:
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
switch (code) {
case 1:
iMyServer.say(data.readString());
break;
case 2:
iMyServer.say1(data.readInt());
break;
case 3:
iMyServer.say2(data.readFloat());
break;
}
return true;
}
同樣的,在客戶端發(fā)送消息的時(shí)候也需要區(qū)分code。Server端提供的業(yè)務(wù)只有幾個(gè)接口還好,若是十幾個(gè)接口甚至更多,書寫case不僅是個(gè)體力活,還容易出錯(cuò)。
2、序列化數(shù)據(jù)
在客戶端發(fā)送數(shù)據(jù)前,先將數(shù)據(jù)序列化,在服務(wù)端接收數(shù)據(jù)處理前,先將數(shù)據(jù)反序列化。這個(gè)過程也是個(gè)重復(fù)的體力活,實(shí)際上雙方都不關(guān)心具體的序列化細(xì)節(jié),只知道丟個(gè)參數(shù)進(jìn)去,弄個(gè)參數(shù)出來即可。
3、面向?qū)ο?/strong>
客戶端先要調(diào)用transact(xx),服務(wù)端在onTransact(xx)里調(diào)用say(xx)方法。
這么看起來有點(diǎn)繞,實(shí)際上比較好的方式是客戶端"直接"調(diào)用服務(wù)端的say(xx)方法:

如上,Client看起來直接調(diào)用了Server的接口,就像是Client拿到了Server對象引用然后操作之,和在同一進(jìn)程里的操作一樣,符合面向?qū)ο蟛僮鞯牧?xí)慣,大大降低了違和感。
說了這么多直接使用Binder編碼的缺點(diǎn),那么是否有一種方法能解決上面的問題呢?

沒錯(cuò)就是AIDL。

接下來的文章將著重分析AIDL原理及其使用。
本文基于Android 10.0。
您若喜歡,請點(diǎn)贊、關(guān)注,您的鼓勵(lì)是我前進(jìn)的動力
持續(xù)更新中,和我一起步步為營學(xué)習(xí)Android
1、Android各種Context的前世今生
2、Android DecorView 必知必會
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6、Android invalidate/postInvalidate/requestLayout 徹底厘清
7、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動Handler-Message-Looper解析
9、Android 鍵盤一招搞定
10、Android 各種坐標(biāo)徹底明了
11、Android Activity/Window/View 的background
12、Android Activity創(chuàng)建到View的顯示過
13、Android IPC 系列
14、Android 存儲系列
15、Java 并發(fā)系列不再疑惑
16、Java 線程池系列