前言
为了防止遗忘这些知识点,写一篇博客加深自己的理解,方便忘记后再重新学习。
概述
AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。
AIDL是用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。线程间通讯有多种方式,下面简单介绍下不同之间区别。
多种线程间通讯方式的不同:详情参考此博客
- Bundle:四大组件间的进程间通信方式,简单易用,但传输的数据类型受限。
- 文件共享: 不适合高并发场景,并且无法做到进程间的及时通信。
- Messenger: 数据通过Message传输,只能传输Bundle支持的类型
- ContentProvider:android 系统提供的。简单易用。但使用受限,只能根据特定规则访问数据。
- AIDL:功能强大,支持实时通信,但使用稍微复杂。
- Socket:网络数据交换的常用方式。不推荐使用。
关于AIDL语法
aidl的语法基本和java一样,仅有几点不同之处:
- 文件类型:AIDL文件的后缀是 .aidl,而不是 .java。
- 数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下。(列:编写了两个文件,一个叫做 person.java ,另一个叫做PersonManager.aidl,它们都在 com.mumu.aidl包下 ,在 .aidl 文件里使用person对象我们就必须在 .aidl 文件里面写上 import com.mumu.aidl.person; )
- 默认支持的数据类型包括:
- Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。(实测short不支持)
- String 类型。
- CharSequence类型。
- List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。
- Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。
- parcelable序列化的数据类型:parcelable所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口;
- 定向tag:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。注意,不要全都用 inout ,工程大了系统的开销就会大很多,排列整理参数的开销很大。详细Tag使用方式参考此博客
dome实例(使用的studio工具)
简单实现基本数据类型(如图一个远程相加运算)
服务端
- 创建一个新的项目作为服务端
- 创建服务器端的aidl包
- 创建的aidl包下aidl文件
- 实现aidl文件接口
- 创建服务器端的aidl包
package ready.mumu.service;
interface MyAidl {
int addnum(int num1 , int num2);
}
- 在java包下创建一个service并实现aidl接口
public class Myservice extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
private final MyAidl.Stub iBinder = new MyAidl.Stub(){
@Override
public int addnum(int num1, int num2) throws RemoteException {
Log.v("MUMU","收到输入的远程请求,收到的值是num1:"+num1+" num2:"+num2);
return num1 + num2;
}
}
- 注册表注册service
<service android:name=".Myservice" android:process=":remote">
<intent-filter>
<action android:name="ready.mumu.service.MyAidl"/>
</intent-filter>
</service>
这里说一下Android声明文件中的android:process属性可以为任意组件包括应用指定进程,如果我们需要让一个服务在一个远端进程中运行(而不是标准的它所在的apk的进程中运行),我们可以在声明文件中这个服务的标签中通过android:process属性为其指定一个进程。
“:remote”又是什么意思呢?“remote”不是关键,这个完全可以自己随意取名字,“:”冒号才是关键。
进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。而进程名不以“:”开头的进程属于全局进程,其他应用可以通过某些方式和它跑在同一个进程中。
客户端
- 将服务端对应的aidl拷贝到客户端,要求aidl完全一致,所在的包名也完全一致
- 创建客户端的activity运行界面,并实现按钮点击事件(xml布局文件就不写了,简单的三个输入框一个按钮)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
//软件启动就绑定服务
bindService();
}
private void initUI() {
et_num1 = (EditText) findViewById(R.id.et_num1);
et_num2 = (EditText) findViewById(R.id.et_num2);
et_res = (EditText) findViewById(R.id.et_res);
bt_add = (Button) findViewById(R.id.bt_add);
bt_add.setOnClickListener(this);
}
- 实现绑定服务方法
private void bindService() {
//获取到服务端
Intent intent = new Intent();
//5.0之后必须显示intent启动 绑定服务 , ComponentName两个参数对应是服务包名和服务文件名(文件名必须是包名+文件名)
intent.setComponent(new ComponentName("ready.mumu.service","ready.mumu.service.Myservice"));
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
- 实现ServiceConnection(conn)绑定回调
MyAidl myaidl;
private ServiceConnection conn = new ServiceConnection() {
//绑定上服务的时候执行
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//拿到远程的服务
myaidl = MyAidl.Stub.asInterface(iBinder);
}
//当服务断开的时候执行
@Override
public void onServiceDisconnected(ComponentName componentName) {
//回收资源
myaidl = null;
}
};
- 实现“远程计算”按钮onclick方法
@Override
public void onClick(View view) {
if(view == bt_add){
int num1 = Integer.parseInt(et_num1.getText().toString());
int num2 = Integer.parseInt(et_num2.getText().toString());
try {
//调用远程服务
int res = myaidl.addnum(num1 , num2);
et_res.setText(res+"");
} catch (RemoteException e) {
e.printStackTrace();
et_res.setText("报错了");
}
}
- 在activity销毁的onDestroy方法中解绑服务
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
如此简单的小dome就完成了。
相对复杂的序列化实现dome(如图)
这里在上边dome的基础上又添加了一个“序列化调用”按钮,点击之后输入传入的自定义的序列化数据,下面的步骤是在上个dome基础之上添加的。
服务端
方便看结构,先来一张服务端的代码结构图
- 创建一个java类myParcelable,定义数据类型、构造方法、get/set方法,实现Parcelable序列化(这个类的包名和aidl的包名要一致)
public class myParcelable implements Parcelable{
String name;
int age;
String sex;
//参数是一个Parcel,用它来存储与传输数据
protected myParcelable(Parcel in) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
name = in.readString();
age = in.readInt();
sex = in.readString();
}
public static final Creator<myParcelable> CREATOR = new Creator<myParcelable>() {
@Override
public myParcelable createFromParcel(Parcel in) {
return new myParcelable(in);
}
@Override
public myParcelable[] newArray(int size) {
return new myParcelable[size];
}
};
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//数据存储至Parcel
dest.writeString(name);
dest.writeInt(age);
dest.writeString(sex);
}
//方便数据清晰
@Override
public String toString() {
return "myParcelable{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
- 在aidl包下创建一个aidl文件(myParcelable.aidl)用于定义parcelable对象(非默认支持数据类型必须通过AIDL文件定义才能被使用)。这个myParcelable.aidl和myParcelable.java的包名要一致,所以上个类创建时才说要与aidl包名一致。
// myParcelable.aidl
package ready.mumu.service;
parcelable myParcelable;
- 在原来的MyAidl.aidl中添加一个readText方法
// MyAidl.aidl
package ready.mumu.service;
//注意需要导入包
import ready.mumu.service.myParcelable;
interface MyAidl {
int addnum(int num1 , int num2);
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
String readText(in myParcelable par);
}
客户端
结构附图
- 将服务端的myParcelable.java和myParcelable.aidl拷贝过来,注意包名一致
- 将服务端的MyAidl.ail复制到客户端,此文件要保持客户端和服务端一致。
- 实现activity中“序列化调用”按钮onclick方法
if(view == bt_par){
try {
String msg = myaidl.readText(new myParcelable("张三",18,"男"));
et_res.setText(msg);
} catch (RemoteException e) {
e.printStackTrace();
et_res.setText("序列化出错了");
}
}
如此,序列化数据的远程调用也就结束了。
结语
可能看起来会稍微混乱一点,实现一下就会发现其实aidl使用还是很简单的,最后留下dome地址,看dome可能会相对更容易理解一些。dome下载地址
作者:A189lin 发表于2016/11/7 19:53:44 原文链接
阅读:22 评论:0 查看评论