《犬夜叉2021》我想通過Binder找到你
阿新 • • 發佈:2021-01-14
![image](https://upload-images.jianshu.io/upload_images/8690467-8bc0c03435c02157.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 前言
本故事純屬虛構,如有不通順的邏輯請輕噴。❤️
## 《犬夜叉2021》
### 第一章:我還能找到你嗎,阿籬
犬夜叉和奈落大決戰之後,**四魂之玉、食骨之井**消失,誰也不知道去了哪,而犬夜叉和阿籬再次被分割到**兩個世界**。
於是犬夜叉拜託一位研究世界宇宙的法師——**積木**,來幫助他找到阿籬。
時間轉眼來到了2021年,積木終於發現了這個世界的祕密。。
其實我們所在的整個宇宙叫做**Android宇宙**,犬夜叉和阿籬所處的兩個世界其實是**兩個程序**,兩個世界可以通過**食骨之井**相連線。
所以想讓犬夜叉重新聯絡到阿籬,必須再找到當年的食骨之井。
### 第二章:食骨之井改名Binder井?
“犬夜叉,我終於找到了”
“找到什麼了?是阿籬嗎?阿籬找到了????”
“沒有,不過我找到了關鍵的東西——食骨之井”
“在哪,快帶我去”
於是,積木法師帶著犬夜叉來到一間屋子裡:
這間屋子門面上寫著`《核心屋》`三個大字,犬夜叉一個箭步飛了進去,在裡面果然找到了當年那個`食骨之井`,但是又有點不一樣,因為它被改了名,旁邊一個破碎的板子上寫著——`Binder井`。板子上還同時刻有使用說明:
>Binder井
這口井聯絡這兩個世界,你看到的也許不是真實的,請慎用!
如需使用,請找到當年遺落的四魂之玉,現在它叫`SM之玉(ServiceManager)`。
找到SM之玉,心裡默唸你想聯絡的那個世界那個人,如果她在那個世界的SM之玉碎片中留下了地址,那麼你就能找到她。
“積木法師,你知道**SM之玉**嗎,哪裡可以找到它”,犬夜叉問到。
### 第三章:四魂之玉——ServiceManager
“說到SM之玉,還要從宇宙的起源說起,**Android宇宙**建立初期,誕生了第一個有人的世界(使用者程序),叫做`Init世界`,而SM之玉就是由這個世界建立的。
SM之玉建立後,啟動了`Binder井`,成為了他的守護神。
但是它的**真身**存在於單獨的世界中,無法獲得。為了讓人們能夠使用到它,它特意在每個世界都留下了自己的**碎片(代理)**。”
“在哪在哪,快告訴我”。
“第0街道(控制代碼值固定為0)”,積木法師指了一個方向說到。
### 第四章:阿籬,我想你了
犬夜叉急忙去**第0街道**找到了**SM之玉**的碎片,然後回到`Binder井`旁邊,心裡默唸道:
“
SM之玉,
求求你幫我找到阿籬吧。
”
忽然,`Binder`井刮出一陣狂風,**一個虛影**出現在了犬夜叉的面前。
>是阿籬~
“阿籬,你能聽到我說話嗎?”
“犬夜叉,我能聽到,沒想到還能看到你”,阿籬的虛影說到。
“我想你了,阿籬...”
## 故事End
故事結束了。
再幫大家理一下故事梗概,其實也就是`Binder`的工作流程:
* **阿籬(服務端)** 為了讓**犬夜叉(客戶端)** 找到她,在**四魂之玉(ServiceManager)** 上留下了**他們世界(程序)** 的地址。
* 犬夜叉在**第0街道(控制代碼為0)** 找到了**四魂之玉碎片(ServiceManager代理)**。
* 通過四魂之玉碎片,犬夜叉看到了**阿籬的虛影(服務端代理)**,並通過虛影告訴了阿籬,想她了(通訊)。
當然,故事畢竟是故事,並不能完全說清楚。
所以下面就完整看看`Binder`的工作流程和原理~
## 程式碼實現犬夜叉的需求
首先,我們使用`AIDL`來實現剛才故事中的場景——讓犬夜叉和阿籬兩個不同程序的人說上話:
```java
//IMsgManager.aidl
interface IMsgManager {
String getMsg();
void tell(in String msg);
}
//阿籬
public class AliWorldService extends Service {
private static final String TAG = "lz1";
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private Binder mBinder = new IMsgManager.Stub() {
@Override
public String getMsg() throws RemoteException {
String tellMsg="犬夜叉...是我";
Log.e(TAG, "阿籬:" + tellMsg);
return tellMsg;
}
@Override
public void tell(String msg) throws RemoteException {
Log.e(TAG, "我是阿籬,我收到了你說的:" + msg);
}
};
}
//犬夜叉
public class QycWorldActivity extends Activity {
private static final String TAG = "lz1";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qyc);
Intent i = new Intent(this, AliWorldService.class);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMsgManager msgManager = IMsgManager.Stub.asInterface(service);
try {
String tellMsg="阿籬,是你嗎";
Log.e(TAG, "犬夜叉:" + tellMsg);
msgManager.tell(tellMsg);
String msg = msgManager.getMsg();
Log.e(TAG, "我是犬夜叉,我收到了你說的:" + msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
```
執行,列印結果:
```java
E/lz1: 犬夜叉:阿籬,是你嗎
E/lz1: 我是阿籬,我收到了你說的:阿籬,是你嗎
E/lz1: 阿籬:犬夜叉...是我
E/lz1: 我是犬夜叉,我收到了你說的:犬夜叉...是我
```
## AIDL原理
程式碼比較簡單,伺服器端新建一個`Binder`物件並傳到`onBind`方法中,客戶端`bindservice`之後,獲取到服務端的代理介面,就可以進行方法的呼叫了。
`AIDL`其實只是一個幫助我們實現程序間通訊的工具,它會根據我們寫的`AIDL`檔案程式碼,生成相應的`java`介面程式碼,其內部也是通過`Binder`實現的。
我們可以通過`build——generated——aidl_source_output_dir——debug——out`檔案路徑找到AIDL為我們生成的介面類。程式碼如下:
```java
public interface IMsgManager extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.example.studynote.binder.IMsgManager {
//1
private static final java.lang.String DESCRIPTOR = "com.example.studynote.binder.IMsgManager";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
//2
public static com.example.studynote.binder.IMsgManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.studynote.binder.IMsgManager))) {
return ((com.example.studynote.binder.IMsgManager) iin);
}
return new com.example.studynote.binder.IMsgManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
//4
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getMsg: {
data.enforceInterface(descriptor);
java.lang.String _result = this.getMsg();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_tell: {
data.enforceInterface(descriptor);
java.lang.String _arg0;
_arg0 = data.readString();
this.tell(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.studynote.binder.IMsgManager {
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;
}
//3
@Override
public java.lang.String getMsg() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getMsg, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void tell(java.lang.String msg) 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.writeString(msg);
mRemote.transact(Stub.TRANSACTION_tell, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getMsg = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_tell = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.lang.String getMsg() throws android.os.RemoteException;
public void tell(java.lang.String msg) throws android.os.RemoteException;
}
```
程式碼比較長,我們依次來分析下:
* `DESCRIPTOR`。Binder的唯一標示。
在Stub類的構造方法中,就是通過`attachInterface`方法將當前的Binder和這個唯一標示進行了繫結。
* `asInterface()`。將服務端的Binder物件轉換成客戶端所需的介面型別物件。
這個方法是客戶端呼叫的,在這個方法中,會通過`queryLocalInterface(DESCRIPTOR)`方法,傳入唯一標示,來獲取對應的Binder。
如果是服務端和客戶端在同一個程序,那麼就會返回服務端的Binder物件,也就是Stub物件本身,然後就直接呼叫物件的方法了。
如果在不同程序,也就是我們一般的跨程序情況,就會返回封裝後的Stub.Proxy這個代理物件。
* `Proxy.getMsg/tell`
接著就看看代理類裡面的方法,也就是我們在客戶端(Activity)中實際呼叫的方法。
這其中有兩個比較重要的物件 :`_data物件`和 `_reply`物件,都是`Parcel`型別的,這裡會對資料進行一個序列化操作,這樣才能進行跨程序傳輸。
如果方法傳有引數,就會把引數寫到`_data物件`,然後呼叫`transact方法`發起遠端呼叫請求(RPC),同時當前執行緒掛起,等待服務端執行完請求。
```java
mRemote.transact(Stub.TRANSACTION_getMsg, _data, _reply, 0);
```
可以看到,傳入了一個int型別的`code——TRANSACTION_getMsg`,這個code其實就是為了要確定呼叫的是哪個方法。
等請求結束後,當前執行緒繼續,會從`_reply`物件中取出返回結果。
整個IPC流程就結束了。那伺服器到底是在哪裡進行任務執行的呢?繼續看onTransact方法。
* `onTransact`
`onTransact`方法就是服務端要做的事了,執行在服務端的`Binder執行緒池`中。
當客戶端發起遠端呼叫請求後,會通過系統底層封裝,其實也就是核心層的`Binder`驅動,然後交給服務端的`onTransact`方法。
在該方法中,首先通過`code`知曉是哪個方法,然後就執行目標方法,並將序列化結果寫到`reply`中,RPC過程結束,交給客戶端處理。
```java
case TRANSACTION_getMsg: {
data.enforceInterface(descriptor);
java.lang.String _result = this.getMsg();
reply.writeNoException();
reply.writeString(_result);
return true;
}
```
最後畫張圖總結下AIDL整個流程:
![image](https://upload-images.jianshu.io/upload_images/8690467-433656d5338f9932.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## Binder
經過了上述AIDL的例子,大家是不是對Binder又進一步瞭解了呢?
在java層面,其實`Binder`就是一個實現了`IBinder`介面的類。
真正跨程序的部分還是在客戶端發起遠端呼叫請求之後,系統底層封裝好,交給服務端的時候。而這個系統底層封裝,其實就是發生在`Linux核心`中。
而在核心中完成這個通訊關鍵功能的還是`Binder`,這次不是`Binder`類了,而是`Binder驅動`。
>驅動你可以理解為一種硬體介面,可以幫助作業系統來控制硬體裝置。
`Binder驅動`被新增執行到`Linux核心空間`,這樣,兩個不同程序就可以通過訪問核心空間來完成資料交換:把資料傳給`Binder驅動`,然後處理好再交給對方程序,完成跨程序通訊。
而剛才通過`AIDL`的例子我們可以知道,客戶端在請求服務端通訊的時候,並不是直接和服務端的某個物件聯絡,而是用到了服務端的一個代理物件,通過對這個代理物件操作,然後代理類會把方法對應的`code、傳輸的序列化資料、需要返回的序列化資料`交給底層,也就是Binder驅動。(這也解釋了為什麼犬夜叉看到的是阿籬的虛影,而不是真身