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

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

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 that
happens 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 ISecondary {
    /**
     * 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類實現了這些方法:

 

複製代碼
 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) {
        }
    };
複製代碼

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

 

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

 

複製代碼
 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://www.cnblogs.com/noTice520/archive/2012/11/01/2750209.html   謝謝了。

 
標籤: aidlipc遠程調用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章