[翻译]Android接口定义语言 (AIDL)

孤人 提交于 2019-12-09 15:00:55

AIDL(Android接口定义语言)与你可能使用过的其它的IDLs是类似的。它允许你定义客户端与service协调一致的编程接口,以便于彼此之间使用进程间通信(IPC)机制进行通信。在Android上,一个进程通常不能访问另一个进程的内存。可以说,它们需要把它们的对象分解为操作系统能够理解的原始数据类型,并在进程之间按次序排列对象。那种排列对象的代码写起来是很乏味的,因此Android通过AIDL来为你处理这些事情。

注意:只有在你允许来自于不同的应用的客户端访问你的service以实现IPC,并想要在你的service中处理多线程时,才需要使用AIDL。如果你不需要跨不同应用执行并发IPC,你应该通过实现一个Binder来创建你的接口,或者如果你想要执行IPC,但不需要处理多线程,可以使用一个Messenger来实现你的接口。无论哪种,请确保在实现一个AIDL之前,你理解了Bound Services。/

在开始设计你的AIDL接口之前,请意识到对于一个AIDL接口的调用是直接的函数调用(大概指的是阻塞调用,在调用一个IPC方法时,客户端线程会一直等待,直到service端处理完成并返回)。你不应该假设调用所发生的线程。依赖于调用是来自于本进程的一个线程,还是一个远端进程,则会发生不同的事情。特别地:

  • 发起于本进程的调用将在发起调用相同的线程中执行。如果这是你的UI线程,则线程将继续在AIDL接口中执行。如果它是另外一个线程,那它将是执行你的service代码的线程。因此,如果只有本地线程访问service,你可以完全控制在哪个线程中来执行它(但如果是那种情况,则你不应该使用AIDL,而应该通过实现一个Binder创建接口)。

  • 调用来自于远端进程,并从平台维护的你自己的进程中的一个线程池派发。你必须为调用可能来自于未知线程做好准备,多个调用可能在同一时刻发生(Service是一个对象,而不是一个线程,这里的未知线程大概指的是binder线程,这里的意思大概是说,AIDL service类要按照线程安全类的标准来构造)。换句话说,一个AIDL接口的实现必须完全是线程安全的。

  • 单路行为的远程调用。当使用时,一个远程调用不发生阻塞;它仅仅是发送事务数据并立即返回。接口的实现最终将它作为来自于Binder线程池的一个普通远端调用来接收。如果单路被用于一个本地调用,则没有影响,调用依然是同步的。

定义一个AIDL接口

你必须使用Java编程语言语法在一个.aidl文件中定义你的AIDL接口,然后同时保存在service所在的应用和其他要bind到service的应用的源代码中(在src/目录下)。

当你编译每个包含.aidl文件的应用时,Android SDK工具会基于.aidl文件产生一个IBinder接口,并把它保存在项目的gen/目录下。Service必须适当的实现IBinder接口。客户端应用可以bind到service并调用来自于IBinder的方法来执行IPC。

要使用AIDL来创建一个bounded service,则遵从以下几个步骤:

  • 创建.aidl文件

    这个文件通过方法签名定义了编程接口。

  • 实现接口

    Android SDK工具基于.aidl文件以Java编程语言产生一个接口。这个接口具有一个名为Stub的内部抽象类,该抽象类扩展了Binder并实现了来自于你的AIDL接口的方法。你必须扩展Stub类并实现那些方法。

  • 将接口暴露给客户端

    实现一个Service并覆写onBind()来返回你的Stub类的实现。

注意:在你首次发布你的AIDL接口之后,对它的任何修改都必须要保持向后兼容,以避免破坏使用了你的service的其他应用。即,由于你的.aidl文件必须被复制到其他的应用以使它们能够访问你的service的接口,你必须维护对于原始接口的支持。

1. 创建.aidl文件

AIDL使用了一种简单的语法供你声明一个接口,接口可以有一个或多个方法,每个方法可以接收一些参数并返回值。参数和返回值可以是任何类型,甚至是其他的AIDL-generated接口。

你必须使用Java编程语言构造.aidl文件。每个.aidl文件必须定义一个单独的接口,并且只需要接口声明和方法签名。

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

  • Java编程语言中的所有原始数据类型(比如int, long, char, boolean, 等等)

  • String

  • CharSequence

  • List

    List中的所有元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。一个List可能被用作一个"generic"类(比如,List<String>)。另外一边实际接收到的具体类总是一个ArrayList,尽管产生的方法使用List接口。

  • Map

    Map中的所有元素必须是所列出的被支持的数据类型,或某种其它的AIDL-generated接口,或你已经声明的parcelables。泛型maps,(比如那些形如Map<String,Integer>的maps)是不被支持的。另外一边实际接收到的具体类总是一个HashMap,尽管产生的方法使用Map接口。

对于没有列出的每种额外的类型你都必须包含一个import声明,即使它们定义在与你的接口相同的package。

当定义你的service接口时,请意识到:

  • 方法可以接收0个或多个参数,并返回一个值或void。

  • 所有的非原始数据类型需要一个指示标记来表明数据的流向。in, out, 或者inout(请参考下面的例子)。

    原始数据类型默认是in,其他则不是。

    注意:你必须把方向限制在真正需要的方向,因为对于参数的排列是昂贵的。

  • .aidl文件中的所有代码注释被包含进了产生的IBinder接口中(除了那些在import和package声明前面的注释)。

  • 只支持方法;你不能在AIDL中暴露static域。

这里有一个.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. 实现接口

当你编译你的应用时,Android SDK工具产生一个以你的.aidl文件命名的.java接口文件。产生的接口包含一个名为Stub的子类,它是一个它的父接口的抽象实现 (比如,YourInterface.Stub),并声明了来自于.aidl文件的所有方法。

注意:Stub也定义了一些辅助方法,最值得注意的就是asInterface()了,它接收一个IBinder (通常是传递给客户端的onServiceConnected()回调方法的那个)并返回一个stub接口的实例。参考调用一个IPC方法部分,来获取更多关于如何做强制类型转换的信息。

要实现产生自.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定义了IPC接口。在下一步中,这个实例会被暴露给客户端,以使它们能够与service交互。

当实现你的AIDL接口时有一些规则应该注意:

  • 进入的调用不保证在主线程执行,因此你需要从一开始就考虑多线程,并适当的构建你的service以实现线程安全。

  • 默认情况下,IPC调用是同步的。如果你知道service需要好多毫秒的时间来完成一个请求,则你不应该在activity的主线程中调用,因为它可能挂起应用(Android可能显示一个"Application is Not Responding"对话框)——你通常应该在客户端的另外一个线程中来调用它们。

  • 你throw的exceptions不会被发回调用者。

3. 将接口暴露给客户端

为你的service实现了接口之后,你需要把它暴露给客户端,以使它们可以bind到它。要将你的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类。

透过IPC传递对象

如果你有一个类,你想要通过IPC接口将它从一个进程发送到另一个进程,那么你可以那样做。然而,你必须确保你的类的代码在IPC通道的另一端也是可以访问的,并且你的类必须支持Parcelable接口。支持Parcelable接口是很重要的,因为它允许Android系统来把对象分解为可以被跨进程处理原始数据类型。

要创建一个支持Parcelable协议的类,你必须做这些事情:

  1. 使你的类实现Parcelable接口。

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

  3. 给你的类添加一个static成员CREATOR,它是一个对象,并实现了Parcelable.Creator接口。

  4. 最后,创建一个.aidl文件,它声明了你的parcelable类 (如下面的Rect.aidl文件所显示的那样)。如果你在使用一个定制的编译过程,则不要把.aidl文件添加到你的build。类似于C语言中的头文件,这个.aidl文件不被编译。

AIDL使用代码中它产生的这些方法和成员来排序和解序你的对象。

比如,这里是一个Rect.aidl文件,用于创建一个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的其它种类的值。

警告:不要忘记从其它进程接收数据的安全隐患。在这个例子中,RectParcel读取4个数字,但你要确保这些数字在可接受的值的范围以内,而无论调用者要去做什麽。参考Security and Permissions来获取更多关于如何使你的应用更安全并远离病毒的信息。

调用一个IPC方法

这里是一个调用类调用一个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的一些说明:

  • 对象是跨进程引用计数的。

  • 你可以发送匿名的对象作为方法参数。

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

这里是一些实例代码,演示了调用一个AIDL-created 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);
            }
        }

    };
}

Done.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!