我们先来简单介绍下什么是AIDL及它的使用场景:
说起AIDL(Android接口定义语言:Android Interface Definition Language),大家都知道在Android中,我们可以利用其来进行IPC(Inter-Process Communication,进程间通信)。
先来看下官方文档中对AIDL的一段描述:
注:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。
可以看到,AIDL的使用场景就是:一对多通信且有并发的IPC需求 。
看到这里,也许大家更困惑了= =,不慌不慌,我先来简单总结下我所理解的AIDL,然后通过一个它的使用案例和基于案例的解析,只要大家耐着性子看完,到最后对AIDL应该都能学有小成啦。
由于在Android中,一个进程通常是无法访问另一个进程的内存,但是日常使用中,我们又经常会遇到IPC(跨进程通信)的需求,那么Android就理所当然地为我们提供了几种进程间通信的机制:文件、Binder、Socket等,而我们这里所讲的AIDL就是基于Binder来实现的(这里对于Binder我们就不做过多的衍生介绍了,非常复杂,以后有空可以单独专研学习下,简单来说就是Binder分为Client和Server两个进程,通过ServiceManager和Binder内核驱动的各种操作来进行跨进程通信)。
由于编写一段基于Binder执行IPC操作的代码比较复杂,于是Android中为我们提供了AIDL这种机制,我们只需要按要求写好相应的.aidl文件,编译后,系统会自动帮我们生成基于Binder的接口,我们就可以用其来实现不同的IPC需求啦~
AIDL的使用及案例
首先,我们来看一下AIDL支持的数据类型:
- Java的基本数据类型;
- String和Charsequence;
- List 和 Map :元素必须是 AIDL 支持的数据类型,Server 端具体的类里则必须是 ArrayList 或者 HashMap;
- AIDL接口;
- Parcelable对象。
需要注意,创建要操作的实体类时,一定要实现Parcelable接口,用于进行序列化与反序列化的操作。
现在,我们就来着手编写一个简单的AIDL实例。
1.创建AIDL
AndroidStudio中,在src→main目录下新建AIDL文件夹。
在java文件夹中创建实现Parcelable的实体类,用于进行序列化和反序列化操作:
package com.sheep.zk.test;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by sheep on 2018/9/5.
*/
public class JRS implements Parcelable {
public static final Creator<JRS> CREATOR = new Creator<JRS>() {
@Override
public JRS createFromParcel(Parcel in) {
return new JRS(in);
}
@Override
public JRS[] newArray(int size) {
return new JRS[size];
}
};
private String mName;
private int mNum;
protected JRS(Parcel in) {
mName = in.readString();
mNum=in.readInt();
}
public JRS(String name,int number) {
mName = name;
mNum=number;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeInt(mNum);
}
@Override
public String toString() {
return "JRS name="+mName+",number="+mNum;
}
}
关于Parcelable的一些操作,这里就不深究了,大家不了解的可以去研究下,一般我们实现序列化就是 Serializable 和 Parcelable这两种方式。
一般在保存数据到 SD 卡或者网络传输时建议使用 Serializable 即可,虽然效率差一些,好在使用方便,而在运行数据传递时建议使用 Parcelable,比如 Intent、Bundle、Binder 等,Android 底层做了优化处理,效率很高。
接着,我们在新建的aidl文件夹中分别创建接口 testAIDL.aidl文件与映射的实体 JRS.aidl文件:(要注意!这个aidl文件夹中的JRS.aidl的包名要和java文件夹中JRS实体类的包名一致!我这个Demo的目录结构如图:
)
JRS.aidl:
// JRS.aidl
package com.sheep.zk.test;
parcelable JRS;
testAIDL.aidl:
package com.sheep;
import com.sheep.zk.test.JRS;
interface testAIDL{
/**
* 传参时除了Java基本类型以及String,CharSequence之外的类型都需要在前面加上定向tag。AIDL中的定向 tag 表示了在跨进程通信中数据的流向,
* 其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。
* 其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,
* 但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的参数为空的对象,
* 但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,
* 并且客户端将会同步服务端对该对象的任何变动。
*/
void addJRS(in JRS jrs);
void reduceJRS(in JRS jrs);
List<JRS> getJRSList();
}
现在我们Make Project,就能生成testAIDL.java文件了,这个java文件我们后续会具体分析,有了它,我们就可以编写服务端与客户端的代码了。
2.编写服务端代码
创建一个服务端service:
public class MyAidlService extends Service{
private final String TAG=this.getClass().getSimpleName();
private ArrayList<JRS> mJRS=new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
mJRS.add(new JRS("wawa",911));
}
private IBinder mIBinder=new testAIDL.Stub() {
@Override
public void addJRS(JRS jrs) throws RemoteException {
mJRS.add(jrs);
}
@Override
public void reduceJRS(JRS jrs) throws RemoteException {
try {
mJRS.remove(jrs);
}catch (Exception e){
Log.e("sheep",e.getMessage());
}
}
@Override
public List<JRS> getJRSList() throws RemoteException {
return mJRS;
}
};
/**
* 客户端与服务端绑定时的回调,返回 mIBinder 后客户端就可以通过它远程调用服务端的方法,即实现了通讯
* @param intent
* @return
*/
@Nullable
@Override
public IBinder onBind(Intent intent) {
mJRS.add(new JRS("haha",1));
mJRS.add(new JRS("gaga",2));
Log.e("sheep","onBind");
return mIBinder;
}
}
从上面代码可以看到,我们绑定时传递给客户端的IBinder对象实质是testAIDL.Stub(),这个我们会在接下来对testAIDL.java的分析中再详细介绍。
因为服务端和客户端是两个进程,所以都需要用到的 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。这里我们为了方便,就在一个项目中同时编写服务端和客户端了,在Manifest中注册 MyAidlService,注意使其运行在另一个进程中:
<service
android:name=".MyAidlService"
android:exported="true"
android:process=":AIDL"
/>
OK,至此服务端我们就编写完了。
3.编写客户端代码
这里,我们创建一个简单的客户端 activity用于演示:
public class MyAidlActivity extends Activity {
private testAIDL myAIDL;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//连接后拿到 Binder,转换成 AIDL,在不同进程会返回个代理
myAIDL=testAIDL.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAIDL=null;
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_myadil);
Button btnAdd=findViewById(R.id.btn_add);
final TextView tvContent=findViewById(R.id.tv_content);
btnAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
JRS jrs=new JRS("Harden",13);
try {
myAIDL.addJRS(jrs);
myAIDL.reduceJRS(new JRS("haha",1));
ArrayList<JRS> arrayList= (ArrayList<JRS>) myAIDL.getJRSList();
tvContent.setText("size="+arrayList.size()+"\n"+arrayList.toString());
Log.e("sheep","size="+arrayList.size()+"\n"+arrayList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
Intent intent=new Intent(this,MyAidlService.class);
bindService(intent,mConnection,BIND_AUTO_CREATE);
}
}
如上代码可知,我们绑定服务端,通过 ServiceConnection 拿到 testAIDL对象,然后就能执行一些 IPC操作了。
bindService 成功后,我们点击两次按钮,执行一些跨进程的请求,来看一下效果:
可以看到,我们在客户端通过 testAIDL 执行了 add与 reduce操作后,服务端的集合 mJRS的数据发生了相应正确的变化,那么至此, activity 与另一个进程的 service 就通信成功了。
现在,我们都知道了一个简单的 AIDL是如何编写的,但大家肯定有疑问,为什么就简单的编写了几个 .aidl文件,就能成功进行跨进程通信了呢?
其实,这一切功能的实现都是基于当时我们Make Project后,Android 自动帮我们生成的 testAIDL.java文件,在这个文件中,系统已经帮我们编写了一套基于 Binder的接口,那么下一篇中,我们就来具体研究下 AIDL 究竟帮我们自动生成了一个怎样的文件吧~~~