1. 程式人生 > >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