AIDL 是Android上提供的一套进程间通讯的机制,在多进程间的通信上他是比较常见的,那为什么要用aidl 呢,我们的四大组件都可以实现跨进程,我只能说使用的场景有所不同罢了。
1.activity 可以跨进程调用另一个进程 的activity 并且也可以携带参数,但是局限性比较大,只能通过intent 在启动时传递。
2.BroadcastReceiver 广播接收器,他可以无限次的发送全局广播,但是它的数据可能不是安全的,并且通信的效率也没有aidl 高,并且维护成本也很高,不能处理耗时操作。
3.ContentProvider 是一种数据共享型组件,用于向其他组件乃至其他应用共享数据。在它内部维持着一份数据集合, 这个数据集合既可以通过数据库来实现,虽然很强大,但是有点重了,实现起来比较麻烦,但是对于大量数据共享是非常有用的。
Service 我们叫它服务,他可以在后台执行一些耗时任务。可以单独启动执行任务,也可以绑定activity交互数据,这种只能在本进程的数据通信。其次就是通过aidl 来实现进程的通信,当然也可以通过信使Messenger 进程间通信。
介绍完了上边四大组件的跨进程通信方式,对跨进程通信的方式有了一个大概了解,下面来着重介绍aidl 的使用。
AIDL 是什么能干什么?
AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写,可以绑定其他进程service 通过aidl 实现进程间数据的通信。
具体的使用步骤:
1.建立两个module ,一个用于服务端app (aidlapplication),一个用于客户端app(clientservice)
2.服务端新建aidl类定义aidl 接口和方法及自定义数据类,配置build.gradle (看情况配置)
3.服务端新建service类(MyService),并且新建内部类(MyBinder)继承aidl 接口类的Stub抽象类
4.服务端 MyService 重写onBind 函数 并返回内部类MyBinder 实例
5.服务端 manifest注册service
6.客户端 ,将服务端定义的aidl 及自定义数据类,复制到服务端,注意包名要完全一致
7.客户端 ,通过包名和action 绑定服务端的service
8.客户端 ,通过绑定成功后返回的service 获取定义的aidl 接口
9.客户端 ,通过接口和服务端通信了。
总共分为9部,这里分的比较细,下面按步骤来学习:
先来假定一个业务逻辑然后我们来实现,客户端可以通过接口直接获取服务端的基本数据类型,可以获取服务端的自定义数据类型,可以修改添加服务端的自定义数据类型。
1.新建两个app这个就不用说,掠过
2.新建aidl 类:
新建aidl 接口类IMyAidlInterface 及自定义数据类Person 类,IMyAidlInterface 新建完成后,会自动新建一个接口方法
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
他只是告诉我们,他支持的基本数据类型,这些基本数据类型不需要import 方式导包,没啥用可以删除了,其他自定义的数据类型以及其他类型数据都要使用import 方式来导包。
创建Person 类并且实现Parcelable 接口
public class Person implements Parcelable {
int age;
int height;
String name;
public Person(int age, int height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person() {
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.age);
dest.writeInt(this.height);
dest.writeString(this.name);
}
protected Person(Parcel in) {
this.age = in.readInt();
this.height = in.readInt();
this.name = in.readString();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
return new Person(source);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public String toString() {
return "Person{" +
"age=" + age +
", height=" + height +
", name='" + name + '\'' +
'}';
}
}
添加IMyAidlInterface 接口的方法,定义我们的业务方法。
package com.example.aidlapplication;
// Declare any non-default types here with import statements
//导入Person 包路径
import com.example.aidlapplication.Person;
import java.util.List;
//自定义数据类型必须parcelable 关键字标注否则会找不到类
parcelable Person;
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
//定义提供的接口 返回值是一个基本数据类型
String getName();
// 返回值是一个自定义的数据类型集合,用于获取本进程内的数据 不在基本数据类型内的,都要导包
List<Person> getPerson();
// 提供远程操作本进程内数据的接口方法
void addPerson(in Person person);
}
注意,此处有坑,先来看这些类的包路径情况
1坑. 我们刚才生成的类都在aidl 文件夹下,这时就会报找不Person 的错误,这时要配置下build.gradle文件,注意是module 的,添加如下
sourceSets {
main {
java.srcDirs = ["src/main/java", "src/main/aidl"]
}
}
但是如果放在java 目录包下就不用配置了,因为默认是去java下找类的。
2坑. 自定义数据类型一定要用 parcelable 关键字标注 如
parcelable Person;
其次必须要正确的导入Person 的包路径,否则也会找不到类错误。
完成上面的操作后我们需要构建下项目,用于通过aidl 生成java 接口文件。
生成的位置位于:
3.服务端新建service类(MyService),并且新建内部类(MyBinder)继承aidl 接口类的Stub抽象类,实现aidl 的所有接口,至于问什么要继承Stub 类,这要去看下构建生成的IMyAidlInterface 类,本篇只讲使用。
public class MyService extends Service {
private static final String TAG = "ClientService";
List<Person> personList = new ArrayList<>();
public MyService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getName() throws RemoteException {
Log.d(TAG, "getName: 远程调用获取基本类型数据");
return "幺妹子";
}
@Override
public List<Person> getPerson() throws RemoteException {
Log.d(TAG, "getPerson: 远程调用获取自定义类型数据");
return personList;
}
@Override
public void addPerson(Person person) throws RemoteException {
Log.d(TAG, "addPerson: " +"收到添加进来的person" + person.toString());
personList.add(person);
}
}
}
4.服务端 MyService 重写onBind 函数 并返回内部类MyBinder 实例
MyBinder 类继承自Stub抽象类,stub实现了IMyAidlInterface 接口并且继承自
binder 类,binder实现了IBinder 接口。这里返回MyBinder 实例,实际返回类型就是IBinder类,当绑定service时返回的就是这个MyBinder 实例。
public IBinder onBind(Intent intent) {
return new MyBinder();
}
5.服务端 manifest注册service
这部非常关键,四大组件都必须在manifest 中注册,并且要简单配置下,以便可以远程调用。
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.example.service.aidl"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
enabled:是否这个service能被系统实例化,如果能则为true,否则为false。默认为true。
exported :是否支持其它应用调用当前组件
process :创建本应用的私有的新进程
Action:Action属性的值为一个字符串,它代表了系统中已经定义了一系列常用的动作。
Category:Category属性用于指定当前动作(Action)被执行的环境
通过Intent Filter 隐式启动service
6.客户端 ,将服务端定义的aidl 及自定义数据类,复制到服务端,注意包名要完全一致.
将服务端 aidl 文件夹复制客户端
注意配置要和服务端一致。
7.客户端 ,通过包名和action 绑定服务端的service
注意包名是服务端的包名,action 启动标示 ,用于匹配intent-filter 启动service
Intent intent = new Intent();
intent.setPackage("com.example.aidlapplication");
intent.setAction("com.example.service.aidl");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
8.客户端 ,通过绑定成功后返回的service 获取定义的aidl 接口
绑定成功后onServiceConnected 方法会返回来两个参数,其中IBinder就是我上边说的onBind 返回的接口类,
通过IMyAidlInterface.Stub.asInterface方法转为我们定义的接口
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
9.客户端 ,通过接口和服务端通信了
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
IMyAidlInterface iMyAidlInterface;
private TextView getAidlTest;
private TextView getAidlCustom;
private TextView getAidlAddCustom;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setPackage("com.example.aidlapplication");
intent.setAction("com.example.service.aidl");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
initView();
}
private void initView() {
getAidlTest = findViewById(R.id.get_aidl_Test);
getAidlAddCustom = findViewById(R.id.get_aidl_add_custom);
getAidlCustom = findViewById(R.id.get_aidl_custom);
getAidlTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exception();
try {
Toast.makeText(MainActivity.this, iMyAidlInterface.getName(), Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
getAidlCustom.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exception();
try {
List<Person> personList = iMyAidlInterface.getPerson();
Iterator<Person> pe = personList.iterator();
for (int i = 0; i < personList.size(); ++i) {
Person person = pe.next();
Log.d(TAG, "onClick: " + person.toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
getAidlAddCustom.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
exception();
try {
iMyAidlInterface.addPerson(new Person(34,56,"这是一条插入的人名" + System.currentTimeMillis() +""));
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private void exception() {
if (iMyAidlInterface == null) {
try {
throw new IllegalAccessException("接口为空");
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return;
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:orientation="vertical">
<TextView
android:id="@+id/get_aidl_Test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取基本数据类型信息"
android:textSize="18sp" />
<TextView
android:id="@+id/get_aidl_custom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="获取自定义数据类型"
android:textSize="18sp" />
<TextView
android:id="@+id/get_aidl_add_custom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="输入自定义数据类型"
android:textSize="18sp" />
</LinearLayout>
</merge>
最后记得构建项目,然后测试的时候先启动服务端,然后启动客户端。