3個問題:什麼是Binder?拿來幹什麼?怎麼用?
什麼是Binder?拿來幹什麼?
Binder 是Android 中的一個類,實現了IBinder 接口。
從IPC 角度:Binder 是 Android 中的一種跨進程通信方式,也可以理解爲一種虛擬的物理設備,它的設備驅動是 /dev/binder,該方式在 Linux 中沒有,
從 Android Framework 角度:Binder 是 ServiceManger 連接各種 Manager(ActivityManager、WindowManger等)和相應 MangerService 的橋樑
從 Android 應用層: Binder 是客戶端和服務端進行通信的媒介,當 bindService 時,服務端會返回一個包含了服務端業務調用的 Binder 對象,通過這個Binder 對象,客戶端就可以獲取服務端提供的服務或數據,這裏的服務包括普通服務和基於 AIDL 的服務
怎麼用?
AIDL 文件創建:
1、創建 實現 Parcelable 接口的操作實體類 Book,以便序列化和反序列化
package com.example.yhadmin.aidldemo.bean;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
2、創建 aidl 文件夾(和java文件夾平行同級),在其中創建接口 aidl 文件和實體類的映射 aidl 文件
在 main 文件夾下新建 aidl 文件夾,使用的包名要和 java 文件夾的包名一致:
2.1先創建實體類的映射 aidl 文件,Book.aidl:
// Book.aidl
package com.example.yhadmin.aidldemo.bean;
// 聲明映射的實體類名稱與類型,還要和聲明的實體類在一個包裏,自定義的對象需要聲明爲 parcelable 類型
parcelable Book;
在其中聲明映射的實體類名稱與類型,注意:這個 Book.aidl 的包名要和實體類包名一致
2.2、然後創建接口 aidl 文件,IBookManager.aidl:
// IBookManager.aidl
package com.example.yhadmin.aidldemo;
// Declare any non-default types here with import statements
import com.example.yhadmin.aidldemo.bean.Book;
/**
*IBookManager 是自定義的一個接口,裏面有我們自定義的方法, 這裏自定義了getBookList、addBook兩個方法
*
*/
interface IBookManager {
//除了基本數據類型,其他類型的參數都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
List<Book> getBookList();//用於從遠程服務端獲取圖書列表
void addBook(in Book book);//用於往圖書列表中添加一本書
}
注意:儘管這裏Book 類已經和 IBookManager 位於相同的包中,但是在 IBookManager 中仍然要導入Book 類,不然會拋異常
編譯通過後,會在 gen 目錄下的包名下會有一個IBookManager.java 類,如下:
分析系統爲IBookManager.aidl 生成的 Binder 類,在gen 目錄下
代碼:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: F:\\MyPro\\Example\\AIDLDEMO\\app\\src\\main\\aidl\\com\\example\\yhadmin\\aidldemo\\IBookManager.aidl
*/
package com.example.yhadmin.aidldemo;
public interface IBookManager
extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
//內部類 Stub,實際就是一個Binder 類
public static abstract class Stub
extends android.os.Binder
implements com.example.yhadmin.aidldemo.IBookManager
{
//Binder 的位移標識,一般用當前Binder 的類名錶示
private static final java.lang.String DESCRIPTOR = "com.example.yhadmin.aidldemo.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.yhadmin.aidldemo.IBookManager interface,
* generating a proxy if needed.
*/
/**
* 用於將服務端的 Binder 對象轉換成客戶端所需的 AIDL 接口類型對象,這種轉換過程是區分進程的,如果客戶端
* 和服務端位於同一進程,那麼此方法返回的就是服務端的 Stub 本身,否則返回的是系統封裝後的 Stub.proxy 對象
* @param obj
* @return
*/
public static com.example.yhadmin.aidldemo.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.yhadmin.aidldemo.IBookManager))) {
return ((com.example.yhadmin.aidldemo.IBookManager) iin);
}
return new com.example.yhadmin.aidldemo.IBookManager.Stub.Proxy(obj);
}
/**
* 用於返回當前 Binder 對象
* @return
*/
@Override
public android.os.IBinder asBinder()
{
return this;
}
/**
* 該方法運行於服務端中的 Binder 線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝後交由此方法
* 處理,該方法的原型爲 public Boolean onTransact(int code, android.os.Parcel data,
* android.os.Parcel reply,int flags).服務端通過 code 可以確定客戶端所請求的目標方法是什麼,接着從 data 中
* 取出目標方法所需的參數(如果目標方法有參數),然後執行目標方法。當目標方法執行完畢後,就像 reply 中寫入返回值
* (如果目標方法由返回值), 這就是onTransact 方法的執行過程。
* 需要注意的是,如果此方法返回 false,則客戶端的請求會失敗,因此我們可以利用這個特性來做權限驗證,畢竟我們不希望
* 隨便一個進程都能遠程調用我們的服務
* @param code
* @param data
* @param reply
* @param flags
* @return
* @throws android.os.RemoteException
*/
@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.example.yhadmin.aidldemo.bean.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.example.yhadmin.aidldemo.bean.Book _arg0;
if ((0 != data.readInt())) {//讀取參數,獲取addBook 方法所需的參數
_arg0 = com.example.yhadmin.aidldemo.bean.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
//Stub 內部類中的代理內部類
private static class Proxy
implements com.example.yhadmin.aidldemo.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;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
/**
* 該方法運行在客戶端,當客戶端遠程調用此方法時,它的內部實現是這樣的:首先創建該方法所需要的輸入型 Parcel
* 對象_data、輸出型 Parcel 對象_reply 和返回值對象List;然後把該方法的參數信息寫入 _data中(如果有參數),
* 接着調用 transact 方法來發起 RPC(遠程過程調用)請求,同時當前線程掛起,然後服務端的 onTransact 方法會被
* 調用,直到 RPC 過程返回後,當前線程繼續執行,並從 _reply 中取出 RPC 過程的返回結果,最後返回 _reply中的數據
*
* @return
* @throws android.os.RemoteException
*/
@Override
public java.util.List<com.example.yhadmin.aidldemo.bean.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.example.yhadmin.aidldemo.bean.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//調用IBinder 的transact 方法
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.yhadmin.aidldemo.bean.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* 該方法運行在客戶端,它的執行過程和getBookList 是一樣的,addBook 沒有返回值,故它不需要從 _reply 中取出
* 返回值
* @param book
* @throws android.os.RemoteException
*/
@Override
public void addBook(com.example.yhadmin.aidldemo.bean.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);
}
//聲明瞭我們在IBookManager 接口中聲明的getBookList、addBook 2個方法
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
//除了基本數據類型,其他類型的參數都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
public java.util.List<com.example.yhadmin.aidldemo.bean.Book> getBookList()
throws android.os.RemoteException;
public void addBook(com.example.yhadmin.aidldemo.bean.Book book)
throws android.os.RemoteException;
}
這是有參數的
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*///除了基本數據類型,其他類型的參數都需要標上方向類型:in(輸入), out(輸出), inout(輸入輸出)
@Override
public java.util.List<com.example.yhadmin.aidldemo.bean.Person> getPersonList(java.lang.String name,
int age)
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.yhadmin.aidldemo.bean.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
_data.writeInt(age);
mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.yhadmin.aidldemo.bean.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
由上可知,IBookManager 不但繼承了 IInterface 接口,同時它自己也還是個接口,所有可以在Binder 中傳輸的接口都需要繼承 IInterface 接口,
AIDL 文件的本質是系統爲我們提供了一套快速實現Binder 的工具而已
注意:
1、當客戶端發起遠程請求時,由於當前線程會被掛起直至服務端進程返回數據,所以如果一個遠程方法是很耗時的,則不能在UI 線程中發起此遠程請求
2、由於服務端的Binder 方法運行在 Binder 的線程池中,所以 Binder 方法不管是否耗時都應該採用同步的方式去實現,因爲它已經運行在一個線程中了
Binder 運行在服務端進程,如果服務端進程由於某種原因異常終止,這個時候我們到服務端的Binder 斷裂(稱之Binder 死亡),會導致我們的遠程調用失敗,更關鍵的是我們不知道Binder 連接已經斷裂,就會導致客戶端的功能受到影響。爲了解決該問題,Binder 中提供了兩個配對的方法
linkToDeath 和 unlinkToDeath
Binder 的兩個很重要的方法:
linkToDeath:通過該方法可以給Binder 設置一個死亡代理,當Binder 死亡時,我們就會收到通知,此時我們就可以重新發起連接請求從而恢復連接
unlinkToDeath:Binder 解除之前的綁定
首先,聲明一個 DeathRecipient 對象,DeathRecipient 是一個接口(在IBinder內部),其內部只有一個 binderDied 方法,我們需要實現這個方法,當Binder 死亡的時候,系統就會回調 binderDied 方法,然後我們就可以移除之前綁定的 binder 代理並重新綁定遠程服務
mBookManager = new IBookManager.Stub() {
@Override
public List<Book> getBookList()
throws RemoteException
{
return null;
}
@Override
public void addBook(Book book)
throws RemoteException
{
}
};
mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);//解除綁定
mBookManager=null;
//TODO:這裏重新綁定遠程服務
}
};
在客戶端綁定遠程服務成功後,給 binder 設置死亡代理
IMessageBoxManager.Sub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);//0爲標記位,
經過上面的兩個步驟,就給我們的Binder 設置了死亡代理,當Binder 死亡的時候我們就可以接收到通知了,同時還可通過 Binder 的 isBinderAlive 方法也可判斷 Binder 是否死亡
錯誤:
未引入Person 包導致,導入Bean 後正常
import com.example.yhadmin.aidldemo.bean.Person;