android中的跨進程通訊的實現(一)——遠程調用過程和aidl

來源:互聯網
上載者:User

android在設計理念上強調組件化,組件之間的依賴性很小。我們往往發一個intent請求就可以啟動另一個應用的activity,或者一個你不知道在哪個進程的service,或者可以註冊一個廣播,只要有這個事件發生你都可以收到,又或者你可以查詢一個contentProvider獲得你想要的資料,這其實都需要跨進程通訊的支援。只是android將其封裝的如此簡單,應用開發人員甚至完全不用關注它是不是和我在一個進程裡。

我們有沒有想過安全性問題,如此簡單就可以跨進程的訪問,安全性問題怎麼保證。本來每個進程都是一個孤島,而通過ipc,這個孤島卻可以和世界通訊了。這裡簡單介紹下android中的安全機制。

android的安全機制分為三層。最基礎的一層,android將資料分為system和data兩個區。其中system是唯讀,data用來存放應用自己的資料,這保證了系統資料不會被隨意改寫。第二層用來使應用之間的資料相互獨立。每個應用都會有一個user id和group id,只有相同的user id並且來自同一個作者,才能訪問它們的資料。作者通過對apk簽名來標識自己。簽名和uid構成了雙重的保證。第三個層次就是許可權體系,這個就不用多說了。

拉回正題,那麼android是如何?ipc的呢?答案是binder。我打算用兩篇來介紹android的binder機制,這一篇著重如何使用,介紹跨進程調用的過程和aidl。另一篇著重binder實現機制。

Binder並不是android最早開始使用,它發源於Be和Palm之前的OpenBinder,由Dianne Hackborn領導開發。Hackborn現在就在google,是android framework的工程師,我們可以從https://lkml.org/lkml/2009/6/25/3看一下,Hackborn如何描述binder。一句話總結:

In the Android platform, the binder is used for nearly everything thathappens across processes in the core platform. 

可是android將binder幾乎封裝的不可見,我們看下階層是怎麼樣的。

最底層的是android的ashmen(Anonymous shared memoryy)機制,它負責輔助實現記憶體的分配,以及跨進程所需要的記憶體共用。

AIDL(android interface definition language)對Binder的使用進行了封裝,可以讓開發人員方便的進行方法的遠程調用,後面會詳細介紹。

Intent是最高一層的抽象,方便開發人員進行常用的跨進程調用。

關於如何使用intent去跨進程的啟動一個activity或者service等,這裡就不再介紹了,是android中非常基礎的內容。

這裡講如何?遠端方法調用。在android中對方法的遠程調用無處不在,隨便開啟framework/base中的包,都會發現很多aidl檔案。AIDL是android為了方便開發人員進行遠程方法調用,定義的一種語言。使用aidl完成一個遠程方法調用只需要三個步驟:

1.用aidl定義需要被呼叫者法介面。

2.實現這些方法。

3.調用這些方法。

我們拿ApiDemo中的例子來學習。在app包下面有一個ISecondary.aidl

interface  {    /**     * Request the PID of this service, to do evil things with it.     */    int getPid();        /**     * This demonstrates the basic types that you can use as parameters     * and return values in AIDL.     */    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,            double aDouble, String aString);}

看起來和java沒有什麼區別。可以看到它定義個了兩個介面方法。從這裡我們可以知道AIDL(android介面定義語言的由來)。android會將該aidl產生一個java檔案(如果你使用eclipse,會自動產生。在gen目錄下。),產生的程式碼如下:

/* * This file is auto-generated.  DO NOT MODIFY. * Original file: /home/dd/workspace/ApiDemos/src/com/example/android/apis/app/ISecondary.aidl */package com.example.android.apis.app;/** * Example of a secondary interface associated with a service.  (Note that * the interface itself doesn't impact, it is just a matter of how you * retrieve it from the service.) */public interface ISecondary extends android.os.IInterface{/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.example.android.apis.app.ISecondary{private static final java.lang.String DESCRIPTOR = "com.example.android.apis.app.ISecondary";/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/** * Cast an IBinder object into an com.example.android.apis.app.ISecondary interface, * generating a proxy if needed. */public static com.example.android.apis.app.ISecondary asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.example.android.apis.app.ISecondary))) {return ((com.example.android.apis.app.ISecondary)iin);}return new com.example.android.apis.app.ISecondary.Stub.Proxy(obj);}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_getPid:{data.enforceInterface(DESCRIPTOR);int _result = this.getPid();reply.writeNoException();reply.writeInt(_result);return true;}case TRANSACTION_basicTypes:{data.enforceInterface(DESCRIPTOR);int _arg0;_arg0 = data.readInt();long _arg1;_arg1 = data.readLong();boolean _arg2;_arg2 = (0!=data.readInt());float _arg3;_arg3 = data.readFloat();double _arg4;_arg4 = data.readDouble();java.lang.String _arg5;_arg5 = data.readString();this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements com.example.android.apis.app.ISecondary{private android.os.IBinder mRemote;Proxy(android.os.IBinder remote){mRemote = remote;}public android.os.IBinder asBinder(){return mRemote;}public java.lang.String getInterfaceDescriptor(){return DESCRIPTOR;}/**     * Request the PID of this service, to do evil things with it.     */public int getPid() 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);mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);_reply.readException();_result = _reply.readInt();}finally {_reply.recycle();_data.recycle();}return _result;}/**     * This demonstrates the basic types that you can use as parameters     * and return values in AIDL.     */public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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(anInt);_data.writeLong(aLong);_data.writeInt(((aBoolean)?(1):(0)));_data.writeFloat(aFloat);_data.writeDouble(aDouble);_data.writeString(aString);mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);_reply.readException();}finally {_reply.recycle();_data.recycle();}}}static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);}/**     * Request the PID of this service, to do evil things with it.     */public int getPid() throws android.os.RemoteException;/**     * This demonstrates the basic types that you can use as parameters     * and return values in AIDL.     */public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;}

我們分析下,android工具將我們寫的aidl檔案產生了怎樣的一個檔案,它都做哪些工作。

首先這個介面繼承了android.os.IInterface.它是所有由aidl檔案產生的基類。介面裡有一個內部類Stub,它繼承自Binder並實現了這個產生的java介面ISecondary。但是它並沒有實現我們定義的介面方法。而這些介面方法其實就是留給我們去實現的。在ApiDemo中,RemoteService類實現了這些方法:

/**     * A secondary interface to the service.     */    private final ISecondary.Stub mSecondaryBinder = new ISecondary.Stub() {        public int getPid() {            return Process.myPid();        }        public void basicTypes(int anInt, long aLong, boolean aBoolean,                float aFloat, double aDouble, String aString) {        }    };

這就是我們要做的第二部操作,實現這些方法。

繼續看這個介面類。在stub中實現了一個很重要的方法asInterface(android.os.IBinder obj)。該方法中會去查詢是否有一個ISecondary的執行個體,這其實是去查詢是不是在同一個應用裡去調用它,那我們就不用實行遠程調用,直接本地調用就可以了。如果不是本地介面,這時候會返回一個Proxy對象。Proxy類是Stub的一個內部類,也同樣實現了ISecondary介面。但是它卻已經實現了這些介面方法。這就意味著如果要進行遠程調用,必須擷取一個Proxy類的執行個體,自然是通過stub類的asInterface方法獲得。看下ApiDemo裡如何擷取該執行個體。

        /**         * Class for interacting with the secondary interface of the service.         */        private ServiceConnection mSecondaryConnection = new ServiceConnection() {            public void onServiceConnected(ComponentName className,                    IBinder service) {                // Connecting to a secondary interface is the same as any                // other interface.                mSecondaryService = ISecondary.Stub.asInterface(service);                mKillButton.setEnabled(true);            }            public void onServiceDisconnected(ComponentName className) {                mSecondaryService = null;                mKillButton.setEnabled(false);            }        };

可以看到是在onServiceConnected裡獲得了這個遠程執行個體,具體如何得到?

ServiceConnection對象其實是在更早之前用來綁定service而調用的bindService方法的參數。

bindService(new Intent(ISecondary.class.getName()),                        mSecondaryConnection, Context.BIND_AUTO_CREATE);

ActivityManagerService在bindService時,會調用ActivityThread的方法,並會傳遞一個Binder引用,而ActivityThread會回調ServiceConnection中的OnServiceConnected方法,並將這個Binder對象傳入,也就是anInterface方法中的這個service。這樣整個流程走完就獲得了遠程執行個體,我們一般會把它儲存到一個全域變數中,供以後調用遠程方法。

這時候我們就可以執行第三步了,進行方法調用。

int pid = mSecondaryService.getPid();

其實這時候我們已經完成了遠程調用,擷取了pid的值。

不過我們不妨繼續看下去。我們看另一個方法basicTypes,apidemo沒有使用,但是另一個方法傳入了參數,更具代表意義,我們去實現basicTypes方法,並通過Proxy進進行遠程調用它(代碼就不貼了)。此時這個調用會被proxy對象轉換成可以用pacel封裝的基礎資料類型,參數也被序列化寫入一個資料包。一個使用者定義的int型code將會被指派給transaction,這個code用來標識方法名,因為Binder此時只允許傳遞int類型。這就需要用戶端和遠程服務端做好約定。

方法實現如下:

public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) 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(anInt);_data.writeLong(aLong);_data.writeInt(((aBoolean)?(1):(0)));_data.writeFloat(aFloat);_data.writeDouble(aDouble);_data.writeString(aString);mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);_reply.readException();}finally {_reply.recycle();_data.recycle();}

方法首先通過obtain方法擷取兩個Parcel對象。調用writeInterfaceToken方法用來標識,以便服務端能夠識別。然後寫入參數,注意這個寫入順序和取出順序必須是一致的。然後對傳給Proxy的binder對象調用了transact方法,該方法中就將code作為參數傳入。pacel對象通過jni介面傳遞到Binder的C++空間,最終傳遞到Binder驅動。binder驅動會讓用戶端進程休眠,並且將傳過來的pacel資料從用戶端進程映射到服務端進程。然後反向的傳遞,從binder驅動傳遞到C++中介層,再通過JNI傳遞到java層。此時Stub的ontransact方法會被調用。方法如下:

@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_getPid:{data.enforceInterface(DESCRIPTOR);int _result = this.getPid();reply.writeNoException();reply.writeInt(_result);return true;}case TRANSACTION_basicTypes:{data.enforceInterface(DESCRIPTOR);int _arg0;_arg0 = data.readInt();long _arg1;_arg1 = data.readLong();boolean _arg2;_arg2 = (0!=data.readInt());float _arg3;_arg3 = data.readFloat();double _arg4;_arg4 = data.readDouble();java.lang.String _arg5;_arg5 = data.readString();this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}

首先通過對code的判斷,執行對應方法的內容,對資料按順序一一解包,讀出參數。最終調用方法,並將傳回值寫入parcel,傳遞給binder驅動。binder驅動重新喚醒用戶端進程並把傳回值傳遞給proxy對象,並最後被解包並作為proxy方法的傳回值。

從這一個流程下來,我們可以知道aidl主要就協助我們完成了封裝資料和解包的過程,並調用了transact過程。而用來傳遞的資料包我們就稱為parcel。關於parcel,我們直接看下官方文檔的描述;

Container for a message (data and object references) that can be sent through an IBinder. A Parcel can contain both flattened
data that will be unflattened on the other side of the IPC (using the various methods here for writing specific types, or the general Parcelable interface),
and references to live IBinder objects
that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.

如果要傳遞的參數不是基礎類型,那就需要對其進行封裝,成為parcelable的執行個體。如下:

 public class MyParcelable implements Parcelable {     private int mData;     public int describeContents() {         return 0;     }     public void writeToParcel(Parcel out, int flags) {         out.writeInt(mData);     }     public static final Parcelable.Creator<MyParcelable> CREATOR             = new Parcelable.Creator<MyParcelable>() {         public MyParcelable createFromParcel(Parcel in) {             return new MyParcelable(in);         }         public MyParcelable[] newArray(int size) {             return new MyParcelable[size];         }     };          private MyParcelable(Parcel in) {         mData = in.readInt();     } }

最後看下這張圖:

是不是很明了了?我想大家看完以後手動寫一個遠程調用而不使用aidl也是可以完成了。不得不說,android設計的非常好,也用aidl讓需要用到ipc的時候對開發人員非常友好。android中ipc通訊的使用和過程大致如此。歡迎轉載,請註明出處,謝謝了。http://blog.csdn.net/notice520/article/details/8135600

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.