Binder
討論到Binder相關(guān)知識(shí)應(yīng)該三天三夜也討論不完,簡(jiǎn)書的 隔壁老李頭 大佬,花了15篇博客,重頭到位系統(tǒng)的介紹了IPC的過程,涉及Linux基礎(chǔ),JNI等先關(guān)知識(shí)也羅列一通,但本文只面向初學(xué)者,所以文章得從基本用法開始.
簡(jiǎn)介
Binder是安卓的一個(gè)類,翻譯成中文被稱作“粘合劑”,他實(shí)現(xiàn)了IBinder的接口
- 從IPC的角度來(lái)看Binder是安卓的一個(gè)跨進(jìn)程的方式 ,深層次的講你可以把他理解為一個(gè)虛擬的物理設(shè)備(下一章 的Binder驅(qū)動(dòng)),改設(shè)備的驅(qū)動(dòng)是/dev/binder,
- 對(duì)于Framwork層,Binder是ServceMananger連接各各Mananger的ManangerService的橋梁。
- 在應(yīng)用層來(lái)說(shuō)Binder是客戶端與服務(wù)端的進(jìn)行通信的媒介,當(dāng)服務(wù)端實(shí)現(xiàn)bindService方法的時(shí)候,要求返回一個(gè)Binder對(duì)象,而客戶端可以通過該對(duì)象來(lái)獲服務(wù) 提供的數(shù)據(jù)和服務(wù)(包括普通的服務(wù)或者基于AIDL的服務(wù))
每層比較完后 ,我們?cè)賮?lái)討論一下Binder到底是什么?
Binder 采用的是面向?qū)ο蟮乃枷耄沂呛艿湫偷拿嫦驅(qū)ο蟮乃枷?,?strong>Binder模型(下一章會(huì)將)的四個(gè)角色里,他們都代表BBBBBBINDERRR,對(duì)于使用者而言,eg:Serve端 和 Client 端口 都持有Binder對(duì)象你不會(huì)發(fā)現(xiàn)他們有什么不同,一個(gè)Binder就代表了所有,我們根本不用關(guān)心他的具體的實(shí)現(xiàn)過程或者細(xì)節(jié)??赡墁F(xiàn)在感受不到,接著往下看吧:
創(chuàng)建aidl文件,然后reBuild可以生成以下java文件,你可能會(huì)問aidl是什么?
aidl:安卓接口定義語(yǔ)言,它是實(shí)現(xiàn)IPC一個(gè)很重要的方式,并且底層基于binder。
然后寫入跨進(jìn)程的接口:
// IBookMananger.aidl
package com.example.lixiongjie.ipc;
import com.example.lixiongjie.ipc.Book;//手動(dòng)導(dǎo)入的
// Declare any non-default types here with import statements
interface IBookMananger {
List<Book> getBookList();
void addBook(in Book book);
}
這里值得注意的是,實(shí)現(xiàn)接口需要導(dǎo)入其他的類的時(shí)候(除了基本變量以外的成員屬性的注意以下問題:
- 變量必須進(jìn)行序列化,而且Binder支持的是Parcelable序列化。
- 創(chuàng)建該成員的AIDL文件聲明
- 手動(dòng)導(dǎo)入該類(例如文中的Book類)
接著就像上文我們所說(shuō)的reBuild生成:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/lixiongjie/Desktop/Ipc/app/src/main/aidl/com/example/lixiongjie/ipc/IBookMananger.aidl
*/
package com.example.lixiongjie.ipc;
// Declare any non-default types here with import statements
public interface IBookMananger extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.lixiongjie.ipc.IBookMananger
{
//Binder的唯一標(biāo)識(shí),一般用類名表示。
private static final java.lang.String DESCRIPTOR = "com.example.lixiongjie.ipc.IBookMananger";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.lixiongjie.ipc.IBookMananger interface,
* generating a proxy if needed.
*/
//用于將服務(wù)端的Binder對(duì)象轉(zhuǎn)換為客戶端所需的AIDL接口類型的對(duì)象,這個(gè)轉(zhuǎn)換是有區(qū)別的,如果客戶端與服務(wù)端統(tǒng)一進(jìn)程返回的就是Stub對(duì)象本身,否則就返回封裝后的Stub.proxy.
public static com.example.lixiongjie.ipc.IBookMananger asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//查詢是不是本地的,如果是就直接返回Stub對(duì)象本身。
if (((iin!=null)&&(iin instanceof com.example.lixiongjie.ipc.IBookMananger))) {
return ((com.example.lixiongjie.ipc.IBookMananger)iin);
}
return new com.example.lixiongjie.ipc.IBookMananger.Stub.Proxy(obj);//否則證明不是本地的,傳入遠(yuǎn)程代理過來(lái)。
}
//返回當(dāng)前Binder對(duì)象
@Override
public android.os.IBinder asBinder()
{
return this;
}
/**
*這個(gè)方法比較復(fù)雜,一般運(yùn)行在遠(yuǎn)程端中的Binder線程池中,當(dāng)客戶端發(fā)送跨進(jìn)程請(qǐng)求后遠(yuǎn)程,然后遠(yuǎn)程端會(huì)通過系統(tǒng)底層的分裝來(lái)把遠(yuǎn)程請(qǐng)求交給此方法來(lái)處理,然后來(lái)看看方法內(nèi)部的實(shí)現(xiàn)吧:
* 首先根據(jù)code確定目標(biāo)方法,然后通過data取出目標(biāo)方法參數(shù),執(zhí)行完后向reply寫入返回值
*@param code 可以確定客戶端的請(qǐng)求的目標(biāo)方法是啥。
* @param data 取出目標(biāo)方法的參數(shù)
* @return 如果為true代表遠(yuǎn)程請(qǐng)求成功,否則就為失敗,可以利用這個(gè)特性來(lái)做權(quán)限驗(yàn)證。
*/
@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.lixiongjie.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.lixiongjie.ipc.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.lixiongjie.ipc.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.example.lixiongjie.ipc.IBookMananger
{
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;
}
/**
* 1.創(chuàng)建兩個(gè)Parcel對(duì)象1.data傳入的對(duì)象2.reply輸出的對(duì)象 還要返回對(duì)象reslut:List;
* 2.調(diào)用transact方法發(fā)起RPC(遠(yuǎn)程過程調(diào)用)請(qǐng)求,同時(shí)線程掛起
* 3.然后服務(wù)端的onTranscat會(huì)調(diào)用,直到RPC返回值,線程才恢復(fù)
* 4.最后從reply中取出PRC的結(jié)果,返回得到的值
* @return
* @throws android.os.RemoteException
*/
@Override
public java.util.List<com.example.lixiongjie.ipc.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.lixiongjie.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.lixiongjie.ipc.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* 和上面一樣,但是值得注意的是addBook沒有返回值,所以他不需要reply,但是在傳參進(jìn)去會(huì)調(diào)用writeToParcel()傳入序列化的對(duì)象Parcel中
* @param book
* @throws android.os.RemoteException
*/
@Override
public void addBook(com.example.lixiongjie.ipc.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.example.lixiongjie.ipc.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.example.lixiongjie.ipc.Book book) throws android.os.RemoteException;
}
由于客戶端進(jìn)行PRC的時(shí)候,客戶端的線程會(huì)掛起等待服務(wù)端的進(jìn)程返回?cái)?shù)值,但是如果這個(gè)遠(yuǎn)程方法很耗時(shí),那就不能再UI線程發(fā)起次遠(yuǎn)程請(qǐng)求了,其次服務(wù)端的Binder運(yùn)行在Binder的線程池中,所以不管耗不耗時(shí)間都應(yīng)該采用同步方式去處理,因?yàn)樗鼈兌荚谝粋€(gè)線程中。
手動(dòng)寫B(tài)inder在服務(wù)端需要?jiǎng)?chuàng)建一個(gè)BookManangerImpl并在Service得到onBind方法返回即可。
上述代碼中我們查看結(jié)構(gòu),第一個(gè)該接口實(shí)現(xiàn)是 android.os.IInterface接口,這里的IInterface代表的就是遠(yuǎn)程server對(duì)象具有什么能力。
抽象類Stub extends android.os.Binder implements com.example.lixiongjie.ipc.IBookMananger
這個(gè)類繼承了Binder, 說(shuō)明它是一個(gè)Binder本地對(duì)象,它實(shí)現(xiàn)了IInterface接口,表明它具有遠(yuǎn)程Server承諾給Client的能力,至于為什么是抽象類,是因?yàn)樵O(shè)定的方法要實(shí)現(xiàn)的子類是服務(wù)端,及在接口中遠(yuǎn)程端實(shí)現(xiàn)的方法。
DESCRIPTOR:是該Binder的唯一標(biāo)識(shí),會(huì)發(fā)現(xiàn)我們?cè)?onTransact 函數(shù)中的返回?cái)?shù)據(jù)體reply,和add方法里寫入數(shù)據(jù)的時(shí)候都會(huì)加上這個(gè)標(biāo)識(shí),驗(yàn)證就在onTransact的case中驗(yàn)證。
in、out、inout
在官方文檔中支出,所有的非原語(yǔ)參數(shù)需要指示數(shù)據(jù)的方向標(biāo)記,可以是in、out、inout。默認(rèn)的原語(yǔ)是in,不能是其他流向。
這里指定的非原語(yǔ)是指:除了Java的基本類型外的其他參數(shù),也就是對(duì)象。我們?cè)贏IDL使用的時(shí)候需要知道這個(gè)參數(shù)的流向。
那什么是數(shù)據(jù)的方向標(biāo)記呢?
首先,數(shù)據(jù)的方向標(biāo)記是針對(duì)客戶端中的那個(gè)傳入的方法參數(shù)而言。數(shù)據(jù)流向的標(biāo)識(shí)符不能使用在返回參數(shù)上,只能使用在方法參數(shù)上面。
in:他表示的是這個(gè)參數(shù)只能從客戶端流向服務(wù)端,比如客戶端傳遞了一個(gè)User對(duì)象給服務(wù)端,服務(wù)端會(huì)收到一個(gè)完整的User對(duì)象,然后假如在服務(wù)端對(duì)這個(gè)對(duì)象進(jìn)行操作,那么這個(gè)改變是不會(huì)反映到客戶端的,這個(gè)流向也就是只能從客戶端到服務(wù)端。
out:他表示,當(dāng)客戶端傳遞參數(shù)到服務(wù)端的時(shí)候,服務(wù)端將會(huì)收到一個(gè)空的對(duì)象,假如服務(wù)端對(duì)該對(duì)象進(jìn)行操作,將會(huì)反映到客戶端。比如,客戶端傳遞一個(gè)User對(duì)象到服務(wù)端,服務(wù)端接收到的是一個(gè)空的User對(duì)象(不是null,只是有點(diǎn)像new一個(gè)User對(duì)象)。當(dāng)服務(wù)端對(duì)這個(gè)User對(duì)象進(jìn)行改變的時(shí)候,他的值變化將會(huì)反映到客戶端。
inout,它具有這二者的功能,也就是客戶端傳遞對(duì)象到服務(wù)端,可以接收到完整的對(duì)象,同時(shí)服務(wù)端改變對(duì)象,也會(huì)反映到客戶端。
總結(jié)來(lái)說(shuō),in類似于傳值,out類似于傳引用,只是out的引用 到了服務(wù)端為空,inout則具有二者的功能,默認(rèn)的是in。
linkToDeath和unlinkToDeath
如果遠(yuǎn)程端因?yàn)槟承┰蚪K止了連接,這樣會(huì)導(dǎo)致我們遠(yuǎn)程調(diào)用失敗,但是關(guān)鍵的地方是我們并不知道Binder的連接斷開,這樣會(huì)導(dǎo)致客戶端受影響。
然后英雄linkToDeath和unlinkToDeath登場(chǎng),通過linkToDeath去設(shè)置死亡代理,當(dāng)監(jiān)聽到死亡的時(shí)候,我們會(huì)受到通知,然后進(jìn)行下一步處理工作。
首先聲明DeathRecipient對(duì)象,該接口只有一個(gè)方法binderDied,當(dāng)Binder死亡的時(shí)候會(huì)回到該方法,然后我們可以移出之前重寫綁定遠(yuǎn)程服務(wù)。
Bundle
四大組件傳輸?shù)娜蠼M件(除了ContentPrider),都是都支持在Intent的中傳輸Bundle數(shù)據(jù),由于Bundle實(shí)現(xiàn)了Parcelable接口,所以在組件間支持跨進(jìn)程傳輸也理所應(yīng)當(dāng),當(dāng)然傳輸?shù)臄?shù)據(jù)也應(yīng)該被序列化,eg:基本類型,實(shí)現(xiàn)parcelable接口對(duì)象,實(shí)現(xiàn)了SerialVersion接口的對(duì)象,和Android特殊的對(duì)象。
除了直接傳輸?shù)牡湫蛨?chǎng)景
見《藝術(shù)探索》62※
使用文件共享
共享文件是一個(gè)不錯(cuò)的進(jìn)程通信方式,在Window上,一個(gè)文件加上排斥鎖來(lái)解決線程讀寫的問題,但是Android是基于Linux ,使得讀寫的并發(fā)可以沒有限制的進(jìn)行,所以兩個(gè)線程對(duì)同一個(gè)文件進(jìn)行操作被允許了,但是有可能會(huì)出現(xiàn)問題,當(dāng)然我們還是可以, 通過序列化一個(gè)對(duì)象在系統(tǒng)文件中然后另外個(gè)進(jìn)程在中恢復(fù)這個(gè)對(duì)象,
//在第一進(jìn)程的Activity儲(chǔ)存
private void persistToFile(){
new Thread(new Runnable() {
@Override
public void run() {
Book book = new Book(1,"童話故事");
File file = new File();
if (!file.exists()){
file.mkdirs();
}
File cache = new File();
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(cache));
out.writeObject(book);
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
//在第二進(jìn)程的Activity讀取
private void recoverFromFile(){
new Thread(new Runnable() {
@Override
public void run() {
Book book = null;
File cache = new File();
if (cache.exists()){
ObjectInputStream inputStream =null;
try {
inputStream = new ObjectInputStream(new FileInputStream(""));
book = (Book) inputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
可以看到這個(gè)其實(shí)就是之前的內(nèi)容,但是我得注意的是儲(chǔ)存的格式可以是文本文件,也可以是XML文件,但是我們得讓讀取的雙方約定格式,第二份存在的問題就是并發(fā)讀寫的問題,如果是并發(fā)讀有可能拿不到最新的,但是并發(fā)寫問題就更為嚴(yán)重,所以要么避免要么得同步來(lái)限制多線程的寫。
SharedPrefercens
SharedPrefercens是輕量級(jí)儲(chǔ)存方案,它通過鍵值對(duì)來(lái)儲(chǔ)存數(shù)據(jù),底層實(shí)現(xiàn)XML文件來(lái)存儲(chǔ)鍵值對(duì),目錄位于/data/data/packagename(包名)/shared_prefs下,本質(zhì)上講SP是文件一種,但是系統(tǒng)對(duì)它的讀寫有緩存策略,導(dǎo)致內(nèi)存有一份SP文件備份緩存,因此在多線程的模式下,讀寫會(huì)不可靠