Binder的原理
要想了解AIDL
就需要先了解Binder
的原理,所以這里先說一下Binder
原理,Binder
的原理大概是這樣:
服務器端:當我們在服務端創建好了一個Binder對象后,內部就會開啟一個線程用于接收binder驅動發送的消息,收到消息后會執行相關的服務器代碼。
Binder驅動:當服務端成功創建一個Binder對象后,Binder驅動也會創建一個mRemote對象,該對象的類型也是Binder類,客戶就可以借助這個mRemote對象來訪問遠程服務,注意這里是借助,真正調用的時候需要將這個轉換成對應的對象,比如使用AIDL
的時候就要轉換成AIDL
對象。
客戶端:客戶端要想訪問Binder的遠程服務,就必須獲取遠程服務的Binder對象在binder驅動層對應的mRemote引用。當獲取到mRemote對象的引用后,就可以調用相應Binder對象的暴露給客戶端的方法(如果有方法的話)。
AIDL
AIDL
的本質其實就是系統為我們提供了一種快速實現Binder
的工具,我們完全可以不用AIDL
,自己去寫代碼實現Binder
,但是當你寫出來的時候會發現其實和AIDL
自動生成的代碼一模一樣。我們接下來來分析一下原理,因為AIDL
的實現其實就是快速實現Binder
,所以原理自然離不開Binder
。但是在分析原理之前,我們先將系統根據我們定義的AIDL
文件自動生成的java文件分析一下。比較重要的就是Stub
和它的內部代理類Proxy
。我們說一下重要的方法:
asInterface(android.os.IBinder obj)
用于將服務器的Binder對象轉換成客戶端所需的AIDL
接口類型的對象,這種轉換過程是區分進程的,如果客戶端和服務端位于統一進程,那么返回服務器的Stub
對象本身,否則返回的是系統封裝后的Stub.proxy
對象。
onTransact(int code,android.os.Parcel data,android.os.Parcel reply,int flags)
這個方法運行在服務端的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝后交由服務端 的onTransact
方法來處理。這個方法有四個參數,分別是code
,data
,reply
,flags
.code是確定客戶端請求的方法是哪個,data是目標方法所需的參數,reply是服務器端執行完后的返回值。如果這個方法返回false
,那么客戶端的請求會失敗。
Proxy#getBookList
這里的getBookList
方法就是在自定義的AIDL
文件中定義的方法,這個方法運行在客戶端,當客戶端遠程調用此方法的時候,內部實現是這樣的:首先在代理類中創建該方法所需要的輸入型Parcel對象_data,輸出型Parcel對象_reply和返回值對象List;然后把該方法的參數信息寫入_data
中,接著mRemote
調用transact
方法來發起RPC(遠程過程調用)請求, 同時當前線程掛起,然后服務端的onTransact
方法會被調用,直到RPC返回后,當前線程繼續執行,并從_reply
中取出RPC過程的返回結果并返回(如果有返回值的話),之前創建的參數其實就是onTransact()
方法需要的參數。
說完了重要方法,接下來分析AIDL
原理:
服務端:因為要實現Binder
,必須在服務器端創建一個Binder
對象,如何創建呢?就是newAIDL
接口中的Stub
內部類,代碼示例如:
Binder mBinder=new IBookManager.Stub(){接口方法實現}
其中IBookManager
是系統根據我們自己定義的IBookManager.AIDL
所生成的類。
Binder驅動:在AIDL
中,Binder驅動其實就是Service。
客戶端:要實現客戶端跨進程和服務端通信,必須獲得服務端的Binder
對象在binder驅動層對應的mRemote引用,如何獲得呢?首先綁定遠程服務,綁定成功后的ServiceConnection
中的IBinder service
其實就是mRemote引用
,但是因為是使用AIDL
方式,所以需要在客戶端中調用IBookManager.Stub.asInterface(android.os.IBinder obj)
方法將服務器返回的Binder對象轉換成AIDL
接口,然后就可以通過這個接口去調用服務器的遠程方法了。
根據原理,我們得出AIDL
的使用流程,其實很簡單,大致就是在服務端創建一個Service
,然后創建一個Binder
對象,最后在客戶端得到這個Binder
對象。
AIDL
使用流程:
先建立AIDL
,如果在你建立的AIDL
接口中,有自定義的類,那么,也需要建立這個類的AIDL,并且名字要完全相同。同時在使用的時候,一定要顯示的導入這個類。接下來的流程就是跟Binder的一樣了。
服務器端:創建Binder
對象,并且實現接口中的方法。
客戶端:綁定service,得到Binder
對象在驅動層對應的mRemote引用。
重點
1.當你在客戶端調用服務器的方法的時候,其實是通過代理去訪問,詳情可以看上面的重點方法介紹里的Proxy#getBookList
,所以你在客戶端連續調用兩次服務器的同一個方法的時候,比如,這里的getBookList,你會發現,里面的對象都不一樣。因為每次在調用方法的時候,在代理類中都會創建該方法所需要的參數對象,所以里面的對象會變化。
2.AIDL
中無法使用普通的接口,只能使用AIDL
接口,并且實現AIDL
接口的時候不能用implements
,因為需要實現的接口其實是自定義接口.Stub
,而不是自己定義的那個接口。使用implements
無法實現。
3.解注冊的時候需要使用到RemoteCallbackList
,需要注意的是這個類的beginBroadcast()
和finishBroadcast()
一定要配對使用,否則會出現異常java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast
,特別是在使用for
循環的時候。
4.對于AIDL
中的in
,out
,inout
這里就直接附上一篇別人寫的博客,這篇博客講的很詳細,而且我也贊同他的觀點,紙上得來終覺淺,絕知此事要躬行。
5.當使用客戶端調用服務器的方法的時候,被調用的方法運行在服務器的Binder線程池中,同時客戶端會被掛起,如果此時服務端方法執行耗時的話,就會導致客戶端線程長時間阻塞,如果客戶端線程是UI線程的話,就會導致客戶端ANR,注意的是onServiceConnected(ComponentName name, IBinder service)
和onServiceDisconnected(ComponentName name)
都運行在UI線程,所以不能在這里調用服務端耗時的方法。同理,對于服務端調用客戶端的方法的情況,比如服務端調用客戶端的listener
中的方法的時候也是一樣。即服務端掛起,方法運行在客戶端的Binder線程池中。
6.當服務端因為某種異常原因停止,我們需要重新啟動服務端,這里有兩種方式,因為AIDL
的底層是Binder
,所以可以使用Binder
的linkToDeath
和unlinkToDeath
方法。還有一種方式是在onServiceDisconnected(ComponentName name)
重新綁定。這兩個區別就是第二種方式可以訪問UI,第一種不行,因為像之前說的,onServiceDisconnected(ComponentName name)
是運行在UI線程里的。而第一種方式使用的時候需要設置一個IBinder.DeathRecipient
接口用于接收服務端binder
因為特殊原因消失的通知,當收到通知的時候就會回調binderDied()
方法,我們在這里unlinkToDeath
并且重新綁定service。而這個binderDied()
方法是運行在客戶端的Binder
線程池中的。
Messenger的原理及使用
Messenger大致的原理是這樣的,因為Messenger的底層還是AIDL
,所以,原理和AIDL
差不多。
服務器:首先需要在服務器創建Binder對象,如何創建呢?通過Messenger
來創建,所以我們需要先構造Messenger
對象,對于Messenger
的構造方法有兩種,如下:
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
所以我們需要先構造一個Handler,這個Handler的作用其實就是處理消息。然后我們再通過這個Handler
來構造Messenger
對象,這個Messenger對象其實就是將客戶端發送來的消息傳遞給Handler來處理,然后我們需要得到Binder對象,通過在Service
的onBind
方法中return Messenger.getBinder()
,這樣就得到了Binder
對象。
Binder驅動:跟AIDL
一樣,還是Service
。
客戶端:也是需要得到服務端的Binder
對象在binder驅動層對應的mRemote引用,獲得的方式是將ServiceConnection
中的IBinder service
當做參數傳入Messenger的構造函數中,如:
Messenger mService=new Messenger(service);
然后就可以用mService.send(msg)
給服務器發消息。實現跨進程通信。
因為這里是借助Messenger
,所以無法調用服務器端的方法,只能通過message來傳遞消息。而當服務器需要回應客戶端的時候,就需要客戶端提供一個Messenger
,然后服務器得到這個Messenger
,因為在就像客戶端向服務端發送請求的時候,也是服務器提供一個Messenger
,然后客戶端得到這個Messenger
。那么如何實現呢?因為客戶端和服務器已經建立了連接,所以只需要在客戶端發送消息的時候,通過消息的replyTo
參數向服務器傳入一個Messenger
,然后服務器在接收到客戶端的消息的時候得到通過message
的replyTo
參數得到這個Messenger
,然后利用這個向客戶端發送消息就可以了。主要代碼如下:
在客戶端發送消息給服務器的時候:
message.replyTo=clientMessenger;
服務器接收消息的時候
Messenger clientMessenger=msg.replyTo;
這樣就在服務器端得到了客戶端的Messenger
,然后在服務器端通過clientMessenger.send(message);
就向客戶端發送了消息。
重點
1.對于使用Messenger
而言,底層其實是AIDL
,但是沒有AIDL
靈活,因為這是借助Messenger
來發送消息從而進行消息的傳遞,不能直接調用服務端的方法,而使用AIDL
是直接可以調用服務端的方法。
2.對于服務端的Messenger
的作用是將客戶端傳遞的消息傳遞給Handler
來處理,而客戶端的是發送消息給服務端。
3.Messenger
是以串行的方式處理客戶端發來的消息,當消息多的時候就就不合適了。而AIDL
是可以并發處理客戶端傳來的消息。