最近工作上有一个需求需要实现应用间的双向通讯,且主应用可能需要接受多个应用的调用,同时有一定的安全要求,经过研究学习后决定使用AIDL进行实现。
一、为什么用AIDL
实现广义的进程间通讯有多种方式,包括但不限于:
①利用Messager机制进行
②利用ContentProvider通过数据交互方式进行
③利用指定Action的广播的方式进行单向通讯
出于可能需要接受多个应用的调用这一方面,Android中的Messager机制是一个单线程处理的机制,不能满足可能出现的多应用同时调用的需求。出于安全需求这一方面,利用ContentProvider这样的方法也不能满足需求。出于需要双向通讯的需求这一方面,广播方式也要抛弃。
AIDL全称Android Interface Definition Language,用于定义一个可供其他应用访问的接口。通常用来实现跨进程调用,访问其他应用的服务,实现进程间通讯等。
AIDL利用的是C/S模型进行应用间的通讯,主应用实现一个Service扮演Server角色,其他应用可以通过bindService的方式来实现Client角色。
二、Server端应用AIDL的实现
Server端需要实现的代码分为三部分,一个是通讯过程中需要用到的自定义类(非必须),一个是AIDL文件,还有一个是Service代码。
(1)自定义类
AIDL本身支持6种基本类型的传输,分别是int,long,boolean,float,double,String。如果在这六种类型之外还需要传输自定义类型的参数,则需要编写自定义类。注意自定义类都必须继承Parcelable接口,并按需实现readFromParcel(Parcel dest)方法。最好每一个自定义类都放到单独一个文件里,便于后面AIDL文件的处理。
(2)AIDL文件
AIDL文件分为两类,一种是自定义类对应的AIDL文件,另一种是主要的接口AIDL文件。
如工程中定义了自定义类,则每个自定义类都必须编写一个同名的AIDL文件。如A.java文件必须对应A.aidl文件。这类aidl文件格式都一样,里面都仅含两行代码如下:
package **.**.**;
parcel A;
其中第一行声明包名,第二行声明可序列化的类的名称。用Android Studio自动添加AIDL文件时,命名不能与已有的java文件同名,这是因为自动生成的AIDL文件里都会添加一个同名的interface,这在Java语言规范里是不允许的。解决办法是先新增一个不同名字的AIDL文件,删除里面的interface并且写好代码后重命名文件。注意此类的AIDL文件不能添加接口!
接口AIDL文件可以自定义名字,此处以IMyAidlInterface为例,新建AIDL文件后,import需要用到的自定义类,并且在接口里添加需要的方法即可。要注意以下几点:
①自动生成的文件里自带的basicTypes方法是官方告诉开发者AIDL中可以使用的基本类型,如果不想后面每次实现接口都重写该方法可以注释或者删除。
②存在多个方法时,不能存在方法名一样参数不同的方法,否则编译时会报错。
③方法参数是自定义类型时,需要在对应参数签名添加in/out/inout关键字,其中in表示输入型参数,即Server可以获取到Client传递过去的数据,但是不能对Client端的数据进行修改;out表示输出型参数,即Server获取不到Client传递过去的数据,但是能对Client端的数据进行修改;inout表示输入输出型参数,即Server可以获取到Client传递过去的数据,且能对Client端的数据进行修改。
(3)Service代码
上述两点做完后,先make一下,让Gandle处理aidl文件并自动生成IMyAidlInterface.java类,否则下面的步骤可能会报错。
在Service中添加一个IMyAidlInterface.Stub类的变量,其中前面部分与接口AIDL文件名字一样,并且重写里面的IMyAidlInterface接口方法。该类继承于iBinder类,用作其他应用绑定该Service所用,故还应在Service的onBind(Intent intent)方法中返回该变量。
完成Service的编码后,需要在Manifest中注册该Service,并且添加一个<Intent-filter><android:name>参数,以便于用Intent定向启动该Service。同时还应添加android:export=”true”属性,以允许外部应用调用。android:permission属性应有类似作用,此处没做测试,以后有空补上。
三、Client端应用AIDL的实现
与Client端应用类似,Client端应用也分为三部分,分别是通讯过程中需要用到的自定义类(非必须)、AIDL文件,以及调用Service代码。
(1)自定义类
将Server端接口需要用到的自定义类完全复制到Client端,注意包名要一致。
(2)文件
将Server端AIDL文件完全复制到Client端,注意包名要一致。
(3)调用Service代码
与Server端类似,上述两点做完后,先make一下,让Gandle处理aidl文件并自动生成IMyAidlInterface.java类,否则下面的步骤可能会报错。
由于Server端提供的服务都封装在IMyAidInterface.Stub类中,该类通过Service暴露,Client端需要以IMyAidInterface的方式调用,故Client端需要做的工作分别是初始化一个IMyAidInterface,通过BindService的方式定向唤起Server端Service,实例化IMyAidInterface,然后进行调用。bindService部分示例代码如下:
Intent intent = new Intent();
intent.setAction("**.**.AIDLClientService");
intent.setPackage("**.**");
conn = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
iMyAidlInterface =IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name){ }
};
BindService(intent, conn, BIND_AUTO_CREATE);
之后就可以通过iMyAidInterface.***()的方式调用Server端应用提供的服务了。
注意以下几点:
①调用bindService(Intent service, ServiceConnection conn, int flags)时,Android5.0以后的规范规定Intent不能隐式声明。
②定义时需要指定Action以及Package,其中Action是Server端的manifest中注册的参数,Package是接口AIDL文件所在的包名
③BindService是一个异步操作,BindService后不是立即执行onServiceConnected(ComponentName name, IBinder service),因此BindService后不能立即执行iMyAidInterface.***()。可以考虑在进入Activity前BindService,或在onCreate()时BindService并将iMyAidInterface.***()放在某个View的OnClick()事件里。
④注意在调用结束后或者onDestory()里调用unbindService(conn)