Android實戰技術:深入理解Android的RPC方式與AIDL

來源:互聯網
上載者:User
Understanding ADIL

AIDL是一個介面描述檔案,用於實現Android平台上面的RPC,aapt在編譯的時候會自動根據規則產生用於IPC的介面和對象,而作為使用者只需要:1.在服務端Service實現介面;2. 在用戶端bindService,onServiceConnected時擷取介面對象。這裡的介面都是AIDL中描述的介面,其他的細節則在由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();@Overridepublic IBinder onBind(Intent intent) {return mBinder;}private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() {@Overridepublic 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);}@Overridepublic IBinder asBinder() {return this;}@Overridepublic 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;}@Overridepublic IBinder asBinder() {return mRemote;}public String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic 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);}@Overridepublic IBinder asBinder() {return this;}@Overridepublic 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();@Overridepublic IBinder onBind(Intent intent) {return mBinder;}private MyPrinterInterfaceStub mBinder = new MyPrinterInterfaceStub() {@Overridepublic 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;}@Overridepublic 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機制的細節。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.