一起來聊聊Android基礎之Binder的使用
Binder實現了IBinder介面,通常我們不會直接去實現Binder,而是通過aidl工具來定義介面,自動生成相應的Binder的子類。
從Android Framework角度來說,Binder是ServiceManager連線各種Manager(ActivityManager,WindowManager,等等)和相應ManagerService的橋樑。
從Android應用層來說,Binder是客戶端和服務端進行通訊的媒介,通過bindService,服務端會返回一個Binder物件。同過這個Binder物件,客戶端就可以獲取服務端提供的服務或資料。
我們通過一個簡單的AIDL示例來學習Binder的使用。
AIDL示例:
目錄結構
Book.java:
package com.weijie.aidlapplication.aidl;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by weijie on 17-3-8.
*/
public class Book implements Parcelable {
public int bookId;
public String bookName;
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.bookId);
dest.writeString(this.bookName);
}
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected Book(Parcel in) {
this.bookId = in.readInt();
this.bookName = in.readString();
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
Book.aidl:
package com.weijie.aidlapplication.aidl;
parcelable Book;
IBookManager.aidl
// IBookManager.aidl
package com.weijie.aidlapplication.aidl;
// Declare any non-default types here with import statements
import com.weijie.aidlapplication.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
注意:
1. Android Studio中,新建AIDL檔案:File->New->AIDL
2. Book.aidl和IBookManager.aidl位於相同的包中,但是在IBookManager中仍然要匯入Book類。
IBookManager.java
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /home/weijie/AndroidStudioProjects/AidlApplication/app/src/main/aidl/com/weijie/aidlapplication/aidl/IBookManager.aidl
*/
package com.weijie.aidlapplication.aidl;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.weijie.aidlapplication.aidl.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.weijie.aidlapplication.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.weijie.aidlapplication.aidl.IBookManager interface,
* generating a proxy if needed.
*/
public static com.weijie.aidlapplication.aidl.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.weijie.aidlapplication.aidl.IBookManager))) {
return ((com.weijie.aidlapplication.aidl.IBookManager)iin);
}
return new com.weijie.aidlapplication.aidl.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
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.weijie.aidlapplication.aidl.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.weijie.aidlapplication.aidl.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.weijie.aidlapplication.aidl.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.weijie.aidlapplication.aidl.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.weijie.aidlapplication.aidl.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.weijie.aidlapplication.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.weijie.aidlapplication.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.weijie.aidlapplication.aidl.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_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.weijie.aidlapplication.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.weijie.aidlapplication.aidl.Book book) throws android.os.RemoteException;
}
IBookManager這個類是系統自動生成的,AS中點選”Make Project”即可生成。
IBookManager繼承了IInterface介面,同時本身也是一個介面。
所有可以在Binder中傳輸的介面都需要繼承IInterface介面。
IBookManager包括:
聲明瞭getBookList和addBook兩個方法
聲明瞭兩個整型的id分別用於標識這兩個方法
聲明瞭一個內部類Stub,這個Stub就是一個Binder類。當客戶端和服務端位於不同程序時,方法需要走transact過程,這個邏輯由Stub的內部代理類Proxy完成。
DESCRIPTOR
Binder的唯一標示,一般用當前Binder的類名錶示。
asInterface(android.os.IBinder obj)
用於將服務端的Binder物件轉換成客戶端所需的AIDL介面型別的物件。
這種轉換是區分程序的,如果客戶端和服務端位於同一程序,那麼返回的是服務端的Stub物件本身,否則返回的是系統封裝後的Stub.proxy物件。
我們來看這個示例:
public class LocalService extends Service {
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IBinder mBinder = new LocalBinder();
}
public void onServiceConnected(ComponentName name, IBinder service) {
// 1. 同一程序
mIMyService = ((LocalBinder.LocalBinder)service).getService;
// 2. 同一程序或不同程序
mIMyService = IMyService.Stub.asInterface(service);
}
當客戶端和服務端在同一個程序時,可以直接將IBinder介面轉型為本地自定義Binder,然後通過自定義Binder的getService方法拿到IMyService介面例項。
asInterface的方法適用性更廣,如果客戶端和服務端位於同一程序,那麼返回的是服務端的Stub物件本身,否則返回的是系統封裝後的Stub.proxy物件。推薦使用這一種方法。
asBinder
返回當前的Binder物件
onTransact(int code, android.os.Parcel data, android.os.Parcel reply,int flags)
這個方法執行在服務端的Binder執行緒池中,當客戶端發起跨程序請求時,呼叫該方法。
服務端通過code判斷客戶端請求的方法,接著從data中取出目標方法所需的引數(if have),然後執行目標方法。
當目標方法執行完畢後,就向reply中寫入返回值(if have)。
如果此方法返回false,那麼客戶端的請求會失敗。
Proxy#getBookList
這個方法執行在客戶端,它的內部實現是這樣的:
Start
建立輸入型Parcel物件_data
建立輸出型Parcel物件_reply
建立返回值物件List
寫入_data
呼叫transact方法發起RPC(遠端過程呼叫),同時當前執行緒掛起
呼叫服務端的onTransact方法直到RPC過程返回後,當前執行緒繼續執行
從_reply中取資料
End
Proxy#addBook
這個方法的執行過程和getBookList是一樣的,addBook沒有返回值,所以它不需要從_reply中取出返回值。
注意:
當客戶端發起遠端請求時,由於當前執行緒會被掛起直至服務端程序返回資料,所以如果一個遠端方法是很耗時的,那麼不能在UI執行緒中發起此遠端請求。
由於服務端的Binder方法執行在Binder的執行緒池中,所以Binder方法不管是否耗時都應該採用同步的方式去實現。
AIDL支援的資料型別:
基本資料型別(int,long,char,boolean,double)
String和CharSequence
List:只支援ArrayList,裡面每個元素都必須被AIDL支援
Map:只支援HashMap,裡面的每個元素都必須被AIDL支援,包括key和value
Parcelabel:所有實現了Parcelable介面的物件,必須顯示import進來
AIDL:必須顯示import進來
如果AIDL檔案中用到了自定義的Parcelable物件,那麼必須新建一個和它同名的AIDL檔案,並在其中宣告它為Parcelable型別,如上面的Book.aidl。
AIDL中除了基本資料型別,其它型別的引數必須標上方向:in,out或者inout。
AIDL介面中只支援方法,不支援宣告靜態常量,這一點區別於傳統的介面。
BookManagerService.java
public class BookManagerService extends Service {
private static final String TAG = "BMS";
// CopyOnWriteArrayList支援併發讀/寫,自動進行執行緒同步
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
public BookManagerService() {
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
}
雖然服務端返回的是CopyOnWriteArrayList,但是在Binder中會按照List的規範去訪問資料並最終形成一個新的ArrayList傳遞給客戶端。
AndroidManifest.xml
註冊BookManagerService
<service
android:name=".aidl.BookManagerService"
android:enabled="true"
android:process=":remote"
android:exported="true"></service>
客戶端實現
public class MainActivity extends AppCompatActivity {
public static final String TAG = "weijie";
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
List<Book> list = bookManager.getBookList();
Log.d(TAG, "onServiceConnected: query book list, list type: " + list.getClass().getCanonicalName());
Log.d(TAG, "onServiceConnected: query book list: " + list.toString());
Book newBook = new Book(3, "Android 開發藝術探索");
Log.d(TAG, "onServiceConnected: add book: " + newBook);
bookManager.addBook(newBook);
List<Book> newList = bookManager.getBookList();
Log.d(TAG, "onServiceConnected: query book list: " + newList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Intent intent = new Intent(this, BookManagerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
輸出結果
01-02 09:47:15.101 5504 5504 D weijie : onServiceConnected: query book list, list type: java.util.ArrayList
01-02 09:47:15.102 5504 5504 D weijie : onServiceConnected: query book list: [Id = 1, Name = Android, Id = 2, Name = Ios]
01-02 09:47:15.102 5504 5504 D weijie : onServiceConnected: add book: Id = 3, Name = Android 開發藝術探索
01-02 09:47:15.105 5504 5504 D weijie : onServiceConnected: query book list: [Id = 1, Name = Android,
-----------------