標籤:
IPC是Inter-Process Communication的縮寫,即跨進程通訊。Android中跨進程通訊有多種方式,如檔案分享權限設定、使用ContentProvider、Broadcast、和Socket等。比較複雜的情況下,常用的兩種方式為Messenger和AIDL,而Messenger的底層實現又是AIDL。
首先不看別的,先來看一下AIDL是如何使用的。
假設我們現在有一個兩數相加的任務,用戶端沒辦法完成(別問我它為什麼完不成==,咱舉栗子簡單點哈~),需要將任務交給另一個進程中的服務端完成,再從服務端擷取到該任務的結果。
我們首先如方式建立一個AIDL介面:
Android Studio會自動為它產生一個路徑,如:
在該檔案中聲明一個介面以及一個我們想讓服務端實現的介面方法。如下:
package com.vera.aidltest;interface IMyAdd { int myAdd(int num_a,int num_b);}
注意,並不是所有資料類型都能在AIDL檔案中使用,AIDL檔案只支援以下幾種資料類型:
- Java 中的基礎資料型別 (Elementary Data Type)
- String 和CharSequence
- List 和 Map ,且List和Map 對象的元素必須是AIDL支援的資料類型
- AIDL 自動產生的介面 ,需要匯入(import,即使同處於一個包中)
- 實現android.os.Parcelable 介面的類的對象. 需要匯入(import,即使同處於一個包中),且必須建立一個與其同名的AIDL檔案,並在檔案中聲明該類為Parcelable
在介面定義好後,系統將為我們產生一個Java檔案,AS下是在app\build\generated\source\aidl\debug目錄下,產生的程式碼如下:
package com.vera.aidltest;public interface IMyAdd extends android.os.IInterface{/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.vera.aidltest.IMyAdd{private static final java.lang.String DESCRIPTOR = "com.vera.aidltest.IMyAdd";/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/** * Cast an IBinder object into an com.vera.aidltest.IMyAdd interface, * generating a proxy if needed. */public static com.vera.aidltest.IMyAdd asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.vera.aidltest.IMyAdd))) {return ((com.vera.aidltest.IMyAdd)iin);}return new com.vera.aidltest.IMyAdd.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_myAdd:{data.enforceInterface(DESCRIPTOR);int _arg0;_arg0 = data.readInt();int _arg1;_arg1 = data.readInt();int _result = this.myAdd(_arg0, _arg1);reply.writeNoException();reply.writeInt(_result);return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements com.vera.aidltest.IMyAdd{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 myAdd(int num_a, int num_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(num_a);_data.writeInt(num_b);mRemote.transact(Stub.TRANSACTION_myAdd, _data, _reply, 0);_reply.readException();_result = _reply.readInt();}finally {_reply.recycle();_data.recycle();}return _result;}}static final int TRANSACTION_myAdd = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);}public int myAdd(int num_a, int num_b) throws android.os.RemoteException;}
代碼很長很淩亂的樣子……嗯,本節我們先不看它,只需要知道它是根據AIDL檔案產生的一個介面IMyAdd,包含一個繼承自Binder的靜態內部抽象類別Stub(咦,這麼連起來說總有哪裡怪怪的……),並且聲明了myAdd()方法。為什麼是抽象類別呢,因為它實現了IMyAdd介面卻並沒有真正實現,那放到哪裡實現呢?當然是我們的服務端咯。
接下來,我們可以就可以來寫用戶端和服務端的代碼了,那麼,我們這裡的用戶端和服務端指的是什麼呢?就本例來說,它們分別是一個Activity和一個Service,這裡我們將它們放在了同一個應用中,只不過通過某種方法使其運行在不同的進程。更多情況下它們並不運行在同一個應用中,這時候我們需要將整個aidl檔案夾的內容複寫一份,這是因為用戶端和服務端的AIDL包結構需要保持一致,否則將會出現還原序列化不成功的結果,那麼跨進程通訊將無法進行。
那麼我們現在開始寫服務端的代碼,建立一個Service名為ServerService如下:
package com.vera.aidltest;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;public class ServerService extends Service { private Binder mBinder=new IMyAdd.Stub(){ @Override public int myAdd(int num_a, int num_b) throws RemoteException { int result=num_a+num_b; Log.d("ServerService","the result of "+num_a+" and "+num_b+" is "+result); return result; } }; @Override public IBinder onBind(Intent intent) { return mBinder; }}
ServerService很簡單,它只是建立了一個Binder對象並在onBinder()方法中將其返回。該Binder對象就是我們實現了介面方法的Stub對象。好啦,現在我們可以在myAdd()方法中愉快地進行我們的操作啦。在這裡我們只是得到num_a和num_b的和並將其列印,最後再返回結果。
另外,我們需要將該Service設定在一個獨立的進程中,不然還怎麼玩跨進程通訊~
更改AndroidManifest.xml如下:
<service android:name=".ServerService" android:process=":remote"></service>
好了,服務端的建立已經完成啦,我們現在來看用戶端。用戶端要做些什麼呢?,我們來看代碼:
package com.vera.aidltest;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.RemoteException;import android.support.v7.app.AppCompatActivity;import android.util.Log;import android.view.View;import android.widget.Button;public class ClientActivity extends AppCompatActivity { Button mButton; private ServiceConnection myConnection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { IMyAdd mMyAdd=IMyAdd.Stub.asInterface(iBinder); try { int result=mMyAdd.myAdd(1,2); Log.d("ClientActivity","get the result is "+result); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName componentName) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_client); mButton=(Button)findViewById(R.id.activity_client_mbutton); mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent=new Intent(ClientActivity.this,ServerService.class); bindService(intent,myConnection, Context.BIND_AUTO_CREATE); } }); } @Override protected void onDestroy(){ unbindService(myConnection); super.onDestroy(); }}
首先,我們建立了一個ServiceConnection的匿名類並執行個體化一個對象,然後,我們又設定了在Button點擊之後通過bindService()方法綁定服務。bindService()方法接收三個參數,第一個是在這之前我們建立的Intent對象,第二個是ServiceConnection的執行個體對象,第三個參數是一個標誌位,BIND_AUTO_CREATE表示在Activity和Service進行綁定後,服務將自動建立。然後我們重寫了onDestroy()方法,將Activity和Service解除綁定。
重點是在建立的ServiceConnection匿名類裡!在這個類裡我們重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分別會在Activity與Service成功綁定和解除綁定的時候調用。在onServiceConnected()方法裡,我們調用了Stub()的asInterface()方法,該方法返回一個Binder代理對象,並向上轉型成為用戶端接受的AIDL介面類型的對象!
好啦,拿到了這個對象,現在只差一步調用方法的事啦,我們來試一試1+2等於多少叭~
運行程式,在點擊Button之後,查看日誌列印資訊,如:
在com.vera.aidltest進程中,用戶端列印出得到的結果為3,在com.vera.aidltest:remote進程中,服務端列印出1+2等於3(我英語不好……),通訊成功啦!
嗯好,那我們來總結一下,具體的步驟吧:
- 首先建立一個AIDL檔案,在其中定義一個介面,介面中應包含我們希望服務端實現的方法的聲明。
- 其次,在服務端中將實現好方法的Stub對象通過onBind()方法返回
- 最後,在用戶端中將服務綁定,並重寫ServiceConnection類中的方法,在其中獲得Binder對象,調用服務端的介面方法。
這其中我們屏蔽了很多細節,只談了使用方法,下一節婷子會把更具體的部分再貼出來,第一篇技術部落格,有什麼不對的地方還請留言哦,麼麼噠~
Android IPC 之 AIDL(一)