什么是 RPC 框架:- 很簡單,就是用來實現遠程調用,進程間通信的,不同的系統由不同的實現,android 這里的實現叫 Binder
不同的系統有不同的實現,但是原理基本都相同,理解起來很簡單:
即將調用的方法(接口名、方法名、參數類型、參數)序列化之后發送到遠端,在遠端反序列化之后調用接口的實現類的方法(接口主要是為了使用動態代理),2 端的實現類通過內部的通信實現諸如發射參數,實現等待,接受返回值等遠程跨進程操作。所以我們在實現RPC框架的時候需要選擇合適的序列化與反序列化方式
AIDL 是 Android 特有的 IPC 進程間通訊方式
AIDL 的寫法其實和綁定服務的代碼差不多,IBander 也是 android 默認提供的一個 AIDL 接口
需要注意的是 5.0 之后,不能隱式啟動 service,不能想以前一樣定義 action 來啟動服務了,尤其是不是跨應用啟動服務,這也算是一種安全上的考慮
若是想非常詳細的了解 AIDL ,請看慕課網的科普視頻
AIDL 寫法
-
使用 Android 提供的方式生命一個 AIDL 接口
Snip20171024_3.png
然后在這個AIDL 接口中聲明業務需要的方法
// IBanZheng.aidl
package com.bloodcrown.bcremoteservice.aldl;
// Declare any non-default types here with import statements
interface IBanZheng {
void banZheng();
/**
* 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);
}
這就是系統幫我們創建的 AIDL 接口,在里面我寫了一個 banZheng() 的方法,剩下的都是系統的事了,Android 系統會根據我們聲明的這個 AIDL 接口創建一個相關的 IPC 通訊類出來:
位置在:
系統會在幫我們創建出一個單獨的 aidl 包出來,里面放我們聲明的 AIDL 類,注意不是實現類
詳細的代碼,有點長,系統幫我們添加的代碼都是進行 ipc通訊的
package com.bloodcrown.bcremoteservice.aldl;
// Declare any non-default types here with import statements
public interface IBanZheng extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.bloodcrown.bcremoteservice.aldl.IBanZheng {
private static final java.lang.String DESCRIPTOR = "com.bloodcrown.bcremoteservice.aldl.IBanZheng";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.bloodcrown.bcremoteservice.aldl.IBanZheng interface,
* generating a proxy if needed.
*/
public static com.bloodcrown.bcremoteservice.aldl.IBanZheng asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.bloodcrown.bcremoteservice.aldl.IBanZheng))) {
return ((com.bloodcrown.bcremoteservice.aldl.IBanZheng) iin);
}
return new com.bloodcrown.bcremoteservice.aldl.IBanZheng.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@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_banZheng: {
data.enforceInterface(DESCRIPTOR);
this.banZheng();
reply.writeNoException();
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;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.bloodcrown.bcremoteservice.aldl.IBanZheng {
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;
}
@Override
public void banZheng() 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_banZheng, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
/**
* 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();
}
}
}
static final int TRANSACTION_banZheng = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void banZheng() throws android.os.RemoteException;
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
其實我們主要看這個類:
public static abstract class Stub extends android.os.Binder implements com.bloodcrown.bcremoteservice.aldl.IBanZheng
和我們在綁定服務時,在服務中寫的用戶返回的內部類一樣不一樣,都是繼承 Binder 類,實現我們自己聲明的接口,然后我們使用也是使用這個stub 類,我們的目的就是讓系統幫我們創建出這個 stub 類
- 在 service 中使用這個 stub 類
現在我們寫的內部類直接繼承這個 stub 即可
class MediaIBander extends IBanZheng.Stub {
@Override
public void banZheng() throws RemoteException {
Log.d(TAG, "辦證啦...");
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
}
然后在綁定生命周期函數內返回這個內部類
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "remoteService - onBind...");
return new MediaIBander();
}
- 在 activity 中接受數據,轉換對象類型
public class MyRemoteServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBanZheng = IBanZheng.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
AIDL 深入理解
AIDL 是 android 系統 IPC 進程間通訊協議,但是記住僅僅只是 android ,換個平臺就不是 AIDL 了。
android 中每個進程都有自己獨立的虛擬機 JVM , 每個JVM 的內存時獨立的,所以進程間通信依靠傳遞對象引用是不行的,因為內存是不連續的, A進程 的內存中有的對象,你把對象引用給到 B進程內存里面可是沒這個對象的,所以 google 就提供了 AIDL
AIDL 是一個橋
進程1 的請求會通過 AIDL 發送給系統,系統根據請求標識找到進程2,把進程1 的請求交給進程2去處理,同理進程2處理完后把結果通過 AIDL 再發送給進程1
AIDL 只支持基本數據類型,集合,Parcelable 序列化類型數據的傳輸
AIDL 方法種若要傳遞對象類型,對象類型需要實現 Parcelable 序列化接口。Parcelable 的原理就是把大的數據類型對象,打散成一個個系統能支持的基本數據類型數據,然后集中打個包傳遞過去,到目標進程再組合成對象類型。這個過程也叫打包,拆包
系統幫我們生成的 AIDL 對象,里面一個 Stub 類型的內部類,Stub 里面又有一個 Proxy 類型的內部類
IPC 通信的核心方法就在于期中的 transact 和 onTransact 方法
- transact 會調用系統底層 IPC 去傳遞數據
- onTransact 會接受系統底層 IPC 傳遞過來的數據
我們跟著代碼來看一下:
- 聲明一個 AIDL 類,內部有一個叫 banZheng 的方法
interface IBanZheng {
void banZheng();
}
- 獲取遠程進程代理
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iBanZheng = IBanZheng.Stub.asInterface(service);
}
上面這是客服端獲取遠程服務的 binder ,我們跟進去看看
public static com.bloodcrown.bcremoteservice.aldl.IBanZheng asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.bloodcrown.bcremoteservice.aldl.IBanZheng))) {
// 是同一個進程,返回 Stub 對象本身
return ((com.bloodcrown.bcremoteservice.aldl.IBanZheng) iin);
}
// 不是同一個進程,返回遠程進程的代理
return new com.bloodcrown.bcremoteservice.aldl.IBanZheng.Stub.Proxy(obj);
}
這個方法是 Stub 的方法,我們可以看到,當服務端和客戶端不再同一個進程時,其實我們拿到的只是遠程進程的代理類,這個代理類會幫我們進程 IPC 底層的通訊
- Proxy 調用底層通訊方法
@Override
public void banZheng() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
// 先把數據打包成可以通過 IPC 通訊傳遞
_data.writeInterfaceToken(DESCRIPTOR);
// 調用底層 IPC 方法傳遞數據(此時線程會掛起,也就是卡線程了)
mRemote.transact(Stub.TRANSACTION_banZheng, _data, _reply, 0);
// 等待遠程返回結果(此時線程會激活,重新執行下面任務)
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
Proxy 實現了我們聲明的 AIDL 接口,所以我們掉遠程 binder 的方法時就是調用的 Proxy 的相關方法,我們看上面的方法,先把數據序列化打包,再調用底層的 transact 方法進行 IPC 通訊,然后等待遠程返回結果,這里會卡住線程,所以客戶端的 IPC 請求最好在非 UI 線程執行
- Stub 的 onTransact 方法是服務端核心,服務端在 onTransact 方法種接受到客服端傳過來的參數,然后計算,再把結果寫會去,客戶端才能收到結果,注意服務端的 binder 是在系統的 binder 線程池中執行的,不要我們自己再起線程了。
AIDL 遠程是異步方法
不知道大家有沒有想過,遠端若是耗時方法的話,會不會卡客戶端線程,我們來測試一下,在兩端分別打印日志,標記關鍵節點時間,遠端延遲3秒返回數據,大家看一下結果就能清楚了
看日志很清楚了吧,客戶端調 AIDL 方法可是會卡主線程的, 所以我們需要注意啊,遠端若是耗時的話,我們需要在客戶端點開線程,再進行 AIDL 遠程通信
我們現在知道了客戶端的 AIDL 方法是異步任務會卡主線程,那么大家就不想知道服務端的方法是怎么運行的嗎
系統有句話這么說:
服務端的 binder 方法運行在系統的 AIDL 線程池里
換句話說,AIDL 在服務端已經跑在單獨的線程里了,不用我們自己開線程了,這里我測試了下,打印服務端啟動時所在線程和 binder 執行任務時所在線程
看日志就清楚了把,系統對于 AIDL 在服務端是有優化的,自動開線程池
AIDL 雙向通訊
AIDL 是單項通訊的,假設我們實現了 AIDL A -> B 進程的通信,那么在 A binder 聯通的時刻把 A 實現的 AIDL.Stub 傳遞給 B ,B 就能通過這個傳過來的 AIDL.Stub 實現 B -> A 的通訊了,AIDL.Stub 對象可以直接 IPC 傳遞的
參考例子可以看:
binder 如何理解
先看圖:
binder 和 AIDL 一樣都是 android 的 IPC 通信機制,不同于 unix 其他的 IPC ,binder 對每個進程的 UID 支持非常好,安全性高
至于我們在使用時感覺 binder 不是夸進程的,那是錯覺。android 4大組件都是有聲明周期的,什么時候 應該怎么運行都是由 ActivityManageService 控制的,看上面的圖應該知道 binder 是組件與 ActivityManageService 通信的,若 Activity 與 Service 在同一個進程,那么內存共享,傳遞的數據可以給過去,這就是我們常見的與 Service 的通信。
若 Activity 與 Service 不在同一個進程,內存不能共享,那么數據就得通過 android 系統特有的 AIDL IPC 通道先序列化然后過去再反序列化,AIDL 的意思就在于跨進程傳遞數據了
詳細的大家請看:
AIDL 解讀補充
AIDL 跨進程通信的核心 Proxy ,大家看構造方法,注意 remote 就是那個遠程 Service,remote 恰恰就是 IBinder 對象,所以 IBinder 才是 android 中 IPC 通訊的基石
private static class Proxy implements com.lypeer.ipcclient.BookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
//此處的 remote 正是前面我們提到的 IBinder service
mRemote = remote;
}
@Override
public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException {
//省略
}
@Override
public void addBook(com.lypeer.ipcclient.Book book) throws android.os.RemoteException {
//省略
}
//省略部分方法
}
我們在客戶端抓換 binder 為指定接口時系統的操作,判斷 binder 在本進程有沒有實例,就是客戶端和服務端是不是在一個進程,是的話就返回對象,queryLocalInterface 方法就是干的這事,不在一個進程的話,就需要 proxy 代理了,proxy 代理了 AIDL 實現的接口方法里的數據序列化,反序列化,至于 IPC 通信靠的還是 binder 去實現
public static com.lypeer.ipcclient.BookManager asInterface(android.os.IBinder obj) {
//驗空
if ((obj == null)) {
return null;
}
//DESCRIPTOR = "com.lypeer.ipcclient.BookManager",搜索本地是否已經
//有可用的對象了,如果有就將其返回
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.lypeer.ipcclient.BookManager))) {
return ((com.lypeer.ipcclient.BookManager) iin);
}
//如果本地沒有的話就新建一個返回
return new com.lypeer.ipcclient.BookManager.Stub.Proxy(obj);
}
我們在客戶端調用 AIDL 的方法,過程是下面這樣走的,核心就是調起 IBinder 類型的 mRemote 對象的 transact 方法,transact 方法就會進行跨進程通信了
@Override
public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException {
//很容易可以分析出來,_data用來存儲流向服務端的數據流,
//_reply用來存儲服務端流回客戶端的數據流
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.lypeer.ipcclient.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//調用 transact() 方法將方法id和兩個 Parcel 容器傳過去
mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);
_reply.readException();
//從_reply中取出服務端執行方法的結果
_result = _reply.createTypedArrayList(com.lypeer.ipcclient.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
//將結果返回
return _result;
}
然后在服務端的 onTransact 方法接受遠程數據,反序列化出來,執行操作過程,然后通過 reply.writeString 把結果寫回去
@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_getBooks: {
//省略
return true;
}
case TRANSACTION_addBook: {
//省略
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
參考自:
使用 Messenger,handle 實現 IPC
Messenger 默認實現了 ibinder ,是對 AIDL 的封裝,大體的使用過程如下,我就不貼代碼了,看代碼的請看:
- 服務端實現一個Handler,在 onBind 時 return mMessenger.getBinder() 返回給自客戶端用來通信
- 客戶端接受 messager 對象 Messenger mService = new Messenger(service);
- 通過 Messenger 發送消息 mService.send(msg);
- 客戶端也給服務端提供一個 Messenger 就能實現雙向通信了
AIDL中的 in,out,inout
什么是 in,out,inout ,是 AIDL 聲明參數在進程2端作用域的標記,看下面這個 AIDL 接口
// BookManager.aidl
package com.lypeer.ipcclient;
import com.lypeer.ipcclient.Book;
interface BookManager {
//保證客戶端與服務端是連接上的且數據傳輸正常
List<Book> getBooks();
//通過三種定位tag做對比試驗,觀察輸出的結果
Book addBookIn(in Book book);
Book addBookOut(out Book book);
Book addBookInout(inout Book book);
}
AIDL 的參數默認是 in 的,一般我們也不寫
- in : 參數在服務端的任何變化不會映射到客戶端
- out:服務端接受的是一個 空內容的變量對象,在 服務端 對這個變量的任何修改都會映射到客戶端
- inout: 服務端接受的是客戶端傳過來的參數有內容,在 服務端 對這個變量的任何修改都會映射到客戶端,而客戶端的修改不會映射到服務端
詳細可以看:
最后我說一下,用 AIDL 雙向通訊的話不用 in,out 來實現,連官方也不是用 in,out 來實現的,其次我沒發現 in,out 的應用場景,暫時作為知識點了解
參考資料
- Android Studio 創建AIDL Demo
-
Android跨進程通信IPC之11——AIDL
這個是一個 IPC 通信的系列,值得一看