Android多进程通信、Binder、IPC机制这一篇就够了!!!

Android IPC简介

IPC含义:进程间通信,是指两个进程间尽享数据交换。
多进程使用场景:

  • 应用根据业务需求需要多进程来实现
  • 为了加大一个应用可使用的内存所以需要多进程来获取多份内存空间。

Android中的多进程模式

一般情况下指一个应用存在多个进程。在Android中使用多进程只有一种办法,就是给四大组件(Activity、Service、Receiver、ContentProvider)在清单文件中指定android:process属性,除此之外没有别的办法。其实还有一种特殊的,就是通过JNI在native曾去fork一个新的进程。

示例:
在这里插入图片描述
进名称以 “ :” 开头的进程属于当前应用的私有进程,其它应用组件不可以和它跑在同一个进程里。 不以“:”开头的是全局进程,其他应用可以通过ShareUID方式可以和它泡在同一个进程中。

Android系统会为每个应用分配一个唯一的ShareUID,具有相同UID的应用才能共享数据。 两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名才可以。这样它们可以互相访问私有数据了。


多进程模式的运行机制

我们在SecondActivity中创建了一个类,然后在这个类中有一个public的静态成员变量。
在这里插入图片描述

然后我们在MainActivity中修改sUserID的值为2,然后我们打印日志看一下:
在这里插入图片描述

上述问题出现问题分析:我们知道Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上又不同的地址空间,这就导致在不同的虚拟机中访问一个类对象会产生多份副本。所以我们修改了进程SecondActivity的值后, 而进程MainActivity的值没有变化。这是核心原因。

所以我们总结一下多进程总结问题:

  • 1、所有运行在不同进程的四大组件,只要通过内存来共享数据的都会共享失败
  • 2、静态成员和单例模式完全失效
  • 3、线程同步机制完全失效
  • 4、SP的可靠性下降
  • 5、Application 会多次创建

第1、2、3个问题:都因为不是一块内存了,所以会有问题的,至于线程同步也是如此,锁住的对象也不是同一个内存地址的对象所以会有问题。
第4个问题:SP不支持两个进程同时去读写,否则一定几率会丢失数据,因为SP地城是通过读写XML来实现的,并发写显然是可能实现问题的。
第5个问题:当一个组建跑在一个新的进程中,系统会同时分配独立的虚拟机,这个过程其实就是启动一个应用,启动应用肯定就重新启动Application,所以会有多个Application。

本节介绍了多进程带来的问题,然后我们在去理解IPC就简单多了。


序列化的两种方式

之所以这里介绍序列化是因为: 对象是不能跨进程传输的, 对象的跨进程传输本质上都是反序列化的过程

Serializable接口

1、这是Java提供的一个序列化接口,它是一个空接口,只需要将对象实现这个接口即可。非常简单。
2、采用ObjectOutputStream和ObjectInputStream即可轻松实现。
在这里插入图片描述

上面演示了采用Serializable方式序列化对象的典型过程,很简单,只需要把实现了Serializable接口的User对象写到文件中就可以了恢复了,但是注意恢复后的对象newUser和user的内容完全一样,但是两者不是同一个对象。

3、然后我们解释一下serialVersionUID,
在这里插入图片描述
序列化的时候系统会把当前的serialVersionUID写入序列化的文件中(也可能是中介),当反序列的时候系统回去检测文件中的serialVersionUID,看它是否和当前类的版本相同,这个时候可以成功反序列化;否则说明当前类和序列化相比发生了某些变化,比如成员变量的数量发生了,都无法反序列化成功。

Parcelable接口

这是android提供的序列化接口,一个类对象实现了序列化就可以通过intent binder传递了。
在这里插入图片描述
在这里插入图片描述

对比:Serializable使用简单但是开销很大, Parcelable是Android中的序列化方式,更推荐使用。


Binder的工作机制

在这里插入图片描述


Android中的IPC方式

  • Intent用Bundle船只
  • 使用文件共享
  • 使用Messenger
  • 使用AIDL
  • 使用ContentProvider
  • 使用Socket

1、使用Bundle

四大组件的三大组件(Activity、Service、Receiver)都支持在Intent中传递Bundle数据。由于Bundle实现了Parcelable接口,所以它可以在不同的进程间传输。基于这点我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以通过Intent在Bundle中传递过去即可,这种最简单


2、使用文件共享

这里需要注意一下SP,它比较特殊,它底层是通过XML文件来存储键值对。它也是文件的一种,但是由于系统对它的读写有一定的缓存策略,即内存中会有一分SO的文件缓存,因为多进程模式下,系统对它的读写就变得不可靠。

我们从MainActivity中序列化User对象到SD卡上,然后在SecondActivity的onResume中去反序列化,我们期望在SecondAcvity中能够正确获取User对象的值。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们打印日志看一下,但是注意一下,只能说是内容相同,已经不是同一个对象了,是两个对象。
在这里插入图片描述


3、使用Messenger

它是轻量级的IPC方案,它的底层实现是AIDL。先看一下构造方法:
在这里插入图片描述

使用方法如下:
1)服务端进程
首先,我们需要在服务端创建一个Servicer来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的binder即可。

2)客户端进程
客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。

首先看服务端代码:

在这里插入图片描述
在这里插入图片描述
看一下客户端代码:
在这里插入图片描述
在这里插入图片描述
然后看一下运行结果:
在这里插入图片描述
上面是一个简单的例子,如果我们在修改一下,就是每当客户端发来一条消息,服务端就自动回复一条“嗯,你的消息我已经收到,稍后回复你”,代码修改如下:
首先看服务端修改,只修改MessengerHandler:

在这里插入图片描述
在这里插入图片描述
接下来客户端修改:
客户端也需要准备一个接收消息的Messenger和Handler:
在这里插入图片描述
很关键的一点,需要把接收服务端消息的Messenger通过Message的replyTo传递给服务端:

在这里插入图片描述
打印如下:
在这里插入图片描述
在这里插入图片描述


4、使用AIDL

通过上面对Messenger介绍之后,发现它是串行的,如果信息量太大,会排队,所以适合轻量级交互。
我介绍一下AIDL的使用

1、服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将将暴露给客户端的接口声明在AIDL文件中,最后在Service中实现这个接口即可。

2、客户端
客户端所要做事情就稍微简单一些,首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转换成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

3、AIDL接口的创建
在这里插入图片描述

AIDL支持的数据类型:
在这里插入图片描述

远程服务端Service的实现:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

客户端实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
紧着着我们在调用李刚一个接口addBook,修改onServiceConnected
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过上面例子大家应该对AIDL有了初步的了解,下面我们在优化一下方案需求,每个用户可以对服务端设置提醒和取消提醒功能,服务端有了新书以后会主动通知设置提醒的用户。并把新书当作参数传递给用户:

在这里插入图片描述
在这里插入图片描述
还需要在AIDL接口中,添加两个方法,注册和解除注册
在这里插入图片描述

然后服务端代码也要改一下,然后还有添加一个线程,5s加一本书,通知用户
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

看一下客户端修改代码:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

看日志打印:确实没5s更新一次:
在这里插入图片描述
ok,我们在onDestroy的时候已经解除绑定,我们在返回的时候会有一个错误:
在这里插入图片描述
但是我们代码已经明明传过去listener过去了,最终服务端没有解注册成功。为什么呢? 这就是我们之前给大家提到的,跨进程通信是序列化和反序列的的形式,只不过是内容相同而已。但是还是两个不同的对象,所以解注册失败。那怎么解决呢?答案是使用RemoteCallbackList,它是专门提供用于删除跨进程接口的。它实现原理很简单,在它的内部有一个Mao结构专门用来保存所有的AIDL的回调,这么Map的key是IBinder类型,value是Callback类型。
在这里插入图片描述
看上面应该明白了,他们生成的是同一个IBinder对象,利用这一点,我们遍历服务端的listener,找出哪个和解注册listener具有相同的Binder对象服务端删除即可。

BookManagerSercice修改:把CopyOnWriteArrayList替换成RemoteCallbackList
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
欧克,修改完毕,我们打印日志看一下效果

在这里插入图片描述
使用注意,我们无法像操作List一样去操作它,尽管它的名字也带个List,但是它并不是一个LIst。遍历RemoteCallbackList必须按照下面的方式进行,其中beginBroadcast和finishBroadcast必须要配对使用。
在这里插入图片描述

5、使用ContentProvider

6、使用Socket

由于时间问题,5 和6 我就不粘贴案例了,以后时间充足我在补上。


Binder连接池

最后给大家介绍一下Binder连接池, 它是解决业务模块如果需要多个AIDL的时候,不需要创建多个服务,通过连接池可以取出不同Binder,不同的Binder我们自己定义Code来区分,获取即可。
在这里插入图片描述

我们提供两个AIDL接口(ISecurityCenter和ICompute)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
两个接口的实现:
在这里插入图片描述
创建Binder连接池AIDL接口
在这里插入图片描述
在这里插入图片描述
创建远程Service并实现IBinderPool,线面是实现:
在这里插入图片描述
远程服务的实现:
在这里插入图片描述
在这里插入图片描述
下面是Binder连接池的具体实现,在它们内部首先它要去绑定远程服务,成功后查询返回不同的Binder。也就是对象的AIDL的实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Activity代码如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

发布了51 篇原创文章 · 获赞 78 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_39079048/article/details/89178521
今日推荐