在認識IPC(Inter-Process Communication,進程間通信)之前,肯定要先理解什么是線程,什么是進程?在多數(shù)操作系統(tǒng)中,線程是最小的可操作調(diào)度單元。而進程一般則是一個具體的應(yīng)用(比如QQ、微信),其可以包含有多個線程。要在不同的進程進行數(shù)據(jù)的傳輸與通信,就需要用到IPC機制了。
在Android中跨進程通信的方式有很多,如intent傳遞、讀寫文件、socket通信、SharedPreferences(不可靠)、ContentProvider以及Binder等。本文主要講解Binder機制的用法及其簡單原理,這次的demo是客戶端跨進程請求服務(wù)端返回一個我們需要的int值,效果如下:

簡單來說Binder是Android系統(tǒng)為我們提供的一個用于進程通信的類,我們一般通過AIDL語言自動讓ide工具為我們生成(如果你不嫌累不嫌頭疼完全可以自己寫),其結(jié)構(gòu)類似這樣:
package com.example.lucky.aidl;
// Declare any non-default types here with import statements
public interface IMyAidlInterface extends android.os.IInterface
{
public int plusResult(int a, int b) throws android.os.RemoteException;
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.lucky.aidl.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.example.lucky.aidl.IMyAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.lucky.aidl.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.example.lucky.aidl.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.lucky.aidl.IMyAidlInterface))) {
return ((com.example.lucky.aidl.IMyAidlInterface)iin);
}
return new com.example.lucky.aidl.IMyAidlInterface.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
{
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.lucky.aidl.IMyAidlInterface
{
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 int plusResult(int a, int b) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_plusResult, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_plusResult = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
}
乍一看代碼很雜亂,邏輯也很復雜,其實對于我們這樣的萌新只需要理解清這個接口的內(nèi)部類Stub、內(nèi)部類中的asInterface(android.os.IBinder obj)、onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)、以及Proxy就可以簡單使用Binder了。下面我會逐一分析這些。
Stub
這個Stub就是我們需要的Binder類了,當我們在客戶端bindService時會通過遠程服務(wù)端的onBinder方法中會return 給我們.
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
通過這個Binder類,我們可以就在客戶端取得 他的內(nèi)部類Proxy,從而在客戶端完成相應(yīng)操作。
asInterface(android.os.IBinder obj)
通過此方法我們可以將服務(wù)端生成的Binder對象(Stub)轉(zhuǎn)化成AIDL接口型的對象。需要注意的是此方法是區(qū)分進程的,如果客戶端和服務(wù)端處于同一進程,則不會進行轉(zhuǎn)換,直接把Stub返回給客戶端,當處于不同進程時,會返回Stub.Proxy對象。
public static com.example.lucky.aidl.IMyAidlInterface asInterface(android.os.IBinder obj){
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.lucky.aidl.IMyAidlInterface))) {
return ((com.example.lucky.aidl.IMyAidlInterface)iin);
}
return new com.example.lucky.aidl.IMyAidlInterface.Stub.Proxy(obj);
}
onTransact
此方法運行在服務(wù)端的Binder線程池中,當我們在客戶端遠程請求服務(wù)端的方法時,就會執(zhí)行該方法。我們可以發(fā)現(xiàn)它就收的參數(shù)類型int code, android.os.Parcel data, android.os.Parcel reply, int flags都是可序列化的Parcel 型,這點后面會講。基本原理就是服務(wù)端通過data取出客戶端傳入的參數(shù),然后寫入reply返回給客戶端。
Proxy
Proxy可以直譯為代理人,從名字上我們就能看出這是服務(wù)端返回給我們的Binder的一個代理人(為了與時俱進,我們可以把它稱作經(jīng)紀人~),通過它我們可以在客戶端遠程調(diào)用服務(wù)端的方法。注意此時會掛起客戶端的當前線程,直到服務(wù)器返回數(shù)據(jù)成功,所以如果我們直接在此處更新UI是很容易引起ANR讓我們的應(yīng)用掛掉(因為等待服務(wù)器一般都比較耗時)!為了避免ANR,常用的方法就是新建線程通過判斷返回的Proxy是否為null來請求服務(wù)端。
通過上面的分析我們應(yīng)該對Binder的工作流程有了一定的了解,接下來就通過demo代碼,更直觀地學會如何使用Binder進行進程間通信。
Demo創(chuàng)建流程
- 1 創(chuàng)建AIDL接口
// IMyAidlInterface.aidl
package com.example.lucky.aidl;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
int plusResult(in int a, in int b);
}
這兒只聲明了一個方法plusResult,往后看就會發(fā)現(xiàn),通過AIDL暴露出來的方法會在Service端實現(xiàn),從而讓客戶端調(diào)用。這兒我們只關(guān)心AIDL文件中支持的數(shù)據(jù)類型:java基本數(shù)據(jù)類型、ArrayList(里面元素也必須支持AIDL)、HashMap(里面元素也必須支持AIDL)、所有可序列化的自定義對象(實現(xiàn)了Parcelable接口)。這兒我們的plusResult方法中接收的參數(shù)是最簡單的int型,如果是自定義的Parcel對象,我們還需要額外創(chuàng)建AIDL文件聲明該對象。例:
int plusResult(in int a, in User use);
我們必須在同目錄下聲明該User對象:
// IMyAidlInterface.aidl
package com.example.lucky.aidl;
parcelable User;
參數(shù)名前面的in、out分別表示輸入、輸出型參數(shù)。
- 2 創(chuàng)建遠程服務(wù)端
此例的Service為了額外創(chuàng)建新的工程直接通過android:process=".anotherProcess"屬性將其進程定義為了其他線程。
public class PlusService extends Service {
private Binder mBinder;
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
mBinder = new IMyAidlInterface.Stub() {
@Override
public int plusResult(int a, int b) throws RemoteException {
/*try {
new Thread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
return a+b;
}
};
}
}
代碼十分簡單,主要在onCreate時實例化 了IMyAidlInterface.Stub,在其中實現(xiàn)了客戶端請求的plusResult方法,然后通過onBind傳遞出去。
- 3 客戶端bindService進行遠程請求
public class MainActivity extends AppCompatActivity {
private ServiceConnection mServiceConnection;
private int plusResult;
@InjectView(R.id.firstEditText)
EditText firstEditText;
@InjectView(R.id.secondEditText)
EditText secondEditText;
@InjectView(R.id.result)
TextView result;
@InjectView(R.id.mButton)
Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if ((firstEditText.getText().length()==0) || (secondEditText.getText().length()==0)) {
Toast.makeText(MainActivity.this,"請輸入數(shù)字",Toast.LENGTH_LONG).show();
return;
} else {
final int a = Integer.valueOf(firstEditText.getText().toString().trim());
final int b = Integer.valueOf(secondEditText.getText().toString().trim());
mServiceConnection = new ServiceConnection() {
//onServiceConnected onServiceDisconnected都是在UI線程中進行的,所以當需要耗時操作時,可以通過判斷bookManager(proxy)對象是否為空,在子線程中進行
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
IMyAidlInterface myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
try {
plusResult = myAidlInterface.plusResult(a,b);
result.setText(" "+plusResult);
Toast.makeText(MainActivity.this,"服務(wù)器響應(yīng)拉,親~",Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
Intent intent = new Intent(MainActivity.this,PlusService.class);
if (mServiceConnection != null) {
bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
}
}
}
});
}
@Override
protected void onDestroy() {
if (mServiceConnection != null) {
unbindService(mServiceConnection);
}
super.onDestroy();
}
}
在ServiceConnection 的onServiceConnected回調(diào)方法中我們獲得了我們想要的經(jīng)紀人IMyAidlInterface myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);,通過它就可以遠程請求服務(wù)端的方法了,這兒為了代碼簡潔沒有新建線程處理,其實只要在服務(wù)端sleep一下,客戶的程序就很容易ANR掛掉了,有興趣的同學可以自己嘗試一下。最后千萬別忘記在客戶端unbindService,這樣一個簡單的跨進程通信demo就創(chuàng)建好了~
總結(jié)下使用AIDL時的那些坑:
- 客戶端、服務(wù)端所有AIDL文件的packagename必須手動修改為相同的
- 遠程服務(wù)端需要通過
android:exported="true"屬性暴露出來 - 有時候找不到系統(tǒng)自動生成的Binder類,我們需要remake一下這個工程
- 自定義創(chuàng)建的對象一定要實現(xiàn)Parcelable且額外聲明
- AIDL即使在相同package內(nèi),也要手動導包
。。。。。。