Binder架構(gòu)的組成
Binder框架有3個方面組成:Binder服務端娶耍、Binder驅(qū)動以及客戶端組成想鹰。
Binder服務端:Binder服務端實際上就是一個Binder對象肢础,該對象一旦創(chuàng)建就會開啟一個隱藏的線程慨蛙,該線程用來接收Binder驅(qū)動發(fā)送的消息,然后執(zhí)行onTransact函數(shù),并根據(jù)onTransact的參數(shù)執(zhí)行不同的服務代碼;因此要實現(xiàn)一個Binder服務就得重載onTransact方法蚀瘸。
重載onTransact方法的主要內(nèi)容就是onTransact函數(shù)的參數(shù)轉(zhuǎn)為服務函數(shù)的參數(shù)奏瞬,而onTransact的參數(shù)來自客戶端 調(diào)用的transact方法;因此酬诀,如果transact參數(shù)確定了,那么onTransact的參數(shù)也就確定了。
Binder驅(qū)動:任意一個服務端Binder對象被創(chuàng)建蒸辆,同時會在Binder驅(qū)動中創(chuàng)建一個 mRemote對象酸些,該對象的類型也是一個Binder類逢渔;客戶端就是通過mRemote 來訪問遠程服務。
客戶端:客戶端想要訪問遠程服務,必須要獲取遠程服務在Binder對象中對應的mRemote引用卒密。怎么獲取呢哲身?
獲取到mRemote后就可以調(diào)用transact方法了,在Binder驅(qū)動中,也重載了transact方法,重載的內(nèi)容主要包括下面幾項:
1.以線程間通信的模式,向服務端發(fā)送客戶端傳遞過來的參數(shù)
2.掛起當前線程,當前線程正是客戶端線程裕膀,并等待服務端執(zhí)行完 指定的 服務函數(shù)后通知(notify)
3.接收到服務端線程的通知扰法,然后繼續(xù)執(zhí)行客戶端線程,并返回到客戶端代碼區(qū).
Binder的結(jié)構(gòu)圖如下:
2.如何設(shè)計Binder
2.1 設(shè)計Binder服務端
從代碼角度來說很簡單,就是繼承自Binder然后重寫onTransact方法放接,以下為IMediaPlayerService的例子:
public class IMediaPlayerService extends Binder {
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
return super.onTransact(code, data, reply, flags);
}
public void start(String path){
}
public void stop(){
}
}
當要啟動該服務,只需要在Activity中new一個IMediaPlayerService對象即可宗兼。重寫onTransact方法并從data中獲取客戶端傳遞過來的參數(shù),比如start方法中傳遞過來的path。然而结执,這里有個問題,就是服務端如何確定客戶端傳遞過來path在data中的位置?因此蒙具,這里需要和客戶端約定好。
這里假設(shè)客戶端在傳入包裹data中放入的第一個數(shù)據(jù)就是path浪箭,那么onTransact的代碼可以如下寫:
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code){
case 1001:
data.enforceInterface("IMediaPlayerService");
String path = data.readString();
start(path);
reply.writeString("我是執(zhí)行完返回的結(jié)果");
break;
case 1002:
stop();
break;
}
return super.onTransact(code, data, reply, flags);
}
onTransact中的code表示 客戶端希望調(diào)用服務端的哪個函數(shù),所以,客戶端和服務端要約定好 一組int值叉存,不同的值表示想要調(diào)用不同的服務端函數(shù)。例如這里的1001表示start痰洒,1002表示要調(diào)用stop。
enforceInterface:是某種校驗,和客戶端的writeInterfaceToken是對應的哩至,等下做具體說明。
readString:表示從包裹data中取出一個字符串path供start調(diào)用冈敛。
如果想要返回客戶端執(zhí)行的結(jié)果就可以在reply中調(diào)用Parcel提供的 相關(guān)函數(shù)來寫入相應的結(jié)果荆陆,比如上面的reply.writeString(“我是執(zhí)行完返回的結(jié)果”)命浴。
2.2 Binder客戶端設(shè)計
想要使用服務端,就得獲得Binder驅(qū)動中對應的mRemote的引用。獲取方法下面詳解失暂,然后調(diào)用mRemote的transact方法。transact方法原型如下:
public boolean transact(int code, @NonNull Parcel data,@Nullable Parcel reply, int flags)throws RemoteException;
其中data表示要傳遞給服務端的包裹(Parcel)笙瑟,遠程服務端需要的數(shù)據(jù)都需要放入這個包裹中戒突。包裹只支持原子類型:String今艺、int、long等题涨,以及實現(xiàn)Parcelable接口的對象向挖∷馕#客戶端調(diào)用的代碼可以寫成類似下面這樣的:
IBinder mRemote = null;
String path = "/sdcard/media/xxx.mp4";
int code = 1001;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("IMediaPlayerService"); //和服務端enforceInterfac一一對應
data.writeString(path);
mRemote.transact(code, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();
看到上面的代碼感覺是不是很熟悉,是不是和使用aidl進行IPC中自動生成的代碼很像蛔翅,哈哈。上面分析可知洁灵,data和reply不是new出來的牍汹,而是調(diào)用Parcel.obtain()申請的有决。就和郵局一樣痒芝,你只能使用郵局使用的信封俄精。其中date和reply都是由客戶端提供的康二,data提供服務端需要的數(shù)據(jù)味混,reply是給服務端將返回結(jié)果放入其中的。
writeInterfaceToken:標注遠程服務的名稱拷获,理論上不是必須的,因為客戶端已經(jīng)獲取了遠程服務的mRemote引用,那么就不會調(diào)用了其他的遠程服務九昧。該名稱是Binder驅(qū)動確保客戶端想調(diào)用的是指定的服務端悲敷。
writeString:用于向包裹中寫入一條String類型的數(shù)據(jù)部宿。注意,包裹中添加的內(nèi)容是有序的瓢湃,這個順序必須是客戶端和服務端之前約定好的理张。在服務端的onTransact方法中會按照指定的順序取出數(shù)據(jù)。
最后調(diào)用transact方法:調(diào)用該方法后箱季,客戶端線程進入 Binder驅(qū)動涯穷,Binder驅(qū)動會掛起當前的線程,并向遠程服務中發(fā)送一個消息藏雏,該消息包含客戶端傳進來的包裹拷况,服務端拿到包裹后作煌,進行數(shù)據(jù)解析,然后調(diào)用相應的服務函數(shù)赚瘦,最后將返回結(jié)果寫入reply中粟誓。然后向Binder驅(qū)動發(fā)送一個通知(notify)喚醒客戶端線程,從而使得客戶端線程從Binder驅(qū)動代碼區(qū)返回到客戶端代碼區(qū)起意。
tansact方法中最后一個參數(shù)flag表示IPC的調(diào)用模式鹰服,0表示服務端執(zhí)行完后會返回執(zhí)行結(jié)果,1表示單向的揽咕,服務端不會返回執(zhí)行結(jié)果悲酷。
3.如何獲取Binder對象
使用過AIDL技術(shù)的同學應該都能想到,那就是使用Service亲善。調(diào)用bindService即可设易,bindService函數(shù)原型如下:
public boolean bindService(Intent service, ServiceConnection conn,int flags);
最關(guān)鍵的就是其中的ServiceConnection 蛹头,ServiceConnection 中包含這個函數(shù):
void onServiceConnected(ComponentName name, IBinder service);
請注意onServiceConnected第二個參數(shù)Service顿肺,當客戶端調(diào)用AMS啟動某個Service后,如果Service正常啟動渣蜗,那么AMS就會調(diào)用ActivityThread中的ApplicationThread對象屠尊,調(diào)用參數(shù)中就包含Binder對象的引用,然后在 ApplicationThread中會回調(diào)bindService中的conn接口耕拷。因此讼昆,客戶端就可以在onServiceConnected方法中將service參數(shù)保存為一個全局變量,以供隨時調(diào)用斑胜。這就解決了第一個問題控淡,客戶端如何獲得Binder對象的引用嫌吠。
4.保證包裹類的參數(shù)順序
Android SDK中提供了aidl工具止潘,該工具可以把一個aidl文件轉(zhuǎn)換為一個java文件,在該Java類文件中辫诅,同時重載了onTransact和transact方法凭戴,統(tǒng)一了存入包裹和讀取包裹的參數(shù)。Aidl工具不是必須的炕矮,有經(jīng)驗的程序員完全可以自己寫出參數(shù)統(tǒng)一的包裹存入和包裹讀出的代碼么夫。
下面我們看看aidl文件自動生成的java文件是什么樣的?我們先定義一個aidl文件肤视,如下:
interface IBookManager {
List<Book> getAllBooks();
void addBook(in Book book);
}
注意档痪,aidl文件只支持原子類型和實現(xiàn)了Parcelable接口的類。上面的Book類就實現(xiàn)了Parcelable邢滑。對應的java文件如下:
package com.example.za_zhujiangtao.zhupro;
// Declare any non-default types here with import statements
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.za_zhujiangtao.zhupro.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.example.za_zhujiangtao.zhupro.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.za_zhujiangtao.zhupro.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.za_zhujiangtao.zhupro.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.za_zhujiangtao.zhupro.IBookManager))) {
return ((com.example.za_zhujiangtao.zhupro.IBookManager)iin);
}
return new com.example.za_zhujiangtao.zhupro.IBookManager.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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getAllBooks:
{
data.enforceInterface(descriptor);
java.util.List<com.example.za_zhujiangtao.zhupro.Book> _result = this.getAllBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.example.za_zhujiangtao.zhupro.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.za_zhujiangtao.zhupro.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.za_zhujiangtao.zhupro.IBookManager
{
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 java.util.List<com.example.za_zhujiangtao.zhupro.Book> getAllBooks() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.za_zhujiangtao.zhupro.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getAllBooks, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.za_zhujiangtao.zhupro.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.example.za_zhujiangtao.zhupro.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getAllBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
}
public java.util.List<com.example.za_zhujiangtao.zhupro.Book> getAllBooks() throws android.os.RemoteException;
public void addBook(com.example.za_zhujiangtao.zhupro.Book book) throws android.os.RemoteException;
}
這些代碼主要完成了一下的3個任務:
1.定義了一個interface IBookManager腐螟,內(nèi)部包含aidl文件聲明的所有方法,并且繼承了IInterface,即該interface的實現(xiàn)類需要提供一個asBinder()函數(shù)乐纸。
2.定義一個Proxy類衬廷,該類實現(xiàn)了IBookManager,該類作為客戶端訪問服務端的代理汽绢,所謂代理就是為了前面提到的第二個問題—統(tǒng)一包裹的輸入和讀取參數(shù)吗跋。
3.定義一個Sub類,他是一個抽象類宁昭,繼承了Binder類且實現(xiàn)了IBookManager接口跌宛。之所以是抽象類是因為具體的服務函數(shù)需要程序員自己在Service類中實現(xiàn)。例如上面的onTransact方法中的 addBook方法最終調(diào)用的是程序員自己在Service類中實現(xiàn)的积仗。
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getAllBooks() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
這個就是我在Service類中實現(xiàn)的秩冈。同時,在Sub類中重載了onTransact方法斥扛,由于transact方法內(nèi)部給包裹類寫入順序是由aidl工具決定的入问,因此,在onTransact方法中稀颁,aidl工具自然知道按照何種順序從包裹中取出數(shù)據(jù)芬失。
在Sub類中還定義了一些int型參數(shù),如TRANSACTION_getAllBooks匾灶, TRANSACTION_addBook棱烂, 這些常量與服務函數(shù)對應,onTransact和transact方法的第一個參數(shù)就是code的值就來源于此阶女。
在Sub類中還定義了一個方法颊糜,asInterface:提供這個函數(shù)的原因是服務端提供的服務除了其他進程可以調(diào)用之外,在本服務進程內(nèi)部的其他類也可以調(diào)用秃踩,對于后者則不需要經(jīng)過IPC調(diào)用衬鱼,而直接在進程內(nèi)部調(diào)用。Bindern內(nèi)部有一個queryLocalInterface的方法憔杨,該函數(shù)是通過輸入字符串來判斷來判斷該Binder對象是不是本地Binder對象的引用鸟赫。
總結(jié)下來說就是,當創(chuàng)建一個Binder對象時消别,服務端進程內(nèi)部會創(chuàng)建一個Binder對象抛蚤,Binder驅(qū)動中也會創(chuàng)建一個Binder對象。如果從遠程獲取服務端的Binder寻狂,則只會返回Binder驅(qū)動中的Binder對象岁经。而如果從服務端進程內(nèi)部獲取Binder對象,則會返回服務端本身的Binder對象蛇券。如下圖:
因此缀壤,asInterface函數(shù)正是利用了queryLocalInterface方法朽们,提供了一個統(tǒng)一接口。無論是本地服客戶端還是遠程客戶端诉位,當獲取了Binder對象后骑脱,都可以把該Binder對象作為asInterface的參數(shù),來返回一個IBookManager接口苍糠。
轉(zhuǎn)載自:https://me.csdn.net/zhujiangtaotaise