前言

IPC 系列文章:

建议按顺序阅读。

上篇文章分析了Binder作为IPC中的一种在Android里发挥着重要的作用,本篇将从代码的角度分析如何使用Binder进行进程间通信。

通过本篇文章,你将了解到:

1、IPC 基础

2、IBinder/Binder 简介

3、编写跨进程的Service

4、Binder 通信Demo

5、AIDL的引入

1、IPC 基础

网上

math?formula=%5Ccolor%7BRed%7D%7B%E7%BB%9D%E5%A4%A7%E9%83%A8%E5%88%86%7D文章在分析了Binder之后立即抛出AIDL概念,然后一堆代码,初看让人比较迷惑,再看也无法完全理解AIDL存在的意义。这里套用一个比较俗的说法:存在即合理(一个东西存在有它存在的理由)。同样适用于程序设计,为什么要用AIDL?它不会凭空想当然的出现,一定是它解决了某些问题。接下来我们一步步分析,自然过渡到使用AIDL。

同一进程里的访问

在Java的世界里,一切都是对象,拿到对象后就可以访问对象的属性和方法。

对象存在于内存的某个区域,怎样拿到对象呢?答案是:引用。

class Student {

private int age;

private String name;

}

private Student getStudent() {

//student 是引用

//该引用指向了堆里面的Student对象

Student student = new Student();

return student;

}

只要拿到了Student引用,就可以操作Student对象。

不同进程里的访问

同一进程里的访问是我们所熟悉的方式,那么不同进程间的访问呢?

假若Student对象在进程B里构造,而想在进程A里使用它。通过上篇文章可知无法直接引用Student,然而可以借助于Binder。

9927f04a36d0

image.png

由图可知:

1、进程A调用Binder提供的接口

2、Binder调用进程B的接口

3、通过Binder的连接,A就可以调用B

Binder(驱动)对于上层来说是透明的,这么看起来就像是A直接调用了B的接口。

2、IBinder/Binder 简介

既然Binder机制要为上层提供接口,那么就需要将接口/类暴露出来,比较重要的接口/类是:IBinder/Binder。

IBinder.java:接口

public interface IBinder {

...

//code : 要执行动作的标示

//data : 从客户端往服务端传递的序列化后的数据,不能为空

//reply : 从服务端返回的序列化后的数据,可能为空

//附加操作标记:0-->表示阻塞等待该方法调用结束 1-->表示执行该方法后立即返回

public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)

throws RemoteException {

...

}

}

Binder.java 抽象类

Binder实现了IBinder:

public class Binder implements android.os.IBinder {

...

//code : 要执行动作的标示

//data : 从客户端往服务端传递的序列化后的数据,不能为空

//reply : 从服务端返回的序列化后的数据,可能为空

//附加操作标记:0-->表示阻塞等待该方法调用结束 1-->表示执行该方法后立即返回

protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,

int flags) throws RemoteException {

...

}

...

}

可以看出,transact(xx)和onTransact(xx)参数很像,类似于layout(xx)-->onLayout(xx)、draw(xx)-->onDraw(xx) 调用方式,猜测transact(xx)里最终调用了onTransact(xx)。

整理出两者有如下联系:

1、进程B实现了Binder里的onTransact(xx)方法,并挂出IBiner接口,告诉外界可以调用这个接口来获取B提供的服务。

2、进程A获取了IBinder接口,并调用transact(xx),传递消息给B。

3、通过Binder驱动的中转,找到该IBinder是进程B放出来的,于是调用onTransact(xx)。

至此,进程A就可以发送消息给进程B了。

大致流程如下:

9927f04a36d0

image.png

3、编写跨进程的Service

上面问题的关键是:进程A如何获取进程B提供的IBinder接口?

明显的这势必又是一次IPC过程,恰好可以借助四大组件之一的Service来完成。

在AndroidMenifest.xml里声明Service的时候:

默认表示该Service与当前App运行在同一进程里。

若要想Service运行在单独的进程里,可增加配置如下:

或者

其中:

.hello表示Service运行在名为.hello的进程里

:hello表示Service运行在名为当前进程名+hello的进程里,举个例子当前进程名为:com.example.myapplication,那么Service运行的进程名为:com.example.myapplication:hello

配置好如上信息之后,开启(显示开启/绑定开启)Service之后,Service就运行在单独的进程里了:

9927f04a36d0

image.png

4、Binder 通信Demo

基本的东西准备好了,接着来看看具体的业务。

编写Server端业务

1、声明接口

既然进程B要提供给外界服务,那么最好声明一个接口:

public interface IMyServer{

//提供给外界调用

public void say(String word);

}

2、定义Binder子类

为了获取Binder驱动发过来的消息,需要声明一个Binder类。

public class MyServerBinder extends Binder {

//匿名内部类实现接口

IMyServer iMyServer = new IMyServer() {

@Override

public void say(String word) {

//为方便起见,仅仅打印客户端传递过来的消息

Log.d("IPC", word + " in process:" + SystemUitl.getAppName(null));

}

};

@Override

protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {

//从序列化后的内容里读取对应的字段值

String word = data.readString();

//收到Client发过来的消息

iMyServer.say(word);

//回复消息给Client

String replyWord = "I'm fine, and you?";

Log.d("IPC", replyWord + " in process:" + SystemUitl.getAppName(null));

reply.writeString(replyWord);

return true;

}

}

继承自Binder,并重写onTransact(xx),该方法里获取了来自客户端(进程A)的消息,将消息提取出来并交给业务接口IMyServer调用。

同时将消息回传给客户端。

SystemUitl.getAppName(null)工具为获取当前运行的进程名。

3、编写Service

Binder准备好之后,需要将Binder传递给客户端,传递的方法是通过Service传递。

public class MyService extends Service {

private MyServerBinder myServerBinder = new MyServerBinder();

@Nullable

@Override

public IBinder onBind(Intent intent) {

//返回IBinder引用给调用者

return myServerBinder;

}

}

在Service里构造了MyServerBinder对象,并在onBind(xx)里将引用传递出去。

当客户端绑定这个Service的时候就能拿到该IBinder引用(注意:此处客户端拿到的引用与服务端不是同一个引用,后续会分析此流程)。

编写Client端业务

此时,Server端已经编写完毕,接着来看客户端如何获取IBinder引用。

1、实现ServiceConnection接口

首先实现一个ServiceConnection接口,用以当绑定关系确立后回调该类里的方法:

ServiceConnection serviceConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

//service 为 Server传递过来的IBinder引用

//构造序列化对象

Parcel data = Parcel.obtain();

Parcel reply = Parcel.obtain();

//写入String

String word = "hello server, how are you ?";

Log.d("IPC", word + " in process:" + SystemUitl.getAppName(null));

data.writeString(word);

try {

//传递消息给Server

service.transact(2, data, reply, 0);

//收到Server的回复消息

String responseWord = reply.readString();

Log.d("IPC", responseWord + " in process:" + SystemUitl.getAppName(null));

} catch (Exception e) {

}

}

@Override

public void onServiceDisconnected(ComponentName name) {

}

};

在onServiceConnected(xx)拿到IBinder引用:service。

先构造待发送的数据,然后调用IBinder transact(xx)发送数据给服务端。

2、绑定服务端的Service

客户端通过绑定服务端的Service来建立绑定关系。

private void bindService() {

Intent intent = new Intent(MainActivity.this, MyService.class);

bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

}

用图来梳理以上流程:

9927f04a36d0

image.png

最后来看看通信结果:

9927f04a36d0

image.png

从日志结果看:Client与Server分别打印了两条日志,说明通信成功了。

5、AIDL的引入

以上是借助Binder来进行两个进程间简单通信,功能是实现了,但是你可能发现了一些端倪:

1、编写冗余

在onTransact(xx)里只调用了say(xx)一个方法,如果需要调用另一个方法,那么就要对code进行区分:

@Override

protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {

switch (code) {

case 1:

iMyServer.say(data.readString());

break;

case 2:

iMyServer.say1(data.readInt());

break;

case 3:

iMyServer.say2(data.readFloat());

break;

}

return true;

}

同样的,在客户端发送消息的时候也需要区分code。Server端提供的业务只有几个接口还好,若是十几个接口甚至更多,书写case不仅是个体力活,还容易出错。

2、序列化数据

在客户端发送数据前,先将数据序列化,在服务端接收数据处理前,先将数据反序列化。这个过程也是个重复的体力活,实际上双方都不关心具体的序列化细节,只知道丢个参数进去,弄个参数出来即可。

3、面向对象

客户端先要调用transact(xx),服务端在onTransact(xx)里调用say(xx)方法。

这么看起来有点绕,实际上比较好的方式是客户端"直接"调用服务端的say(xx)方法:

9927f04a36d0

image.png

如上,Client看起来直接调用了Server的接口,就像是Client拿到了Server对象引用然后操作之,和在同一进程里的操作一样,符合面向对象操作的习惯,大大降低了违和感。

说了这么多直接使用Binder编码的缺点,那么是否有一种方法能解决上面的问题呢?

9927f04a36d0

image.png

没错就是AIDL。

9927f04a36d0

1.gif

接下来的文章将着重分析AIDL原理及其使用。

本文基于Android 10.0。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营学习Android

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐