AIDL

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AIDL相关的知识,希望对你有一定的参考价值。

AIDL的作用

    在android平台,每个应用程序都是一个单独的JVM,都运行在自己的进程空间里, 通常,一个进程不允许访问另一个进程的内存空间(一个应用不能访问另一个应用)。当用户(程序开发人员)想在一个App中访问另一个App的进程空间的时候,就需要进程间通信。在Android中,远程服务为我们提供了实现进程间通信的方式,其中,AIDL是应用程序开发人员常的一种方式。


    AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以在Android设备上两个进程之间进行进程间通信(interprocess communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。换句比较浅显的话来说,就是我这个App应用的activity,需要调用其他App应用的Service.当然同一App应用的activity 与service也可以在不同进程间,这可以设置Service配置中,android:process=":remote"。

    

    AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。

    可以看出,aidl的适用场景为: 只有你允许客户端从不同的应用程序为了进程间的通信而去访问你的service时,你可以使用AIDl来实现。例如,百度地图给我们提供了下面的service:com.baidu.location.f,我们只要在我们的应用程序的manifest.xml文件中声明这个service,就能用它提供的服务了。:

    <service

        android:name="com.baidu.location.f"

        android:enabled="true"

        android:process=":remote" >

    </service>


定义AIDL接口

    AIDL接口文件,和普通的接口内容没有什么特别,只是它的扩展名为.aidl。保存在src目录下。如果其他应用程序需要IPC,则那些应用程序的src也要带有这个文件。Android SDK tools就会在gen目录自动生成一个IBinder接口文件。service必须适当地实现这个IBinder接口。那么客户端程序就能绑定这个service并在IPC(Inter-Process Communication,进程间通信)时从IBinder调用方法。

    每个aidl文件只能定义一个接口,而且只能是接口的声明和方法的声明。

1.创建.aidl文件

    AIDL只支持方法,不能定义静态成员,并且方法也不能有类似public等的修饰符;AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。

    其中对于Java编程语言的基本数据类型 (int, long, char, boolean等),String和CharSequence,集合接口类型List和Map,不需要import 语句。

    而如果需要在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。AIDL允许传递实现Parcelable接口的类,需要import.

    需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。

    AIDL只支持接口方法,不能公开static变量。

    例如 (IMyService.aidl): 

    package com.demo; 

    

    import com.demo.Person; 

    

    interface IMyService { 

        void savePersonInfo(in Person person); 

        List<Person> getAllPerson(); 

    }

2.实现接口

    创建一个类实现刚才那个aidl的接口:

    public class RemoteService extends Service { 

        private LinkedList<Person> personList = new LinkedList<Person>(); 

        @Override 

        public IBinder onBind(Intent intent) { 

            return mBinder; 

        }

    

        private final IMyService.Stub mBinder = new IMyService.Stub(){

            @Override 

            public void savePersonInfo(Person person) throws RemoteException { 

                if (person != null){ 

                    personList.add(person); 

                } 

            } 

            

            @Override 

            public List<Person> getAllPerson() throws RemoteException { 

                return personList; 

            } 

        }; 

    }

 

    这里会看到有一个名为IMyService.Stub类,查看aidl文件生成的Java文件源代码就能发现有这么一段代码:

    public static abstract class Stub extends android.os.Binder implements com.demo.IMyService

    原来Stub类就是继承于Binder类,也就是说RemoteService类和普通的Service类没什么不同,只是所返回的IBinder对象比较特别,是一个实现了AIDL接口的Binder。

    接下来就是关于所传递的数据Bean——Person类,是一个序列化的类,这里使用Parcelable 接口来序列化,是Android提供的一个比Serializable 效率更高的序列化类。

    Parcelable需要实现三个函数:

    1) void writeToParcel(Parcel dest, int flags) 将需要序列化存储的数据写入外部提供的Parcel对象dest。而看了网上的代码例子,个人猜测,读取Parcel数据的次序要和这里的write次序一致,否则可能会读错数据。具体情况我没试验过!

    2) describeContents() 没搞懂有什么用,反正直接返回0也可以

    3) static final Parcelable.Creator对象CREATOR  这个CREATOR命名是固定的,而它对应的接口有两个方法:

    createFromParcel(Parcel source) 实现从source创建出JavaBean实例的功能

    newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话(return new T[size])即可。估计本方法是供外部类反序列化本类数组使用。


仔细观察Person类的代码和上面所说的内容:

public class Person implements Parcelable { 

    private String name; 

    private String telNumber; 

    private int age; 

    

    public Person() {} 

    public Person(Parcel pl){ 

        name = pl.readString(); 

        telNumber = pl.readString(); 

        age = pl.readInt(); 

    } 

    

    public String getName() { 

        return name; 

    } 

    public void setName(String name) { 

        this.name = name; 

    } 

    public String getTelNumber() { 

        return telNumber; 

    } 

    public void setTelNumber(String telNumber) { 

        this.telNumber = telNumber; 

    } 

    public int getAge() { 

        return age; 

    } 

    public void setAge(int age) { 

 

    } 

    @Override 

    public int describeContents() { 

        return 0; 

    } 

    @Override 

    public void writeToParcel(Parcel dest, int flags) { 

        dest.writeString(name); 

        dest.writeString(telNumber); 

        dest.writeInt(age); 

    } 

    

    public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() { 

    

        @Override 

        public Person createFromParcel(Parcel source) { 

            return new Person(source); 

        } 

        @Override 

        public Person[] newArray(int size) { 

            return new Person[size]; 

        } 

    }; 

}


然后创建Person.aidl文件,注意这里的parcelable和原来实现的Parcelable 接口,开头的字母p一个小写一个大写:

package com.demo; 


parcelable Person;

 

    对于实现AIDL接口,官方还提醒我们:

    1. 调用者是不能保证在主线程执行的,所以从一调用的开始就需要考虑多线程处理,以及确保线程安全;

    2. IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应,这种情况应该考虑单独开启一个线程来处理。

    3. 抛出的异常是不能返回给调用者(跨进程抛异常处理是不可取的)。

 

3. 客户端获取接口

    客户端如何获取AIDL接口呢?通过IMyService.Stub.asInterface(service)来得到IMyService对象:

private IMyService mRemoteService; 


private ServiceConnection mRemoteConnection = new ServiceConnection() {    

    public void onServiceConnected(ComponentName className, IBinder service) {    

        mRemoteService = IMyService.Stub.asInterface(service);    

    }    

    public void onServiceDisconnected(ComponentName className) {    

        mRemoteService = null;    

    }    

};

    在生成的IMyService.java里面会找到这样的代码:

public static com.demo.IMyService asInterface(android.os.IBinder obj) {...}

 

而service的绑定没有什么不同:

if (mIsRemoteBound) { 

    unbindService(mRemoteConnection); 

}else{ 

    bindService(new Intent("com.demo.IMyService"),mRemoteConnection, Context.BIND_AUTO_CREATE); 

}


mIsRemoteBound = !mIsRemoteBound;


通过IPC调用/传递数据

    客户端绑定service后就能通过IPC来调用/传递数据了,直接调用service对象的接口方法:

addPersonButton.setOnClickListener( 

    new View.OnClickListener(){ 

        private int index = 0; 

   

        @Override 

        public void onClick(View view) { 

            Person person = new Person(); 

            index = index + 1; 

            person.setName("Person" + index); 

            person.setAge(20); 

            person.setTelNumber("123456"); 

            try { 

                mRemoteService.savePersonInfo(person); 

            } catch (RemoteException e) { 

                e.printStackTrace(); 

            } 

        } 

    }); 


listPersonButton.setOnClickListener( 

    new View.OnClickListener(){ 

   @Override 

        public void onClick(View view) { 

            List<Person> list = null; 

            try { 

                list = mRemoteService.getAllPerson(); 

            } catch (RemoteException e) { 

                e.printStackTrace(); 

            } 

            if (list != null){ 

                StringBuilder text = new StringBuilder(); 

                for(Person person : list){ 

                text.append("\nPerson name:"); 

                text.append(person.getName()); 

                text.append("\n age :"); 

                text.append(person.getAge()); 

                text.append("\n tel number:"); 

                text.append(person.getTelNumber()); 

            } 

            inputPersonEdit.setText(text); 

        }else { 

            Toast.makeText(ServiceActivity.this, "get data error", 

            Toast.LENGTH_SHORT).show(); 

        } 

    } 

});


Permission权限

    如果Service在AndroidManifest.xml中声明了全局的强制的访问权限,其他引用必须声明权限才能来start,stop或bind这个service.

    另外,service可以通过权限来保护她的IPC方法调用,通过调用checkCallingPermission(String)方法来确保可以执行这个操作。


AndroidManifest.xml的Service元素

<service android:name=".RemoteService" android:process=":remote"> 

    <intent-filter> 

        <action android:name="com.demo.IMyService" /> 

    </intent-filter> 

</service>

    这里的android:process=":remote",一开始我没有添加的,在同一个程序里使用IPC,即同一个程序作为客户端/服务器端,结果运行mRemoteService = IMyService.Stub.asInterface(service);时提示空指针异常。观察了人家的在不同程序里进行IPC的代码,也是没有这个android:process=":remote"的。

    也就是说android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。

 

 

 

 

 

 

 

 

 

 

 

 

假设A应用需要与B应用进行通信,调用B应用中的download(String path)方法,B应用以Service方式向A应用提供服务。需要下面四个步骤: 


1> 在B应用中创建*.aidl文件,aidl文件的定义和接口的定义很相类,如:在cn.itcast.aidl包下创建IDownloadService.aidl文件,内容如下:

package cn.itcast.aidl;

interface IDownloadService {

    void download(String path);

}

当完成aidl文件创建后,eclipse会自动在项目的gen目录中同步生成IDownloadService.java接口文件。接口文件中生成一个Stub的抽象类,里面包括aidl定义的方法,还包括一些其它辅助方法。值得关注的是asInterface(IBinder iBinder),它返回接口类型的实例,对于远程服务调用,远程服务返回给客户端的对象为代理对象,客户端在onServiceConnected(ComponentName name, IBinder service)方法引用该对象时不能直接强转成接口类型的实例,而应该使用asInterface(IBinder iBinder)进行类型转换。


编写Aidl文件时,需要注意下面几点: 

    1.接口名和aidl文件名相同。

    2.接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。

    3.Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作为参数或返回值,自定义类型必须实现Parcelable接口。

    4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。

    5.在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。

    6.Java原始类型默认的标记为in,不能为其它标记。


2> 在B应用中实现aidl文件生成的接口(本例是IDownloadService),但并非直接实现接口,而是通过继承接口的Stub来实现(Stub抽象类内部实现了aidl接口),并且实现接口方法的代码。内容如下:

public class ServiceBinder extends IDownloadService.Stub {

    @Override

    public void download(String path) throws RemoteException {

        Log.i("DownloadService", path);

    }

}


3> 在B应用中创建一个Service(服务),在服务的onBind(Intent intent)方法中返回实现了aidl接口的对象(本例是ServiceBinder)。内容如下:

public class DownloadService extends Service {

    private ServiceBinder serviceBinder = new ServiceBinder();

    @Override

    public IBinder onBind(Intent intent) {

        return serviceBinder;

    }

    public class ServiceBinder extends IDownloadService.Stub {

        @Override

        public void download(String path) throws RemoteException {

            Log.i("DownloadService", path);

        }

    }

}

其他应用可以通过隐式意图访问服务,意图的动作可以自定义,AndroidManifest.xml配置代码如下:

<service android:name=".DownloadService" >

    <intent-filter>

        <action android:name="cn.itcast.process.aidl.DownloadService" />

    </intent-filter>

</service>


4> 把B应用中aidl文件所在package连同aidl文件一起拷贝到客户端A应用,eclipse会自动在A应用的gen目录中为aidl文件同步生成IDownloadService.java接口文件,接下来就可以在A应用中实现与B应用通信,代码如下:

public class ClientActivity extends Activity {

    private IDownloadService downloadService;

    

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        this.bindService(new Intent("cn.itcast.process.aidl.DownloadService"), this.serviceConnection, BIND_AUTO_CREATE);//绑定到服务

    }

    

    @Override

    protected void onDestroy() {

        super.onDestroy();

        this.unbindService(serviceConnection);//解除服务

    }

    

    private ServiceConnection serviceConnection = new ServiceConnection() {

        @Override

        public void onServiceConnected(ComponentName name, IBinder service) {

            downloadService = IDownloadService.Stub.asInterface(service);

            try {

                downloadService.download("http://www.itcast.cn");

            } catch (RemoteException e) {

                Log.e("ClientActivity", e.toString());

            }

        }

        @Override

        public void onServiceDisconnected(ComponentName name) {

            downloadService = null;

        }

    };

}


以上是关于AIDL的主要内容,如果未能解决你的问题,请参考以下文章

基于 AIDL 的双向通信(已更新代码)

实现自己的HAL-6 serivce 和aidl层的代码

Android AIDL使用详解_Android IPC 机制详解

AIDL简单示例

进程间的对话——aidl

AIDL介绍以及简单使用