1. 程式人生 > >《犬夜叉2021》我想通過Binder找到你

《犬夜叉2021》我想通過Binder找到你

![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驅動。(這也解釋了為什麼犬夜叉看到的是阿籬的虛影,而不是真身