Android實戰技術:深入理解Android的RPC方式與AIDL
揭開面紗 可以看一下gen資料夾下生成的與AIDL檔案同名的原始碼檔案,這個檔案看似很複雜,通過這個檔案來可以瞭解AIDL的本質,這裡面有一個介面,裡面在的方法就是AIDL檔案中所定義的方法;還有一個Stub,這個就是我們要在Service端實現的基類;還有一個Proxy。它們之間的關係是這們的。
從使用者的角度來瀏覽這個原始碼檔案:它的最外層是一個與AIDL同名的介面,這裡是PrinterInterface,其內有一個接受String的方法print。Client端使用時是用PrinterInterface.Stub.asInterface,可以看到這個方法會返回一個實現了PrinterInterface介面的物件。另外就是Server端會讓Service實現PrinterInterface.Stub,其實是實現PrinterInterface,因為Stub也繼承自PrinterInterface。所以,貌似的時序是這樣的:客戶端獲取了一個實現了PrinterInterface介面的物件,而服務端要實現此介面。
但是這樣看起來還是有些亂,我們需要繼續脫去它的衣服!(天熱啊,得繼續脫啊!)
脫去外套 因為由AIDL生成的檔案無法編譯,所以我們建立一個一模一樣的檔案來進行,以方便我們對其進行編輯和改動。我們分別在獲取IBinder物件時,Stub的相關方法裡和Proxy的相關方法里加上日誌語句,以跟蹤程式的行為:
通過跟蹤除錯可以得到以下結論:
當通訊的雙方在同一個程序中時,onServiceConnected傳回的物件是Service.onBind()所返回的物件;但如果是跨程序時,則其返回的是一個BinderProxy物件。所以,可以看到在AIDL生成的類中會有這樣的判斷:
if (((iin != null) && (iin instanceof MyPrinterInterface))) { Log.e(TAG, "we have local interface, so we use it"); return ((MyPrinterInterface) iin); }
這實際上就是判斷此通訊是在同一程序中,還是跨程序,因為同一程序傳回的物件是Service.onBind()所返回的物件,而此物件必然實現了介面(要不然搞毛啊!)。所以,如果僅是在同一個程序之中,不會走Binder程序IPC,而是直接返回Service所提供的物件,直接呼叫其方法,因此也就不會有物件必須Parcelable的限制!
也就是說,當在同一個程序中時AIDL實際上變成了這樣的:
也就是說它是直接返回了Service.onBind()的物件,這其實跟前面提到的第一種方式:直接實現Binder物件的方式是一樣一樣的,其他的程式碼全是多餘的。因此,如前面建議的,如果僅是在同一個程序中,就直接使用Binder就好了,沒有必要建立AIDL檔案。
當在不同的程序中時,客戶端Stub.asInterface會返回一個Stub.Proxy物件,呼叫其上的print方法。而服務端僅會執行Stub.onTransact()方法,然後就調到Service的print方法了。
當跨程序的時候,就要使用Binder物件的IPC相關的方法和機制。客戶端需要實現Binder.transact()方法來執行遠端的一個方法,這是給客戶端來使用;而服務端則需要實現Binder.onTransact()來響應客戶端所請求的方法。對於上層使用者來說,用transact()把函式的資訊(引數,標識和開關)傳送出去,剩下的就是Binder的工作了,內部還有大量的細節,但是最終會呼叫到服務端Binder的onTransact()方法,這裡識別出函式的標識,然後呼叫具體的實現,再傳回返回值,這樣一個IPC的函式呼叫就完成了。
當跨程序時,僅以下程式碼是各自所必須的,去掉了無關程式碼:
Server service:
public class MyServerService extends Service { private static final String TAG = "MyServerService"; private Handler mHandler = new Handler(); @Override public IBinder onBind(Intent intent) { return mBinder; } private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() { @Override public void print(String msg) throws RemoteException { MyServerService.this.print(msg); } }; public void print(String msg) { try { Log.e(TAG, "Preparing printer..."); Thread.sleep(1000); Log.e(TAG, "Connecting printer..."); Thread.sleep(1000); Log.e(TAG, "Printing.... " + msg); Thread.sleep(1000); Log.e(TAG, "Done"); } catch (InterruptedException e) { } mHandler.post(new Runnable() { @Override public void run() { Toast.makeText(MyServerService.this, "MyServerService Printing is done.", Toast.LENGTH_LONG).show(); } }); } }
serer side interface definition:
public interface MyPrinterInterface extends android.os.IInterface { public void print(String msg) throws android.os.RemoteException; } abstract class MyPrinterInterfaceStub extends Binder implements MyPrinterInterface { private static final String DESCRIPTOR = "MyPrinterInterface"; private static final String TAG = "MyPrinterInterfaceStub"; public MyPrinterInterfaceStub() { attachInterface(this, DESCRIPTOR); } @Override public IBinder asBinder() { return this; } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException { Log.e(TAG, "onTransact, code is " + code); switch (code) { case INTERFACE_TRANSACTION: { Log.e(TAG, "onTransact, code is " + code + ", when this happens"); reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_print: { data.enforceInterface(DESCRIPTOR); String _arg0; _arg0 = data.readString(); Log.e(TAG, "ontransact, arg is " + _arg0 + ", when this happened?"); this.print(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0); }
Client activity:
public class AnotherMyClientActivity extends Activity { private static final String TAG = "MyClientActivity"; MyPrinterInterface mService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.printer_activity); setTitle("My interface another client Activity"); ((Button) findViewById(R.id.play)).setText("Print via my interface"); } @Override protected void onStart() { super.onStart(); doBindService(); } private void doBindService() { Intent intent = new Intent(); intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MyServerService"); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); doUnbindService(); } private void doUnbindService() { if (mService != null) { unbindService(mConnection); } } public void onButtonClick(View v) { if (mService == null) { Log.e(TAG, "what the fucl service is not ready"); return; } try { mService.print("In another application, create a client based on user defined IPC interfaces"); } catch (RemoteException e) { e.printStackTrace(); } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { Log.e(TAG, "on service connected, service obj " + service); mService = MyPrinterInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName arg0) { mService = null; } }; }
client side interface definiition:
public interface MyPrinterInterface extends android.os.IInterface { public void print(String msg) throws android.os.RemoteException; public abstract class Stub extends Binder implements MyPrinterInterface { private static final String DESCRIPTOR = "MyPrinterInterface"; private static final String TAG = "MyPrinterInterface.Stub"; public Stub() { attachInterface(this, DESCRIPTOR); } public static MyPrinterInterface asInterface(IBinder obj) { if ((obj == null)) { return null; } Log.e(TAG, "we are talking to a remote one, we must use a proxy object to wrapper binder"); return new Stub.Proxy(obj); } static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0); private static class Proxy implements MyPrinterInterface { private IBinder mRemote; Proxy(IBinder remote) { mRemote = remote; } @Override public IBinder asBinder() { return mRemote; } public String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public void print(String msg) throws android.os.RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(msg); mRemote.transact(Stub.TRANSACTION_print, _data, _reply, 0); Log.e(TAG, "lalalala, let us passing the parameters and calling the message"); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } } }
本質--脫去內衣 其實AIDL的作用就是對Binder的二個方法:Binder.transact()和Binder.onTransact()進行封裝,以供Client端和Server端進行使用。因為實現transact()和onTransact()方法的方式基本上是相同的,所以就可以用模板來生成具體的程式碼。理論上講只需要為Client端生成transact()相關程式碼,為服務端生成onTransact()程式碼即可,但因為工具無法準確的確定某一個應用到底是Client端還是Server端,所以它就生成所有的程式碼,放在一個檔案中。這就是你看到的自動生成的檔案。
還需要注意的一點是Client端的Proxy是組合Binder物件,呼叫其transact()方法;而服務端必須繼承Binder物件,覆寫onTransact()方法。為蝦米呢?因為Client是主動發起IPC函式Call,所以它可以直接呼叫Binder的方法來進行IPC。而Server是被動的,是要接收進來的IPC call,但Service自己無法得知啥時候Call會來,因此必須實現回撥(onTransact())給Binder,以讓Binder在有IPC Call進來的時候告訴Service。
原理和內幕 AIDL的角色是實現Android平臺上面的RPC(Remote Procedure Call)也即遠端例程呼叫。RPC是IPC中的一種,但是它是以呼叫在本地或者另一個程序,甚至是另一個主機上的方法的機制。RPC的目的就是可以讓程式不用擔心方法具體是在哪個程序裡面或者哪以機器上面,就像正常的本地方法那樣去呼叫即可,RPC機制會處理所有的具體細節。RPC一般用IDL(Interface Definition Language)來描述,實現則要看具體的平臺和語言。可以參考Wikipedia來看RPC 和IDL 的更多資訊。
AIDL提供Android平臺的RPC的支援:開發者僅需要要定義AIDL,做一些相關的適配工作,然後就可以使用這些方法了,不用具體關心介面描述的方法空究竟是在同一個程序中還是在其他的程序中。這些RPC實現的細節由Binder和系統來處理。
Binder RPC的流程:<Binder RPC sequence>
可以看到這個流程是一個標準的RPC流程。
不用AIDL來實現 知道了AIDL的本質後,就可以不用AIDL來實現IPC,雖然AIDL簡單方便,但是它卻非常不容易理解,而且程式碼有冗餘(服務端並不需要為Client準備的物件,反之亦然)。
所以我們可以自已實現:
Server interface:
public interface ServerPrinterInterface { public void print(String msg) throws android.os.RemoteException; } abstract class MyPrinterInterfaceStub extends Binder implements ServerPrinterInterface, IInterface { private static final String DESCRIPTOR = "MyPrinterInterface"; static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0); private static final String TAG = "MyPrinterInterfaceStub"; public MyPrinterInterfaceStub() { attachInterface(this, DESCRIPTOR); } @Override public IBinder asBinder() { return this; } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws android.os.RemoteException { Log.e(TAG, "onTransact, code is " + code); switch (code) { case INTERFACE_TRANSACTION: { Log.e(TAG, "onTransact, code is " + code + ", when this happens"); reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_print: { data.enforceInterface(DESCRIPTOR); String _arg0; _arg0 = data.readString(); Log.e(TAG, "ontransact, arg is " + _arg0 + ", when this happened?"); this.print(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } }
service:
public class MyServerService extends Service { private static final String TAG = "MyServerService"; private Handler mHandler = new Handler(); @Override public IBinder onBind(Intent intent) { return mBinder; } private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() { @Override public void print(String msg) throws RemoteException { MyServerService.this.print(msg); } }; public void print(String msg) { try { Log.e(TAG, "Preparing printer..."); Thread.sleep(1000); Log.e(TAG, "Connecting printer..."); Thread.sleep(1000); Log.e(TAG, "Printing.... " + msg); Thread.sleep(1000); Log.e(TAG, "Done"); } catch (InterruptedException e) { } mHandler.post(new Runnable() { @Override public void run() { Toast.makeText(MyServerService.this, "MyServerService Printing is done.", Toast.LENGTH_LONG).show(); } }); } }
client interface:
public interface ClientPrinterInterface { public void print(String msg) throws android.os.RemoteException; } class MyPrinterInterfaceProxy implements ClientPrinterInterface { private static final String DESCRIPTOR = "MyPrinterInterface"; static final int TRANSACTION_print = (IBinder.FIRST_CALL_TRANSACTION + 0); private static final String TAG = "MyPrinterInterfaceProxy"; private IBinder mRemote; MyPrinterInterfaceProxy(IBinder remote) { mRemote = remote; } @Override public void print(String msg) throws android.os.RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(msg); mRemote.transact(TRANSACTION_print, _data, _reply, 0); Log.e(TAG, "lalalala, let us passing the parameters and calling the message"); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } }
client activity:
public class AnotherMyClientActivity extends Activity { private static final String TAG = "MyClientActivity"; ClientPrinterInterface mService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.printer_activity); setTitle("My interface another client Activity"); ((Button) findViewById(R.id.play)).setText("Print via my interface"); } @Override protected void onStart() { super.onStart(); doBindService(); } private void doBindService() { Intent intent = new Intent(); intent.setClassName("com.example.effectiveandroid", "com.example.effectiveandroid.MyServerService"); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); doUnbindService(); } private void doUnbindService() { if (mService != null) { unbindService(mConnection); } } public void onButtonClick(View v) { if (mService == null) { Log.e(TAG, "what the fucl service is not ready"); return; } try { mService.print("In another application, create a client based on user defined IPC interfaces"); } catch (RemoteException e) { e.printStackTrace(); } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { Log.e(TAG, "on service connected, service obj " + service); mService = new MyPrinterInterfaceProxy(service); } @Override public void onServiceDisconnected(ComponentName arg0) { mService = null; } }; }
從這裡可以看到不使用AIDL有二個好處:
1. 自己實現還有一個好處就是可以隨意設計包名。如果使用AIDL則Client端的AIDL檔案所在package必須與Server端的AIDL的package完全一致,否則會找不到service,Client端會有異常。但如果自己實現介面,就沒有此限制,可以把介面檔案放在任何的package內。
為什麼呢?因為AIDL生成的Stub和Proxy用的標識DESCRIPTOR加入了package的名字。而如果自己實現介面,可以任意的寫這個DESCRIPTOR。
2. 介面的名字實際上無所謂,更進一步,其實方法的簽名也可以不完全一致。因為這二個介面,一個是在Client端,另一個是在Server端。它們之間的聯絡是間接的通過Binder物件實現的。只要Binder物件的transact和onTransact二個方法能找到相應的介面方法即可。 關鍵的通訊標識和方法標識
從上面的例子可以看出客戶Proxy的transact,和服務Stub的onTransact使用二個標識來識別對方:一個是DESCRIPTOR,這個是標識Binder的Token,也就是說是標識服務端和客戶端;方法的標識就是TRANSACTION_print,是用來標識呼叫的是哪個方法。這個理解起來也不是很難,就好比打電話,先要通過通訊的標識電話號碼找到相應的人,然後跟人說的時候要告訴他是哪件事(哪個方法)。
接下來可以二個方面來進行深入的研究:
1. bindService是如何獲得Binder物件的(無論是本地時Service的實現,還是遠端時的BinderProxy),或者說是如何查詢到Binder物件。
這是ServiceConnection.onServiceConnected的呼叫棧:
2. Binder.transact()和Binder.onTransact()的細節,這也是具體Binder IPC機制的細節。