android中的跨进程通信的实现(一)——远程调用过程和aidl
阅读原文时间:2021年04月20日阅读:1

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  {
    /**
     * 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](http://developer.android.com/reference/android/os/Parcelable.html) interface), and references to live [IBinder](http://developer.android.com/reference/android/os/IBinder.html) 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