Android--AIDL,一种实现进程间通信的方式

9/6/2015来源:Java教程人气:1801

Android--AIDL,一种实现进程间通信的方式

一、什么是AIDL

  AIDL:Android Interface Definition Language,即Android接口定义语言,是我们实现ipC的一种常用手段。

  我们知道,Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote PRocedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、BroadcastReceiver 和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。

二、实现AIDL时的所需类、接口

  1、Parcelable、Parcel

   当我们借助AIDL来在进程之间的复杂数据传递时,要实现Parcelable接口。那什么是Parcelable接口呢?我们看Android API的相关介绍:

   Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementing the Parcelable.Creator interface.

   Android中所有需要跨进程传递的复杂数据对象都必须实现Parceable接口。实现Parcelable接口是为了让对象序列化,以进行进程间的数据传递。java中也有一种实现序列化的方式:实现Serializable接口。但在Android中,不使用这类方式;实现Parcelable接口,一般更高效。

   我们看下该接口的源码定义,这里为了方便,去除掉了一些代码注释片段:

public interface Parcelable {    public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;        public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;        /**     * Describe the kinds of special objects contained in this Parcelable's     * marshalled representation.     *       * @return a bitmask indicating the set of special object types marshalled     * by the Parcelable.     */    public int describeContents();        /**     * Flatten this object in to a Parcel.     *      * @param dest The Parcel in which the object should be written.     * @param flags Additional flags about how the object should be written.     * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.     */    public void writeToParcel(Parcel dest, int flags);    /**     * Interface that must be implemented and provided as a public CREATOR     * field that generates instances of your Parcelable class from a Parcel.     */    public interface Creator<T> {        /**         * Create a new instance of the Parcelable class, instantiating it         * from the given Parcel whose data had previously been written by         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.         *          * @param source The Parcel to read the object's data from.         * @return Returns a new instance of the Parcelable class.         */        public T createFromParcel(Parcel source);                /**         * Create a new array of the Parcelable class.         *          * @param size Size of the array.         * @return Returns an array of the Parcelable class, with every entry         * initialized to null.         */        public T[] newArray(int size);    }    public interface ClassLoaderCreator<T> extends Creator<T> {        public T createFromParcel(Parcel source, ClassLoader loader);    }}

  再看API文档中给我们提供的一个实现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();     } }

   实现Parcelable接口的步骤:

   1、重写describeContents方法,内容描述,我们返回0即可。

   2、重写writeToParcel方法,将自己的对象包装成一个Parcel对象,即将自定义对象写入到一个Parcel对象,通过这个序列化的Parcel对象来传递数据。这里我们可以将Parcel对象看成是个容器,它包装了我们定义的对象,方便在别的进程中重新获取数据。

   3、实例化静态内部对象CREATOR实现接口Parcelable.Creator,这里接口的实现必须与下面的描述一致,包括大小写、限定修饰符等。

 public static final Parcelable.Creator<MyParcelable> CREATOR

   注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方 法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。

  writeToParcel方法将对象包装成一个Parcel对象,createFromParcel方法又将Parcel对象映射成定义的对象。

   这里可以看下API文档中的方法说明,再结合代码理解:

   writeToParcel():Flatten this object in to a Parcel.

   createFromParcel():Create a new instance of the Parcelable class, instantiating it from the given Parcel whose data had previously been written by Parcelable.writeToParcel().

  可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。

  2、ServiceConnection

  AIDL通常是一个Client端绑定一个Service端,在客户端中远程绑定一个Service服务,需要实现ServiceConnection接口。

   ServiceConnection:Interface for monitoring the state of an application service. See Service and Context.bindService() for more information.Like many callbacks from the system, the methods on this class are called from the main thread of your process.

   要实现ServiceConnection接口,

   必须重写两个方法:

   1)、public abstract void onServiceConnected (ComponentName name, IBinder service)

   调用这个方法来传送Service中返回的IBinder对象,进而可以获取到这个IBinder对象,在Client端调用AIDL的方法。

   2)、public abstract void onServiceDisconnected (ComponentName name)

   Called when a connection to the Service has been lost. This typically happens when the process hosting the service has crashed or been killed. Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.

   要绑定Service时,调用bindService,给它传递ServiceConnection的实现和封装了约定的action的Intent对象。解除绑定时,调用unbindService()。

三、AIDL的实现步骤

   AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类构架,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:

(1)在Eclipse Android工程的Java包目录中建立一个扩展名为aidl的文件。该文件的语法类似于Java代码,但会稍有不同。 (2)如果aidl文件的内容是正确的,ADT会自动生成一个Java接口文件(*.java)。 (3)建立一个服务类(Service的子类)。 (4)实现由aidl文件生成的Java接口。 (5)在AndroidManifest.xml文件中配置AIDL服务,尤其要注意的是,<action>标签中android:name的属性值就是客户端要引用该服务的ID,也就是Intent类的参数值。   如果我们是自定义了一个对象类型,并要将它运用到AIDL中,我们除了要这个类型实现Parcelable接口外,还要为这个类型创建一个aidl文件,目的是定义一个Parcelable类,告诉系统我们需要序列化和反序列化的类型。每一个实现了Parcelable的类型都需要对应的.aidl文件。AIDL编译器在编译AIDL文件时会自动查找此类文件。  AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是在与接口相同的包中。下面是AIDL能支持的数据类型:

  1.AIDL支持Java原始数据类型。

  2.AIDL支持String和CharSequence。

  3.AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句,即使位于同一个包中。

  4.AIDL支持传递实现了android.os.Parcelable接口的复杂类型,同样在引用这些类型时也需要import语句。

  5.AIDL支持java.util.List和java.util.Map,但是有一些限制。集合中项的允许数据类型包括Java原始类型、 String、CharSequence或是android.os.Parcelable。无需为List和Map提供import语句,但需要为 Parcelable提供import语句。

  6.非原始类型中,除了String和CharSequence以外,其余均需要一个方向指示符。方向指示符包括in、out、和inout。in表示由客户端设置,out表示由服务端设置,inout表示客户端和服务端都设置了该值。

  下面先看下Service端和Client端的代码工程结构:

  

  首先我们创建自己的Student类并按照上述规则实现Parcelable接口,本文的示例都是以这个类对象为传输的对象:

package com.aidl.service;public class Student implements Parcelable {    public Student(String name, int age, String college) {        this.name = name;        this.age = age;        this.college = college;    }    public Student() {    }    private Student(Parcel source) {        readFromParcel(source);    }    private String name;    private int age;    private String college;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    public String getCollege() {        return college;    }    public void setCollege(String college) {        this.college = college;    }    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeString(name);        dest.writeInt(age);        dest.writeString(college);    }    public void readFromParcel(Parcel source) {        this.name = source.readString();        this.age = source.readInt();        this.college = source.readString();    }    public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() {        @Override        public Student createFromParcel(Parcel source) {            return new Student(source);        }        @Override        public Student[] newArray(int size) {            return new Student[size];        }    };}

   软后创建该类对应的aidl文件,告诉我们要序列化和反序列化的类型是Student:

package com.aidl.service;parcelable Student;  

   创建aidl接口文件,本文是在com.file.aidl包中,这里要注意类型的导入,不然编译器会识别不出类型:

package com.file.aidl;import com.aidl.client.Student;interface StudentInfo {     String getStudentInfo(in Student p, int year);     Student setStudentInfo(in Student p);     int getStudentListCount(in List<Student> list);}

   这里要注意,Client端和Service端中,aidl接口文件要包中在同名的package,文件名也要相同,不然我们在运行程序时,会抛出: java.lang.SecurityException: Binder invocation to an incorrect interface 进程通信异常。最简单的方法就是在Service端编写好代码后,直接copy到Client端工程中。

   eclipse会帮我们编译出对应的.java文件:

/* * This file is auto-generated.  DO NOT MODIFY. * Original file: D:\\Users\\xzm\\workspace\\AndroidAIDLServiceTest1\\src\\com\\file\\aidl\\StudentInfo.aidl */package com.file.aidl;public interface StudentInfo extends android.os.IInterface {    /** Local-side IPC implementation stub class. */    public static abstract class Stub extends android.os.Binder implements com.file.aidl.StudentInfo {        private static final java.lang.String DESCRIPTOR = "com.file.aidl.StudentInfo";        /** Construct the stub at attach it to the interface. */        public Stub() {            this.attachInterface(this, DESCRIPTOR);        }        /**         * Cast an IBinder object into an com.file.aidl.StudentInfo interface, generating a proxy if needed.         */        public static com.file.aidl.StudentInfo asInterface(android.os.IBinder obj) {            if ((obj == null)) {                return null;            }            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin != null) && (iin instanceof com.file.aidl.StudentInfo))) {                return ((com.file.aidl.StudentInfo) iin);            }            return new com.file.aidl.StudentInfo.Stub.Proxy(obj);        }        @Override        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_getStudentInfo: {                data.enforceInterface(DESCRIPTOR);                com.aidl.service.Student _arg0;                if ((0 != data.readInt())) {                    _arg0 = com.aidl.service.Student.CREATOR.createFromParcel(data);                } else {                    _arg0 = null;                }                int _arg1;                _arg1 = data.readInt();                java.lang.String _result = this.getStudentInfo(_arg0, _arg1);                reply.writeNoException();                reply.writeString(_result);                return true;            }            case TRANSACTION_setStudentInfo: {                data.enforceInterface(DESCRIPTOR);                com.aidl.service.Student _arg0;                if ((0 != data.readInt())) {                    _arg0 = com.aidl.service.Student.CREATOR.createFromParcel(data);                } else {                    _arg0 = null;                }                com.aidl.service.Student _result = this.setStudentInfo(_arg0);                reply.writeNoException();                if ((_result != null)) {                    reply.writeInt(1);                    _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);                } else {                    reply.writeInt(0);                }                return true;            }            case TRANSACTION_getStudentListCount: {                data.enforceInterface(DESCRIPTOR);                java.util.List<com.aidl.service.Student> _arg0;                _arg0 = data.createTypedArrayList(com.aidl.service.Student.CREATOR);                int _result = this.getStudentListCount(_arg0);                reply.writeNoException();                reply.writeInt(_result);                return true;            }            }            return super.onTransact(code, data, reply, flags);        }        private static class Proxy implements com.file.aidl.StudentInfo {            private android.os.IBinder mRemote;            Proxy(android.os.IBinder remote) {                mRemote = remote;            }            @Override            public android.os.IBinder asBinder() {                return mRemote;            }            public java.lang.String getInterfaceDescriptor() {                return DESCRIPTOR;            }            @Override            public java.lang.String getStudentInfo(com.aidl.service.Student p, int year) throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                java.lang.String _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    if ((p != null)) {                        _data.writeInt(1);                        p.writeToParcel(_data, 0);                    } else {                        _data.writeInt(0);                    }                    _data.writeInt(year);                    mRemote.transact(Stub.TRANSACTION_getStudentInfo, _data, _reply, 0);                    _reply.readException();                    _result = _reply.readString();                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }            @Override            public com.aidl.service.Student setStudentInfo(com.aidl.service.Student p) throws android.os.RemoteException {                android.os.Parcel _data = android.os.Parcel.obtain();                android.os.Parcel _reply = android.os.Parcel.obtain();                com.aidl.service.Student _result;                try {                    _data.writeInterfaceToken(DESCRIPTOR);                    if ((p != null)) {                        _data.writeInt(1);                        p.writeToParcel(_data, 0);                    } else {                        _data.writeInt(0);                    }                    mRemote.transact(Stub.TRANSACTION_setStudentInfo, _data, _reply, 0);                    _reply.readException();                    if ((0 != _reply.readInt())) {                        _result = com.aidl.service.Student.CREATOR.createFromParcel(_reply);                    } else {                        _result = null;                    }                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }            @Override            public int getStudentListCount(java.util.List<com.aidl.service.Student> list) 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);                    _data.writeTypedList(list);                    mRemote.transact(Stub.TRANSACTION_getStudentListCount, _data, _reply, 0);                    _reply.readException();                    _result = _reply.readInt();                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }        }        static final int TRANSACTION_getStudentInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);        static final int TRANSACTION_setStudentInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);        static final int TRANSACTION_getStudentListCount = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);    }    public java.lang.String getStudentInfo(com.aidl.service.Student p, int year) throws android.os.RemoteException;    public com.aidl.service.Student setStudentInfo(com.aidl.service.Student p) throws android.os.RemoteException;    public int getStudentListCount(java.util.List<com.aidl.service.Student> list) throws android.os.RemoteException;}

   最后,编写自己的Service:

package com.aidl.service;public class IAIDLService extends Service {    @Override    public IBinder onBind(Intent intent) {        return new MyBinder();    }    class MyBinder extends StudentInfo.Stub {        @Override        public String getStudentInfo(Student p, int year)                throws RemoteException {            return "Student'info is: " + "Name: " + p.getName() + " Age: "                    + p.getAge() + " College: " + p.getCollege() + " Time: "                    + year;        }        @Override        public Student setStudentInfo(Student p) throws RemoteException {            if (p.getAge() == 25) {                p.setCollege("SWJTU");                p.setName("Jhon");            } else                p.setCollege("SCU");            return p;        }        @Override        public int getStudentListCount(List<Student> list)                throws RemoteException {            return list.size();        }    }}

   最后,在配置文件中配置自己的服务:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.aidl.service"    android:versionCode="1"    android:versionName="1.0" >    <uses-sdk        android:minSdkVersion="14"        android:targetSdkVersion="21" />    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"        android:label="@string/app_name"        android:theme="@style/APPTheme" >                <service android:name="IAIDLService">            <intent-filter>                <action android:name="com.aidl.student"/>            </intent-filter>        </service>            </application></manifest>

  这里,Service端的代码就结束了。接下来是Client端的代码。

  有文件目录可知,Service端和Client端有几部分代码可以共用,我们直接copy过去即可,再次提醒,要注意aidl接口文件的路径。

  先看布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    tools:context="${relativePackage}.${activityClass}" >    <Button         android:id="@+id/connect"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:text="连接服务"        />    <Button     android:id="@+id/getInfo"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="获取学生信息"    /><Button     android:id="@+id/setInfo"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="设置学生信息"    /><Button     android:id="@+id/getCount"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="获取学生数量"    /><Button     android:id="@+id/disconnect"    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:text="断开连接"    /></LinearLayout>

   接着是主Activity文件:

package com.aidl.client;public class MainActivity extends Activity {    private static final String action = "com.aidl.student";    private Button getButton;    private Button setButton;    private Button getCountButton;    private Button bConnect;    private Button bDisconnect;    private StudentInfo info;    private boolean bool = false;    @Override    protected void onCreate(Bundle savedInstanceState) {                super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        getButton = (Button) findViewById(R.id.getInfo);        setButton = (Button) findViewById(R.id.setInfo);        getCountButton = (Button) findViewById(R.id.getCount);        bConnect = (Button) findViewById(R.id.connect);        bDisconnect = (Button) findViewById(R.id.disconnect);        bConnect.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                if (!bool) {                    Intent intent = new Intent(action); // 这里的action就是Service端配置的action,必须要保持一致                    Log.d("test", "before bindService()");                    bindService(intent, connection, BIND_AUTO_CREATE); // 绑定到远程Service                    Log.d("test", "after bindService()");                }                bConnect.setClickable(false);                bDisconnect.setClickable(true);            }        });                getButton.setOnClickListener(new OnClickListener() {                        @Override            public void onClick(View v) {                Student stu = new Student("Jim", 18, "Bb college");                try {                    String stuInfo = info.getStudentInfo(stu, 2010);                    Toast.makeText(getApplicationContext(), stuInfo,                            Toast.LENGTH_SHORT).show();                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });        setButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                Student stu = new Student("Jerry", 25, "Cc college");                try {                    Student s = info.setStudentInfo(stu);                    String info = "Name: " + s.getName() + " Age: "                            + s.getAge() + " College: " + s.getCollege();                    Toast.makeText(getApplicationContext(), info,                            Toast.LENGTH_SHORT).show();                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });        getCountButton.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                List<Student> list = new ArrayList<Student>();                list.add(new Student("cc", 19, "Dd coolege"));                list.add(new Student("dd", 20, "Ee coolege"));                try {                    int count = info.getStudentListCount(list);                    Toast.makeText(getApplicationContext(),                            "The count: " + count, Toast.LENGTH_SHORT).show();                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });                bDisconnect.setOnClickListener(new OnClickListener() {            @Override            public void onClick(View v) {                if (bool) {                    Log.d("test", "before unbindService()");                    unbindService(connection);                    Log.d("test", "after unbindService()");                }                bDisconnect.setClickable(false);                bConnect.setClickable(true);                bool = false;            }        });    }    private MyConnection connection = new MyConnection();    private class MyConnection implements ServiceConnection {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            info = StudentInfo.Stub.asInterface(service); // 绑定成功就会拿到传过来的MyBinder对象            if (info == null) {                Toast.makeText(getApplicationContext(), "服务连接失败!",                        Toast.LENGTH_SHORT).show();                return;            } else {                bool = true;                Toast.makeText(getApplicationContext(), "服务连接成功!",                        Toast.LENGTH_SHORT).show();            }            Log.d("test", "onServiceConnected");        }        @Override        public void onServiceDisconnected(ComponentName name) {            Log.d("test", "onServiceDisconnected");        }    }}

上几张运行效果图,运行时要先连接服务,再进行后续操作:

源码工程下载连接

参考资料:

http://www.cnblogs.com/renqingping/archive/2012/10/25/Parcelable.html

http://blog.csdn.net/liuhe688/article/details/6409708