AIDL详解

AIDL(Android接口定义语言)和其它接口定义语言一样,它允许你定义Client和Service约定好的编程接口,以实现两者的进程间通信(IPC)。在安卓系统中,一个进程通常不能访问另一进程的内存。所以,需要把他们的对象分解成操作系统可以理解的原语,这样才能把对象传递给对方进程。接口定义语言的编写很复杂,Android系统已经实现了,所以你只需要关注怎样定义接口。

只有当来自不同应用程序的Client访问你的Service,并且需要在Service中处理多线程任务时,才需要使用AIDL。如果不需要在不同的应用程序间执行并发IPC,则应该通过Binder创建接口;如果要执行IPC,但不需要处理多线程,请使用Messenger实现接口。在学习AIDL之前,请确保已对Bound Service有充分的了解。

调用AIDL接口是直接调用的函数,不能确定函数的调用是在哪个线程中进行的。调用是来自本地进程中还是远程进程,会导致如下不同的情况:
1、 从本地进程发起的调用,运行在调用者所在的线程。如果从你的UI线程调用,该线程在AIDL接口中继续执行。如果它是另一个线程,那就是在Service中执行代码的那个线程。因此,如果只有本地线程访问Service,你可以完全控制在哪个线程中执行它(但是如果是这样的话,那么你不应该用AIDL,而应该通过实现Binder创建接口来代替AIDL)。
2、 从远程进程发起的调用,运行在系统维护的线程池创建的线程中,且在Service所在进程中。因为可能同时发起调用,你必须准备好处理来自未知线程的调用,即AIDL接口的实现必须是线程安全的。
3、 oneway关键字会影响远程调用的行为。当使用这个关键字时,远程调用不会阻塞;它只发送事务数据并立即返回。接口的实现最终将它作为从Binder thread pool发起的普通线程调用。如果在本地调用中使用oneway,那么没有影响,且调用仍然是同步的。

定义AIDL接口

AIDL接口是用java语法定义,以.aidl文件保存,并保存在Service和Client的src/目录下。编译包含.aidl文件的APP时, Android SDK tools会根据.aidl文件生成IBinder interface,并保存到工程的/gen目录。Service必须实现IBinder接口。Client绑定到Service后,就可以调用IBinder中的函数进行进程间通信。使用AIDL创建Bounded Service的步骤如下:
1、 创建.aidl文件。
这个文件定义了带有方法签名的编程接口。
2、 实现接口。
Android SDK工具基于你的.aidl文件生成java语言格式的接口。这个接口有一个命名为Stub的内部抽象类,它继承于Binder,并实现你AIDL接口中的方法。你必须扩展Stub类,并实现方法。
3、 把接口开放给Client。
继承一个Service,重写onBind()函数,返回你实现的Stub对象。

APP发布后,任何有关AIDL接口的修改都要考虑到向前兼容,避免破坏使用您Service的其他应用程序。因为你的.aidl文件必须copy到其它Client,所以你必须兼容原来的接口。

1、 创建.aidl文件

AIDL使用简单的语法让你可以在一个接口中申明1个或多个方法,这些方法可以携带参数,也可以有返回值。参数和返回值可以是任何类型,甚至是其他的AIDL生成的接口。.aidl文件是用java编程语言构建的,每一个aidl文件只能定义一个接口,这个接口只需要接口声明和方法签名。

默认情况下,AIDL支持下列数据类型:
•java编程语言中的所有基本类型(如int,long,char, boolean等)
•String
•CharSequence
•List
List中的所有元素必须是支持的数据类型,如本列表中的类型、其它AIDL生成的接口、你申明的parcelables数据类型。List可以用作“泛型”类(例如,List)。虽然函数总申明使用List,但是对方实际接收到的具体类总是ArrayList。
•Map
Map中的所有元素必须是支持的数据类型,如本列表中的类型、其它AIDL生成的接口、你申明的parcelables数据类型。泛型映射Map(如Map

// 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/目录,然后build工程,SDK tools会在工程的/gen目录生成IBinder interface。生成的文件名与.aidl文件名相同,但是扩展名为.java,如IRemoteService.aidl编译生成IRemoteService.java。

2、实现接口

当你编译你的APP,Android SDK tools会根据.aidl文件生成.java接口文件。生成的接口包括一个名为Stub的子类,是它的父接口的一种抽象的实现(例如,YourInterface.Stub),并且申明了.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类的一个实例,它定义了Service的RPC接口。在下一步中,将这个实例暴露给Clients,这样Client就可以与Service交互。实现AIDL接口时,需要遵循下面的规则:
•对接口的调用可能在未知线程上执行,因此您需要从一开始就考虑多线程,并正确地将您的Service构建为线程安全的。
•默认情况下,RPC调用是同步的。如果您知道Service需要超过几毫秒才能完成一个请求,那么您不应该从Activity的主线程调用它,因为这样可能导致APP出现ANR,所以您应该从Client的一个单独线程去调用它。
•Service抛出的异常不会被返回给调用者。

把接口开放给Client

一旦您实现了Service的接口,就需要将其公开给Client,这样Client就可以绑定到Service了。开放接口需要继承Service,实现onBind()且返回一个Stub类的实例。下面例子中Service把IRemoteService接口开放给Client:

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
        }
    };
}

现在,当一个Client(如Activity)调用bindService()连接到该Service,Client的onServiceConnected()回调就会接收到Service的onBind()方法返回的mBinder实例,然后Client调用YourServiceInterface.Stub.asInterface(service)把IBinder映射为YourServiceInterface类型。如果Client和Service不在同一个APP,那么必须复制.aidl文件到Client的src/目录。如下所示:

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;
    }
};

通过IPC传递对象

您可以通过IPC接口把一个类从一个进程传递到另一个进程。然而,这个类必须支持Parcelable接口,因为它允许Android系统将对象分解成可以跨越进程的原语。创建一个支持Parcelable协议的类的步骤如下:
1、 让你的类实现Parcelable接口。
2、 实现writeToParcel方法,把当前对象的状态写入Parcel。
3、 在你的类中添加一个叫CREATOR的static字段,并实现Parcelable.Creator接口。
4、 最后,创建一个aidl文件声明您的Parcelable类(如下Rect.aidl)。

如下是Rect.aidl文件:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

如下是实现Parcelable类的Rect.java

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, int flags) {
        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();
    }

    public int describeContents() {
        return 0;
    }
}

要注意从其他进程读取数据的安全限制。在此例子中,Rect从Parcel读取4个值,但是需要确保这些值在可接受的范围值内。有关如何使应用程序免受恶意软件攻击的更多信息,请参见 Security and Permissions。

调用IPC方法

下面是Client调用远程AIDL接口的步骤:
1、 把.aidl文件拷贝到Client的src/目录下。
2、 申明一个IBinder接口的实例。
3、 实现ServiceConnection。
4、 调用Context.bindService(),并且传入ServiceConnection的实例。
5、 在onServiceConnected()中,收到IBinder实例。调用YourInterfaceName.Stub.asInterface((IBinder)service),把IBinder转换为YourInterface类型。
6、 调用接口定义的方法。捕获并处理DeadObjectException,当连接断开时会收到这个异常。捕获并处理SecurityException,当进行IPC的两个进程有AIDL定义冲突时会接收到这个异常。
7、 用Context.unbindService()断开连接。

关于调用IPC Service的几点建议:
•对象是跨进程引用计数的。
•可以将匿名对象作为方法参数发送。

下面的例子展示怎样调用AIDL 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.
            Intent intent = new Intent(Binding.this, RemoteService.class);
            intent.setAction(IRemoteService.class.getName());
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            intent.setAction(ISecondary.class.getName());
            bindService(intent, 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);
            }
        }

    };
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值