IPC是(Inter-Process-Communication)的简称,中文名是:进程间的通信。顾名思义就是可以实现进程与进程之间数据交换的一种机制。
那AIDL又是什么东西呢?AIDL是(Android-Interface-Definition-Language)的简称,中文名是:安卓接口定义语言。
由于Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。
为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、Broadcast和Content Provider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。(摘自搜狗百科)
了解了基本概念之后,我们再说一下AIDL都支持哪些类型的数据:
1、基本数据类型(int、long、char、boolean、double)
2、String和CharSequence
3、List和Map集合
4、集合内元素必须是AIDL支持的数据类型
5、服务端具体使用的集合必须是ArrayList和HashMap
6、Parcelable:实现了Parcelable接口的对象
7、AIDL本身接口也可以在AIDl文件使用。
这里需要说明一下,如果想要在跨进程中传递模型数据,需要实现Parcelbale接口。只有实现了序列化,才可以传递,否则会失败。如果不了解Parcelable的同学可以去搜索一下Parcelable的相关知识。这里不做讲解。
那么如何实现一个自己的AIDL呢?
AIDL有几大步骤:
1、创建AIDL文件
2、在Service中实现AIDL
3、创建客户端,连接远程服务
接下来我们就按照步骤一步一步的实现一个自己的AIDL。
1、创建AIDL文件
首先我们新建一个安卓项目,然后在main目录右键,创建一个AIDL文件,我们先创建一个Person.aidl文件。
Person.aidl文件的内容很简单,内容如下:
// Person.aidl
package com.yangzhenyu.msgservice;
parcelable Person;
这里我解释一下,parcelable Person这行代码的意思是Person是一个序列化的类,可以被用来传递。接下来我们创建Person的实体类,内容也比较简单,直接粘代码了:
package com.yangzhenyu.msgservice;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
public class Person implements Parcelable {
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@NonNull
@Override
public String toString() {
return "姓名:"+this.name+" 年龄:"+this.age;
}
}
Person类有两个变量,一个是name,一个是age,然后实现了Parcelable接口,这一点很重要,上文中已经提到,只有实现了Parcelable的类,才可以在进程中传递,Person对外提供了一个两参的构造方法并且重写了toString方法。
接下来我们再声明一个IPersonManager的AIDL文件,IPersonManager.aidl文件其实是一个接口,里面封装了一些操作,这里我简单的封装了四个方法:
import com.yangzhenyu.msgservice.Person;
interface IPersonManager {
List<Person> getPersonList();
void addPerson(in Person person);
Person getFirstPerson();
void deletePerson(int index);
}
都是对Person这个类的一些简单操作,有一点需要注意,我们需要手动的将Person这个类的包导入一下。
有些同学可能注意到有的方法参数前面会有一个in标记,那个折又是什么意思呢?
声明方法时,如果不是基本数据类型,需要在参数前面增加一个tag,这个tag有三种,in,out,inout,这里表示的是这个参数可以支持的流向:
-
in: 代表这个对象能够从客户端传递到服务器
-
out: 代表这个对象能够作为返回值从服务器到客户端
-
inout: 代表这个对象能从客户端到服务器,也可以作为返回值从服务器到客户端
需要注意的是,创建AIDL文件的时候,会自动生成一个basicTypes方法,这个方法展示了AIDL默认支持的基本数据类型。我们可以删掉这个方法即可。因为我们要写自己的方法。
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
//删除这个方法就行,我们需要实现自己的方法
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
目前位置我们AIDL文件的创建就到此结束了。接下来我们来实现Service。
2、在Service中实现AIDL文件
首先我们需要创建一个MyService继承自Service类,MyService自动重写了onBind方法,这个方法需要返回一个IBiner对象
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
接下来我们需要实现AIDL接口,在这里,我们在MyService中创建一个Person的数组mList,用来对接口方法做具体的实现
private Binder mBinder = new IPersonManager.Stub() {
@Override
public List<Person> getPersonList() throws RemoteException {
return mList;
}
@Override
public void addPerson(Person person) throws RemoteException {
mList.add(person);
Log.d(TAG,"插入成功:"+person.toString());
}
@Override
public Person getFirstPerson() throws RemoteException {
return mList.size()>0?mList.get(0):null;
}
@Override
public void deletePerson(int index) throws RemoteException {
if (mList.size()>index){
Person person = mList.get(index);
mList.remove(index);
Log.d(TAG,"删除成功:"+person.toString());
}
}
};
为了方便使用,我们在Service的构造方法中对mList进行了一次初始化的操作(这个操作可有可无,我这边是为了方便使用),然后在onBind方法中将mBinder对象返回。最后记得去AndroidMainfest.xml文件中声明service,
<service android:name=".MyService"
android:enabled="true"
android:exported="true"
android:process=":YangService">
<intent-filter>
<action android:name="YANGZHENYU.SERVICE"/>
</intent-filter>
</service>
注意:Service需要声明隐式意图,因为要给别的应用程序使用。还有一点需要留意,当你尝试运行项目的时候,可能会报找不到Person这个类,原因是Android Studio默认会去java目录里寻找Person类,我们将Person类放在了aidl目录下,之所以这么做是为了方便别的应用程序拷贝aidl相关的文件,因此为了解决报错问题,我们需要在gradle中声明Person的目录地址
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
......
......
sourceSets {
main{
java.srcDirs = ['src/main/java','src/main/aidl']
}
}
}
到这里,我们的服务器端已经完成了,现在可以运行项目将服务器端安装到手机里面了。接下来我们来实现客户端的代码。
3、创建客户端,连接远程服务
首先第一步我们需要将服务器端的AIDL文件全部拷贝过来,将它放置在与java目录同级的目录下就好,并且保持包名一致。
接下来我们在MainActivity中实现ServiceConnection这个接口
private static ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mManager = IPersonManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
在这里我们主要实现onServiceConnected这个方法,我们通过IPersonManager.Stub.asInterface这个方法拿到服务器的IPersonManager对象,然后就可以操作这个对象里面的方法了。使用和正常的其他方法一样。在这里我在activity_main.xml中定义了五个按钮,分别是绑定远程服务、获取全部列表、插入人员、获取第一个人、删除一个人。绑定服务通过隐式意图来完成,注意一点,在Android5.0之后,隐式意图需要传递package,否则会报错。然后我们还需要在activity的onDestroy中解绑,否则会造成内存泄漏。
Intent intent = new Intent();
intent.setAction("YANGZHENYU.SERVICE");
intent.setPackage("com.yangzhenyu.msgservice");
bindService(intent,mConn,BIND_AUTO_CREATE);
。。。
。。。
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConn);
}
完整的代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG="Client";
private static IPersonManager mManager;
private Button mBindBtn;
private Button mObtainListBtn;
private Button mInsertBtn;
private Button mFirstBtn;
private Button mDeleteBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBindBtn = findViewById(R.id.bind_btn);
mObtainListBtn = findViewById(R.id.obtain_list_btn);
mInsertBtn = findViewById(R.id.insert_btn);
mFirstBtn = findViewById(R.id.first_btn);
mDeleteBtn = findViewById(R.id.delete_btn);
mBindBtn.setOnClickListener(this);
mObtainListBtn.setOnClickListener(this);
mInsertBtn.setOnClickListener(this);
mFirstBtn.setOnClickListener(this);
mDeleteBtn.setOnClickListener(this);
}
private static ServiceConnection mConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mManager = IPersonManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
public void onClick(View v) {
try {
switch (v.getId()) {
case R.id.bind_btn:
Intent intent = new Intent();
intent.setAction("YANGZHENYU.SERVICE");
intent.setPackage("com.yangzhenyu.msgservice");
bindService(intent,mConn,BIND_AUTO_CREATE);
break;
case R.id.obtain_list_btn:
if (mManager!=null){
List<Person> list = mManager.getPersonList();
Log.d(TAG,list.toString());
}
break;
case R.id.insert_btn:
if (mManager!=null){
mManager.addPerson(new Person("张麻子",38));
}
break;
case R.id.first_btn:
if (mManager!=null){
Person person = mManager.getFirstPerson();
Log.d(TAG,"第一个人是:"+person.toString());
}
break;
case R.id.delete_btn:
if (mManager!=null){
mManager.deletePerson(2);
}
break;
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConn);
}
}
接下来我们就可以将客户端安装到手机上了
我们首先点击绑定远程服务,这个时候,客户端拿到IPersonManager对象,接下来,我们点击获取全部列表,此时控制台打印服务器端的初始化的list的内容:
com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:乐乐 年龄:23, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25]
接下来我们尝试插入一条数据,控制台然后再次获取全部列表查看,此时可以看到,插入的数据已经成功,我们在数组的最后插入了张麻子
//这个是在服务器端打印的内容
com.yangzhenyu.msgservice:YangService D/Server: 插入成功:姓名:张麻子 年龄:38
//这个是在客户端获取列表打印的内容,此时插入成功了
com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:乐乐 年龄:23, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25]
com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:乐乐 年龄:23, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25, 姓名:张麻子 年龄:38]
接下来我们再尝试获取第一个人和删除一个人(这里我直接在代码里写死了,删除第二条数据,也就是乐乐会被删除),也都可以正常完成操作。
//客户端打印的内容
com.yangzhenyu.msgclient D/Client: 第一个人是:姓名:小王 年龄:21
//服务器端打印的删除一个人的内容:
com.yangzhenyu.msgservice:YangService D/Server: 删除成功:姓名:乐乐 年龄:23
//再次获取列表,可以发现,数据确实已经删除了
com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25, 姓名:张麻子 年龄:38]
到这里,我们已经正常的完成了AIDL的全部流程。
最后我们再简单的总结一下:
1、为了方便客户端使用AIDL文件,最好将aidl相关的类放在同一个地方,方便拷贝
2、如果找不到AIDL相关的类,需要在gradle中声明路径
3、安卓5.0之后隐式意图需要指定报名,否则会报错
以上就是如何实现AIDL的全部流程,如果还有疑问,欢迎交流。最后希望大家发现问题及时指正,多多包涵。