API Guides之AIDL

原文地址 http://developer.android.com/guide/components/aidl.html

(按照个人理解进行翻译,方便以后查找资料,个人水平有限,如有错误,还请谅解)

翻译:

Android Interface Definition Language(AIDL)

AIDL(Android接口定义语言)与我们工作中使用的其它IDLs(接口定义语言)相似。它允许我们定义程序接口,使客户端和服务器端通过使用IPC与对方通信。在Android中,一个进程一般不能访问另一个进程的内存。因此为了通信,他们需要将他们的对象分解成操纵系统能理解的基本类型数据,并将数据重新编组(就是调整这些对象的内存结构)。编写这些编组的代码是枯燥的,因此Android系统使用AIDL为我们处理这些。

注意:只有当我们允许客户端从不同的应用程序通过IPC访问我们的service时,并且要在我们的service中处理多线程时,才必须使用AIDL。如果我们不需要在不同的应用程序中执行并发IPC,那么我们应该实现一个Binder创建接口,或者我们想要执行IPC但不需要处理多线程时,应该使用Messenger实现接口。不管怎样,在实现一个AIDL之前,确信已经理解了Bound Services。

在我们开始设计AIDL接口之前,应该知道对AIDL接口的调用是直接函数调用。我们不应该对调用发生的线程做任何假设。根据调用是来自本地(这里指我们的应用程序)进程还是远程(这里指其他应用程序)进程,会发生不同的事。具体地:

* 从本地进程发出的调用,执行于发生调用的线程。如果是我们的UI主线程,那个线程将继续在AIDL接口中执行。如果是其它线程,那么将在service中继续执行代码。因此,如果只有本地线程访问service,我们完全可以控制它里面的执行线程(但如果是这种情况,那么我们不应该使用AIDL,而应该实现一个Binder创建接口)。

* 调用发生在远程进程,从我们的进程里平台维护的线程池中发送。我们必须为来自未知线程的多个同时调用做准备。也就是说,一个AIDL的实现必须完全是线程安全的。

* oneway关键词会改变远程调用的行为。当使用它时,一个远程调用不会被阻塞;它简单的发送传输数据并立即返回。接口的实现最终接收它,将它作为正常的来自Binder线程池的调用,作为普通的远程调用。如果oneway用在本地调用中,将不会有影响,调用仍然是同步的。

Defining an AIDL Interface

我们必须使用Java编程语法在一个 .aidl 文件中定义我们的接口,将它保存于service宿主应用程序,和任何其他绑定到service上的应用程序的源代码(位于src目录下)中。当我们编译包含 .aidl 文件的每个应用程序时,Android SDK工具集会自动生成一个基于 .aidl 文件的IBinder接口,并将它保存于项目的gen目录下。service必须恰当的实现IBinder接口。接下来,客户端应用程序可以绑定到service上,并调用IBinder中的方法执行IPC。

为了使用AIDL创建一个bound service,请按照一下步骤:

1. 创建 .aidl 文件

        这个文件定义了方法签名(包含函数返回值类型,函数名,函数参数)的程序接口。

2. 实现这些接口

        Android SDK工具集根据我们的 .aidl 文件,自动生成Java语言的接口。这个接口有一个继承自Binder名为Stub的内部抽象类,其中包含AIDL接口中需要实现的方法。我们必须继承Stub类,并实现这些方法。

3. 开放这个接口给客户端

        实现一个Service,并重写onBind()返回我们Stub类的实现。

注意:从我们第一次软件发布之后的任何AIDL接口的改变,必须是向后兼容的,为了避免破坏使用我们的service的应用程序。就是说,因为我们的 .aidl 文件必须拷贝到其他应用程序里面,使它们能够访问我们的service接口,所以我们必须维护最初版本接口的支持。

1. Create the .aidl file

AIDL使用简单的语法,使我们可以声明一个接口,包含一个或多个可以有参数和返回值的方法。参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。

我们必须使用Java编程语言编写 .aidl 文件。每个 .aidl 文件必须定义一个单独的接口,只包含接口声明和方法签名。

默认情况下,AIDL支持一下数据类型:

* Java中的所有基本类型(例如int,long,char,boolean,等等)

* String

* CharSequence

* List

        List中的所有元素必须是上面支持的数据类型之一,或其他AIDL生成的接口,或我们声明的parcelables。一个List可以使用泛型类(例如,List<String>)。其他应用程序实际接收的具体类总是一个ArrayList,尽管这些方法使用List接口生成。

* Map

        Map中的所有元素必须是上面支持的数据类型之一,或其他AIDL生成的接口,或我们声明的parcelables。泛型map,例如Map<String, Integer>是不被支持的。其他应用程序实际接收的具体类总是一个HashMap,尽管这些方法使用Map接口生成。

针对每个不属于上面的数据类型,我们必须用一个import声明,甚至它们被定义在与接口相同的包中。

* 方法可以携带0或多个参数,可以返回一个值或void

* 所有非基本类型参数都需要一个指向标签,指明这个参数的去向。in,out,inout三者之一(查看下面的示例)

   基本类型默认都是in,不能是其它标签。

   注意:我们应该限定真正需要的指向,因为参数编组的代价是昂贵的。

* 所有 .aidl 文件中的代码注释包含在自动生成 的IBinder接口中(除了在import和package声明语句前面的注释)。

* 只有方法被支持;我们不能在AIDL中公开static fields(静态域)。

下面是一个 .aidl 文件的的示例:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** Demonstrates some 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);
}
很简单地将我们的 .aidl 文件保存到项目src目录下,当我们编译程序时,SDK工具集自动在项目的gen目录下生成IBinder接口。这个自动生成的文件名与 .aidl 文件名匹配,但使用 .java 扩展名(例如,IRemoteService.aidl产生IRemoteService.java)。

如果我们使用Eclipse,总是会立即生成binder类。如果我们不使用Eclipse,当我们下一次编译程序时,Ant工具会自动生成binder类——当编写完 .aidl 文件后,我们应该尽快使用ant debug(或ant release)编译我们的项目,使我们的代码可以链接到生成的类。

2. Implement the interface

当我们编译应用程序时,Android SDK工具集自动生成一个名为.aidl文件名,扩展名为.java的接口文件。生成的接口中,包含一个名为Stub的子类,它是父类接口的抽象类实现(例如,YourInterface.Stub),并声明 .aidl 文件中的所有方法。

注意:Stub也定义了少量的辅助方法,尤其是asInterface(),带有一个IBinder(通常就是传递给客户端onServiceConnected()回调方法的实参)和返回一个stub接口的实例。关于如何进行这类转换的更多信息,请查看Calling an IPC Method方法。

为了实现从 .aidl 文件生成的接口,应该派生自生成的Binder接口(例如,YourInterface.Stub)并实现从 .aidl 文件继承的方法。

下面是一个示例,使用一个匿名实例实现名为IRemoteService的接口(定义在上面的IRemoteService.aidl 示例中):

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};
现在mBinder是Stub类(一个Binder)的一个实例,它定义了service的RPC(远程过程调用)接口。下一步,这个实例将公开给客户端,使它们能够与service交互。

当实现我们的AIDL接口时,我们应该清楚以下一些原则:

* 进来的调用不保证在主线程中执行,因此我们从一开始就需要考虑多线程编程,并且构建我们的service成为线程安全的。

* 默认情况下,RPC调用是同步的。如果我们知道service需要花费长时间完成一个请求,就不应该在activity的主线程中调用它,因为这可能会阻塞应用程序(Android可能会显示一个“Application is Not Responding”对话框(应用程序无响应))——我们通常应该在客户端的单独的线程中调用。

* 我们抛出的任何异常都不会发送给调用者。

3. Expose the interface to clients

一旦我们实现了service的接口,我们需要公开它使客户端能够绑定到service上。为了公开service中的接口,派生自Service并实现onBind()方法来返回一个类实例(这个类实现了自动生成的Stub,正如上面部分讨论的一样)。下面是一个示例service,公开IRemoteService示例接口给客户端:

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}
现在,当客户端(例如activity)调用bindService()连接这个service,客户端的onServiceConnected()回调方法会收到service的onBind()方法返回的mBinder实例。

客户端必须也能访问到接口类,因此如果客户端和service分别在不同的应用程序中,那么客户端的应用程序必须拷贝 .aidl 文件到它的src目录下(将会自动生成android.os.Binder接口——提供客户端访问AIDL方法)。

当客户端在onServiceConnected()回调方法中收到这个IBinder时,它必须调用YourServiceInterface.Stub.asInterface(service)将参数强制类型转换为YourServiceInterface类型。例如:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};
想要更多的示例代码,请查看ApiDemos里面的RemoteService.java类。

Passing Objects over IPC

如果我们有一个类想要通过IPC接口,从一个进程发送到另一个进程,我们可以做到这点。然而,我们必须确保类里面的代码在IPC通道的另一边可用,并且我们的类支持Parcelable接口。对Parcelable接口的支持很重要,因为它允许Android系统将对象分解为原始类型,可以跨进程编组。

为了创建一个支持Parcelable协议的类,我们必须按照以下步骤:

1. 让我们的类实现Parcelable接口

2. 实现writeToParcel,携带当前对象的状态,并将它写入一个Parcel

3. 类中增加一个静态域,名为CREATOR,它是实现Parcelable.Creator接口的对象

4. 最后,创建一个 .aidl 文件,并声明我们的parcelable类(如同在下面的Rect.aidl文件中展示的一样)。

        如果我们使用自定义的编译过程,不要在编译过程中添加.aidl文件。与C语言中的头文件相似,这个.aidl文件不会被编译。

AIDL使用它生成的代码中的方法和域,编组和解编我们的对象。

例如,下面的Rect.aidl文件声明(原文是create,这里我认为declare更好)一个是parcelable的Rect类:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
下面的示例展示了Rect类如何实现Parcelable协议。

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}
Rect类的编组是非常简单的。看看Parcel的其他方法,看看我们可以写入到一个Parcel的其它类型的值。

警告:不要忘了从其它进程接收数据的安全问题。在这种情况下,Rect从Parcel读取四个数字,但不管调用者做什么,都是由我们来确保可以接收值的范围。请查看Security and Permisssions获取更多关于如何防止恶意软件的入侵的信息。

Calling an IPC Method

下面是调用者调用定义在AIDL中的远程接口,必须做的步骤:

1. 在项目的src目录下包含.aidl文件

2. 声明一个IBinder接口(基于AIDL自动生成)的实例

3. 实现ServiceConnection

4. 调用Context.bindService(),传递我们的ServiceConnection实现

5. 在我们的onServiceConnected()实现中,我们将收到一个IBinder实例(名为service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回的参数强制类型转换为YourInterface类型

6. 调用接口中定义的方法。我们总是应该捕获DeadObjectException异常,当连接破坏时它被抛出;这是远程方法调用唯一抛出的异常。

7. 使用我们的接口实例调用Context.unbindService()方法,断开连接。

当调用IPC service时的一些意见:

* 跨进程的对象引用计数

* 我们可以传递匿名对象作为方法的参数

更多关于绑定到一个service上的信息,请阅读Bound Services文档。

下面是一些示例代码,展示了调用AIDL创建的service,从ApiDemos项目的Remote Service示例中提取的。

public static class Binding extends Activity {
    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary mSecondaryService = null;

    Button mKillButton;
    TextView mCallbackText;

    private boolean mIsBound;

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
        mKillButton = (Button)findViewById(R.id.kill);
        mKillButton.setOnClickListener(mKillListener);
        mKillButton.setEnabled(false);

        mCallbackText = (TextView)findViewById(R.id.callback);
        mCallbackText.setText("Not attached.");
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            mKillButton.setEnabled(true);
            mCallbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mKillButton.setEnabled(false);
            mCallbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * 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);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            bindService(new Intent(IRemoteService.class.getName()),
                    mConnection, Context.BIND_AUTO_CREATE);
            bindService(new Intent(ISecondary.class.getName()),
                    mSecondaryConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
    };

    private OnClickListener mUnbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (mIsBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(mSecondaryConnection);
                mKillButton.setEnabled(false);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener mKillListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently our service has a call that will return
            // to us that information.
            if (mSecondaryService != null) {
                try {
                    int pid = mSecondaryService.getPid();
                    // Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here; packages
                    // sharing a common UID will also be able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    mCallbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // Just for purposes of the sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

    };
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值