相關(guān)概念
序列化
Serialization(序列化)是一種將對象以一連串的字節(jié)描述的過程;反序列化deserialization是一種將這些字節(jié)重建成一個對象的過程。從用途角度來說,如果對象需要持久化或者在不同進程間傳輸,就需要序列化。
Java原生提供了Serializable接口支持序列化,Serializable比較強大,持久化和數(shù)據(jù)傳輸都可以支持,但是在序列化開銷比較大,故Android為Binder通信等只在內(nèi)存中傳輸?shù)膱鼍霸O(shè)計了另一套序列化機制--Parcelable。
Parcelable的性能比Serializable好,在內(nèi)存開銷方面較小,所以在內(nèi)存間數(shù)據(jù)傳輸時推薦使用Parcelable,如activity間傳輸數(shù)據(jù),而Serializable可將數(shù)據(jù)持久化方便保存,所以在需要保存或網(wǎng)絡(luò)傳輸數(shù)據(jù)時選擇Serializable,因為android不同版本Parcelable可能不同,所以不推薦使用Parcelable進行數(shù)據(jù)持久化。
一個簡單的示例:
1 public class MyParcelable implements Parcelable {
2 private int mData;
3 private String mStr;
4
5 public int describeContents() {
6 return 0;
7 }
8
9 // 寫數(shù)據(jù)進行保存
10 public void writeToParcel(Parcel out, int flags) {
11 out.writeInt(mData);
12 out.writeString(mStr);
13 }
14
15 // 用來創(chuàng)建自定義的Parcelable的對象
16 public static final Parcelable.Creator<MyParcelable> CREATOR
17 = new Parcelable.Creator<MyParcelable>() {
18 public MyParcelable createFromParcel(Parcel in) {
19 return new MyParcelable(in);
20 }
21
22 public MyParcelable[] newArray(int size) {
23 return new MyParcelable[size];
24 }
25 };
26
27 // 讀數(shù)據(jù)進行恢復(fù)
28 private MyParcelable(Parcel in) {
29 mData = in.readInt();
30 mStr = in.readString();
31 }
32 }
android studio 可以通過android parcelable code generator插件快速生成一個JavaBean對象的序列化相關(guān)代碼
AIDL的使用
由上一講我們知道android IPC是通過Binder實現(xiàn)的,但是Binder相關(guān)的概念非常復(fù)雜,為了方便開發(fā)者google就推出了AIDL(安卓接口定義語言)。通過編寫AIDL文件,eclipse或者android studio 就可以幫我們生成Binder通信的相關(guān)代碼。開發(fā)者即使不了解Binder機制也可以實現(xiàn)IPC了。
AIDl的關(guān)鍵字
- oneway
正常情況下Client調(diào)用AIDL接口方法時會阻塞,直到Server進程中該方法被執(zhí)行完。oneway可以修飾AIDL文件里的方法,oneway修飾的方法在用戶請求相應(yīng)功能時不需要等待響應(yīng)可直接調(diào)用返回,非阻塞效果,該關(guān)鍵字可以用來聲明接口或者聲明方法,如果接口聲明中用到了oneway關(guān)鍵字,則該接口聲明的所有方法都采用oneway方式。(注意,如果client和Server在同一進程中,oneway修飾的方法還是會阻塞) - in
非基本數(shù)據(jù)類型和string的參數(shù)類型必須加參數(shù)修飾符,in的意思是只輸入,既最終server端執(zhí)行完后不會影響到參數(shù)對象 - out
與in相反,out修飾的參數(shù)只能由server寫入并傳遞到client,而client傳入的值并不會傳遞到server - inout
被inout修飾的參數(shù),既可以從client傳遞到server,也可以server傳遞到client
AIDL自動生成文件詳解
不多說,直接上代碼
aidl 文件
//方法參數(shù)選擇了Intent類型,其他任何實現(xiàn)了Parceable的類型都可以作為方法參數(shù)
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void meth1(int args);
void meth2(in Intent args);
void meth3(out Intent args);
void meth4(inout Intent args);
oneway void meth5(inout Intent args);
int meth6(in Intent args);
int meth7(out Intent args);
int meth8(inout Intent args);
oneway int meth9(in Intent args);
Intent meth10(in Intent args);
}
自動生成代碼
public interface IMyAidlInterface extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.xns.aidldemo.IMyAidlInterface {
private static final java.lang.String DESCRIPTOR = "com.xns.aidldemo.IMyAidlInterface";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.xns.aidldemo.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.xns.aidldemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
//先調(diào)用queryLocalInterface,這個方法是IBinder定義的,默認(rèn)實現(xiàn)是返回NULL,而在BBinder的子類BnInterface中,重載了該方法,返回this,而
//BpInterface并沒有重載,使用IBinder的默認(rèn)實現(xiàn),返回NULL。
//簡單的說就是如果server調(diào)用binder對象在一個進程就直接返回其本身,如果不在一個進程就返回代理對象
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.xns.aidldemo.IMyAidlInterface))) {
return ((com.xns.aidldemo.IMyAidlInterface) iin);
}
return new com.xns.aidldemo.IMyAidlInterface.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
/**
* 代理類中的每個方法都會通過onTransact來調(diào)用server端的接口的方法,此方法運行在server端binder線程池
* @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_meth1: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
this.meth1(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_meth2: {
data.enforceInterface(DESCRIPTOR);
android.content.Intent _arg0;
if ((0 != data.readInt())) {
_arg0 = android.content.Intent.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.meth2(_arg0);
return true;
}
...
...
...
case TRANSACTION_meth10: {
data.enforceInterface(DESCRIPTOR);
android.content.Intent _arg0;
if ((0 != data.readInt())) {
_arg0 = android.content.Intent.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
android.content.Intent _result = this.meth10(_arg0);
reply.writeNoException();
if ((_result != null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
/**
* server調(diào)用接口在client的代理類
*/
private static class Proxy implements com.xns.aidldemo.IMyAidlInterface {
private android.os.IBinder mRemote;
/**
*
* @param remote server與client通信的中介,通過binder驅(qū)動交互
*/
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.
*/
@Override
public void meth1(int args) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(args);
//向binder驅(qū)動傳輸數(shù)據(jù),server端對應(yīng)的onTransact會被執(zhí)行
mRemote.transact(Stub.TRANSACTION_meth1, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void meth2(android.content.Intent args) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((args != null)) {
_data.writeInt(1);
//args為in 修飾,此處會將args寫入到需要傳輸?shù)腳data中
args.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//oneway方法,不會阻塞,也就沒有_reply
mRemote.transact(Stub.TRANSACTION_meth2, _data, null, android.os.IBinder.FLAG_ONEWAY);
} finally {
_data.recycle();
}
}
@Override
public void meth3(android.content.Intent args) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_meth3, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
//args為out類型,所以方法執(zhí)行完了會回寫args,但是由于沒有in,所以相當(dāng)于args只是在方法調(diào)用完了接受結(jié)果,卻不能傳遞參數(shù)
args.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void meth4(android.content.Intent args) 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 ((args != null)) {
_data.writeInt(1);
args.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_meth4, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
args.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void meth5(android.content.Intent args) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((args != null)) {
_data.writeInt(1);
args.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_meth5, _data, null, android.os.IBinder.FLAG_ONEWAY);
//雖然args定義為inout,但是由于該方法被oneway修飾,所以不會回寫args,out屬性無效
} finally {
_data.recycle();
}
}
...
...
...
@Override
public int meth9(android.content.Intent args) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((args != null)) {
_data.writeInt(1);
args.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_meth9, _data, null, android.os.IBinder.FLAG_ONEWAY);
} finally {
_data.recycle();
}
//該方法是有返回值的,但是被oneway修飾后該方法不會接受_reply,所以不會返回值.
return _result;
}
@Override
public android.content.Intent meth10(android.content.Intent args) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
android.content.Intent _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((args != null)) {
_data.writeInt(1);
args.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_meth10, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
_result = android.content.Intent.CREATOR.createFromParcel(_reply);
} else {
_result = null;
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
//所有方法的id,onTransact中switch通過這些id判斷調(diào)用哪個方法
static final int TRANSACTION_meth1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_meth2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_meth3 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_meth4 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
static final int TRANSACTION_meth5 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
static final int TRANSACTION_meth6 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
static final int TRANSACTION_meth7 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 6);
static final int TRANSACTION_meth8 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 7);
static final int TRANSACTION_meth9 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 8);
static final int TRANSACTION_meth10 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 9);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void meth1(int args) throws android.os.RemoteException;
public void meth2(android.content.Intent args) throws android.os.RemoteException;
public void meth3(android.content.Intent args) throws android.os.RemoteException;
public void meth4(android.content.Intent args) throws android.os.RemoteException;
public void meth5(android.content.Intent args) throws android.os.RemoteException;
public int meth6(android.content.Intent args) throws android.os.RemoteException;
public int meth7(android.content.Intent args) throws android.os.RemoteException;
public int meth8(android.content.Intent args) throws android.os.RemoteException;
public int meth9(android.content.Intent args) throws android.os.RemoteException;
public android.content.Intent meth10(android.content.Intent args) throws android.os.RemoteException;
}
總結(jié)下,IDE其實用我們編寫的AIDL文件幫我們做了這些事:
- 創(chuàng)建了IMyAidlInterface的實現(xiàn)類Stub和Stub的子類Proxy
- Stub類中實現(xiàn)了有IBinder對象轉(zhuǎn)換為IMyAidlInterface類型的asInterface,asInterface中通過queryLocalInterface(DESCRIPTOR)方法查看本進程是否有IMyAidlInterface在server端的實現(xiàn)類(既判斷Server與Client是否在同一進程),如果是同一進程就直接返回Server端的IMyAidlInterface實現(xiàn)者,如果不在同一進程就返回代理對象
- Proxy類中實現(xiàn)了aidl中定義的方法,根據(jù)oneway、in、out、inout修飾符來生成不同的代碼,決定是否向binder驅(qū)動寫入數(shù)據(jù)或者執(zhí)行完后向方法參數(shù)回寫數(shù)據(jù)。注意:oneway修飾一個方法后,該方法不阻塞client調(diào)用線程,但是方法沒有返回值,方法參數(shù)在執(zhí)行方法執(zhí)行完后也不會回寫。
- Proxy類中實現(xiàn)的方法最終通過transact()方法向Binder驅(qū)動寫入數(shù)據(jù)(運行再client進程),最終Stub類中的onTransact()方法會被調(diào)用到(運行在server進程),就這樣完成一次跨進程方法調(diào)用。
異常處理
Binder死亡處理
在進程間通信過程中,很可能出現(xiàn)一個進程死亡的情況。如果這時活著的一方不知道另一方已經(jīng)死了就會出現(xiàn)問題。那我們?nèi)绾卧贏進程中獲取B進程的存活狀態(tài)呢?
android肯定給我們提供了解決方式,那就是Binder的linkToDeath和unlinkToDeath方法,linkToDeath方法需要傳入一個DeathRecipient對象,DeathRecipient類里面有個binderDied方法,當(dāng)binder對象的所在進程死亡,binderDied方法就會被執(zhí)行,我們就可以在binderDied方法里面做一些異常處理,釋放資源等操作了。示例如下:
...
mClientCallBack = IRemoteCallBack.Stub.asInterface(callback);
if (mClientDeathHandler == null) {
mClientDeathHandler = new ClientDeathRecipient();
}
mClientCallBack.asBinder().linkToDeath(new ClientDeathRecipient(), 0);
...
private class ClientDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
mCallbackList.unregister(mClientCallBack);
mClientCallBack = null;
Logger.d(TAG,"client is died");
}
}
上面是我在server端對client的回調(diào)接口的binder對象設(shè)置的DeathRecipient。在client死亡時,解注冊client的回調(diào),并且置空。
client注冊回調(diào)接口
之前一直說的都是client向server的通信,那如果server要調(diào)用client呢?
一個比較容易想到的辦法就是通過AIDL在server端設(shè)置一個client的回調(diào)。這樣的話就相當(dāng)于client端是server端的server了。
有注冊回調(diào)就肯定有解注冊,但是client端與server不在一個進程,server是無法得知client解注冊時傳入的回調(diào)接口是哪一個(client調(diào)用解注冊時,是通過binder傳輸?shù)絪erver端,所以解注冊時的回調(diào)接口是新創(chuàng)建的,而不是注冊時的回調(diào)接口)。為了解決這個問題,android提供了RemoteCallbackList這個類來專門管理remote回調(diào)的注冊與解注冊。
用法如下:
//TaskCallback.aidl 用于存放要回調(diào)client端的方法
package com.xns.demo.server;
interface ITaskCallback {
void actionPerformed(int actionId);
}
//ITaskBinder.aidl 用于存放供給client端調(diào)用的方法
package com.xns.demo.server;
import com.xns.demo.server.ITaskCallback;
interface ITaskBinder {
boolean isTaskRunning();
void stopRunningTask();
void registerCallback(ITaskCallback cb);
void unregisterCallback(ITaskCallback cb);
}
//myservice
package com.xns.demo.server;
import com.xns.demo.server.ITaskBinder;
import com.xns.demo.server.ITaskCallback;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
public class MyService extends Service {
private static final String TAG = "aidltest";
...
@Override
public IBinder onBind(Intent t) {
printf("service on bind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
printf("service on unbind");
return super.onUnbind(intent);
}
void callback(int val) {
final int N = mCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
try {
mCallbacks.getBroadcastItem(i).actionPerformed(val);
}
catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
mCallbacks.finishBroadcast();
}
private final ITaskBinder.Stub mBinder = new ITaskBinder.Stub() {
public void stopRunningTask() {
}
public boolean isTaskRunning() {
return false;
}
public void registerCallback(ITaskCallback cb) {
if (cb != null) {
mCallbacks.register(cb);
}
}
public void unregisterCallback(ITaskCallback cb) {
if(cb != null) {
mCallbacks.unregister(cb);
}
}
};
final RemoteCallbackList <ITaskCallback>mCallbacks = new RemoteCallbackList <ITaskCallback>();
}
//client端
package com.xns.demo;
...
import com.xns.demo.server.*;
public class MyActivity extends Activity {
private static final String TAG = "aidltest";
private Button btnOk;
private Button btnCancel;
...
ITaskBinder mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = ITaskBinder.Stub.asInterface(service);
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
private ITaskCallback mCallback = new ITaskCallback.Stub() {
public void actionPerformed(int id) {
printf("callback id=" + id);
}
};
}
RemoteCallbackList可以實現(xiàn)正常注冊于解注冊的原因在于注冊與解注冊時雖然對應(yīng)的回調(diào)接口不是同一個,但是其對應(yīng)的Binder對象卻是同一個。
Messenger與AIDL的異同
其實Messenger的底層也是用AIDL實現(xiàn)的,但用起來還是有些不同的,這里總結(jié)了幾點區(qū)別:
1. Messenger本質(zhì)也是AIDL,只是進行了封裝,開發(fā)的時候不用再寫.aidl文件。
結(jié)合我自身的使用,因為不用去寫.aidl文件,相比起來,Messenger使用起來十分簡單。但前面也說了,Messenger本質(zhì)上也是AIDL,故在底層進程間通信這一塊,兩者的效率應(yīng)該是一樣的。
2. 在service端,Messenger處理client端的請求是單線程的,而AIDL是多線程的。
使用AIDL的時候,service端每收到一個client端的請求時,就在BInder線程池中取一個線程去執(zhí)行相應(yīng)的操作。而Messenger,service收到的請求是放在Handler的MessageQueue里面,Handler大家都用過,它需要綁定一個Thread,然后不斷poll message執(zhí)行相關(guān)操作,這個過程是同步執(zhí)行的。
3. client的方法,使用AIDL獲取返回值是同步的,而Messenger是異步的。
Messenger只提供了一個方法進行進程間通信,就是send(Message msg)方法,發(fā)送的是一個Message,沒有返回值,要拿到返回值,需要把client的Messenger作為msg.replyTo參數(shù)傳遞過去,service端處理完之后,在調(diào)用客戶端的Messenger的send(Message msg)方法把返回值傳遞回client,這個過程是異步的,而AIDL你可以自己指定方法,指定返回值,它獲取返回值是同步的(如果沒有用oneway修飾方法的話)。
總的來說,AIDL靈活性更高,如果需要IPC通信的地方比較多,還是更推薦自定義AIDL一點。