Android Binder入門學(xué)習(xí)筆記
寫在前面
Binder是Android給我們提供的一種跨進程通信方式。理解Binder能幫助我們更好的理解Android的系統(tǒng)設(shè)計,比如說四大組件,AMS,WMS等系統(tǒng)服務(wù)的底層通信機制就都是基于Binder機制的。當然了,Binder機制的底層驅(qū)動實現(xiàn)很復(fù)雜,本文的目的只是為了理清Binder的使用和在應(yīng)用層的結(jié)構(gòu)和流程,對于Binder在底層是如何實現(xiàn)的,目前能力還沒到這一步去分析,不會涉及到。對于這部分,不妨將它看成是一個黑盒子,我們輸入什么,然后底層會給我們提供什么。
代理模式
我們知道,A進程如果想要執(zhí)行B進程的b方法,是沒辦法直接辦得到的,但是通過Binder機制,B進程可以返回給A進程一個代理對象Proxy,然后A進程通過調(diào)用Proxy的方法,由Proxy幫我們將信息傳遞給B進程,從而間接調(diào)用b方法。沒錯,Binder實現(xiàn)過程中用到了代理模式。所以在繼續(xù)前行之前,有必要簡單了解下代理模式先。
代理模式相對來說好理解一些,因為在生活中,到處都有代理的影子,比如說我們想去香港買個Mac,但是自己不方便去,于是我們找了代購;比如說現(xiàn)在年底了要搶火車票,但是在12306手動搶票根本搶不到啊,所以我們找了第三方搶票軟件,它會每隔幾十ms就幫我們查詢一次,有票的話就幫我們下單。這里就以搶火車票為例來說明代理模式的結(jié)構(gòu)。
proxy
模式比較簡單,就直接上代碼了。
// 聲明買票接口 public interface ITicket { boolean buyTicket(); } // 官方的12306 public class Real12306 implements ITicket { @Override public boolean buyTicket() { if (搶票成功) return true; return false; } } // 第三方搶票軟件 public class ThirdParty12306 implements ITicket { private Real12306 real12306; public ThirdParty12306(Real12306 real12306) { this.real12306 = real12306; } @Override public boolean buyTicket() { while (true) { if (real12306.buyTicket()) { return true; } // 10ms查詢一次結(jié)果 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Main { public static void main(String[] args) { // 初始化我們的購票信息 Real12306 real12306 = new Real12306(); ThirdParty12306 thirdParty12306 = new ThirdParty12306(real12306); // 開始不斷搶票,釋放我們的勞動力 thirdParty12306.buyTicket(); } }
使用了代理模式之后,我們就不用時時刻刻盯著12306刷票了,只需要把這些重復(fù)無聊的工作交給代理去幫我們干就好了。
AIDL
一般來說,我們使用Binder都是通過AIDL來完成的。我們新建一個aidl文件,然后定義一個接口,這樣Android Studio就會幫我們生成一個java接口文件。以一個最簡單的接口來說吧。
package example.com.aidl; interface IMath { int add(int a, int b); }
生成的IMath.java文件中,代碼有點亂,整理一下之后,結(jié)構(gòu)大致是這樣子的:
aidl
簡單來說,生成了一個IMath接口,接口內(nèi)定義了一個抽象類IMath.Stub,繼承了Binder,IMath.Stub又有一個內(nèi)部類IMath.Stub.Proxy。IMath.Stub和IMath.Stub.Proxy都實現(xiàn)了IMath這個接口。結(jié)合上面的代理模式,從這里我們就可以猜出,在跨進程通信中,由于各個進程都是獨立的,我們的客戶端拿不到服務(wù)端的IMath.Stub類,只能獲得它的代理IMath.Stub.Proxy,再通過它來間接幫我們訪問IMath.Stub類,從而完成跨進程通信。
Binder流程
看了上面的結(jié)構(gòu)圖之后,估計大家還是看不懂的。不急,我們再結(jié)合上面這個例子來說明。Binder機制是基于C/S模型的,也就是說,需要一個client進程和一個Server進程。Client和Server是相對的,誰發(fā)消息,誰就是Client,誰接收消息,誰就是Server。在實際開發(fā)中,Server進程通常是四大組件中的Service(Service必須在Manifest文件中指定進程名字)。
class RemoteService : Service() { val math = Math() override fun onCreate() { super.onCreate() Log.d(TAG, "onCreate") } override fun onBind(intent: Intent): IBinder { return math } inner class Math : IMath.Stub() { override fun add(a: Int, b: Int): Int { return a + b } } }
在RemoteService中,我們先定義一個Math類,繼承自IMath.Stub,在這里實現(xiàn)我們具體的服務(wù)端邏輯。因為IMath.Stub繼承自Binder,Binder又實現(xiàn)了IBinder接口,所以在onBind()方法中直接返回math對象。接著再來看客戶端的業(yè)務(wù)邏輯。
// 定義ServiceConnection類 inner class MyServiceConnection : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { Log.d(TAG, "onServiceDisconnected") } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { if (service == null) return // 將IBinder轉(zhuǎn)換成IMath math = IMath.Stub.asInterface(service) Log.d(TAG, "result is ${math.add(1, 2)}") } } // 在onCreate中綁定RemoteService val intent = Intent(this, RemoteService::class.java) bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
當連接上Service后,就會回調(diào)客戶端的onServiceConnected()方法,這里傳進來的service是一個BinderProxy對象。
BinderProxy是Binder的代理類,同樣也實現(xiàn)了IBinder接口。我們在Server端返回的明明是一個Math對象,到這里就變成了BinderProxy對象了,是不是有點神奇?別忘了,Math本身就是一個Binder對象。由于是跨進程通信,我們無法直接拿到這個Binder對象,只能由BinderProxy對象來幫助我們完成任務(wù)。至于Binder是怎么變成BinderProxy的,這就是Binder機制的底層原理了,將它當成一個黑盒子就好了。
拿到BinderProxy對象后,再將它轉(zhuǎn)換成我們定義的IMath接口。
// IMath.java private static final java.lang.String DESCRIPTOR = "example.com.aidl.IMath"; public static example.com.aidl.IMath asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof example.com.aidl.IMath))) { return ((example.com.aidl.IMath) iin); } return new example.com.aidl.IMath.Stub.Proxy (obj); } // Binder.java public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { if (mDescriptor != null && mDescriptor.equals(descriptor)) { return mOwner; } return null; }
從asInterface()方法中可以看到,根據(jù)Key值DESCRIPTOR在Binder中匹配mOwner,它是一個IInterface對象。但既然是去取值,就應(yīng)該有地方將他們存進來的,我們好像錯過了什么。這里還得回到Math的初始化過程,Math繼承自IMath.Stub,看一下它的構(gòu)造方法就能明白了。
// IMath.java public Stub() { this.attachInterface(this, DESCRIPTOR); } // Binder.java public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) { mOwner = owner; mDescriptor = descriptor; }
到了這里,IInterface的獲取已經(jīng)很明顯了吧。但其實,這里取出來的是Null。What?為什么?別忘了,RemoteService是運行在一個單獨的進程中的,attachInterface()方法是Binder調(diào)用的。而我們的客戶端拿到的只是BinderProxy,查詢到的IInterface當然是Null了,所以我們還得接著看asInterface()方法。(當然了,如果RemoteService和客戶端運行在同一個進程的話,這里就能直接拿到IInterface了,但這與跨進程通信就沒有半毛錢關(guān)系了。)
return new example.com.aidl.IMath.Stub.Proxy(obj);
直接返回了一個代理對象。后續(xù)我們要跟Server端做交互就得靠它了。比如我們調(diào)用了Proxy.add()方法:
@Override public int add(int a, int b) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain (); int _result; try { // 使用Parcel來寫入數(shù)據(jù)以便于跨進程傳輸 _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(a); _data.writeInt(b); // mRemote是在asInterface中獲得的BinderProxy對象 mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); // 使用Parcel來接收返回值 _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; }
核心方法是mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);。這里的mRemote是客戶端拿到的BinderProxy對象,然后就要開始跨進程傳輸了。又到了黑盒子出現(xiàn)的時候了,客戶端發(fā)起跨進程通信后,服務(wù)端就會在自己進程的onTranscat()方法中收到通知:
@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_add : { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } default: { return super.onTransact(code, data, reply, flags); } } }
在Server端收到信息后,會先通過Parcel將信息解析出來,然后執(zhí)行我們調(diào)用的add()方法,也就是我們在RemoteService中重寫IMath.Stub的add()方法。最后將結(jié)果寫回Parcel中再跨進程傳回給客戶端,從而完成了一次跨進程通信。
如果看到這里,對于Binder的流程還有疑惑的話,那就再來一張時序圖好了。
binder
看圖說話,當我們在客戶端中去bindService()的時候,Server端在onBind()中返回了一個Binder對象,經(jīng)過Binder驅(qū)動的轉(zhuǎn)換,這個Binder到了客戶端中變成了BinderProxy,客戶端接著再把BinderProxy轉(zhuǎn)換成Stub.Proxy,后面我們與Server的跨進程通信就都是通過Stub.Proxy發(fā)起的,然后Binder驅(qū)動會幫我們將數(shù)據(jù)跨進程傳輸給真正的Binder,Binder執(zhí)行完操作后再將結(jié)果寫入由Binder驅(qū)動傳回來。由此完成了一次跨進程通信。
從圖中我們也可以看出通信過程是同步的。當客戶端發(fā)起請求的同時,當前的線程會被掛起,直到結(jié)果返回。所以要注意的是如果請求太耗時的話,不應(yīng)該在主線程中去請求,否則容易出現(xiàn)ANR。給個Systrace直觀感受一下。
Systrace
相應(yīng)的CPU信息是處于休眠狀態(tài)的。
cpu
最后
掌握了Binder的上層原理之后,后面再來深入Framework層學(xué)習(xí)就會簡單一些,這篇文章也是為了后面的學(xué)習(xí)打下基礎(chǔ)。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
- Android Framework Application Framework層簡單介紹
- Android中使用socket使底層和framework通信的實現(xiàn)方法
- android開發(fā)教程之framework增加字符串資源和圖片等resource資源
- Android 往Framework中添加新資源的方法詳解
- Android中關(guān)于Binder常見面試問題小結(jié)
- Android中Binder IPC機制介紹
- Android 圖文詳解Binder進程通信底層原理
- Android中的binder機制詳解
- Android中Binder詳細學(xué)習(xí)心得
- Android?Framework如何實現(xiàn)Binder
相關(guān)文章
Android網(wǎng)絡(luò)編程之UDP通信模型實例
這篇文章主要介紹了Android網(wǎng)絡(luò)編程之UDP通信模型實例,本文給出了服務(wù)端代碼和客戶端代碼,需要的朋友可以參考下2014-10-10Android UI自定義ListView實現(xiàn)下拉刷新和加載更多效果
這篇文章主要介紹了Android UI自定義ListView實現(xiàn)下拉刷新和加載更多效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11android 中使用TableLayout實現(xiàn)表單布局效果示例
本篇文章主要介紹了android 中使用TableLayout實現(xiàn)表單布局效果示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06ExpandableListView實現(xiàn)二級列表購物車
這篇文章主要為大家詳細介紹了ExpandableListView實現(xiàn)二級列表購物車,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-11-11Retrofit2.0添加Header的方法總結(jié)(推薦)
這篇文章主要介紹了Retrofit2.0添加Header的方法總結(jié)(推薦),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09Android App中用Handler實現(xiàn)ViewPager頁面的自動切換
這篇文章主要介紹了Android App中用Handler實現(xiàn)ViewPager頁面的自動切換的方法,類似于相冊自動播放,主要是切換后要提示當前頁面所在的位置,需要的朋友可以參考下2016-05-05Android Studio preview 不固定及常見問題的解決辦法
preview 可以幫助您預(yù)覽您的布局文件將如何在用戶的設(shè)備上呈現(xiàn)。這篇文章主要介紹了Android Studio preview 不固定及常見問題的解決辦法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05Android使用Canvas?2D實現(xiàn)循環(huán)菜單效果
循環(huán)菜單有很多種自定義方式,我們可以利用ViewPager或者RecyclerView?+?CarouselLayoutManager?或者RecyclerView?+?PageSnapHelper來實現(xiàn)這種效果,今天我們使用Canvas?2D來實現(xiàn)這種效果,感興趣的朋友可以參考下2024-01-01