Android 中的 Binder 机制

序列化

在说跨进程通信之前,得先说一下序列化。

序列化又称为对象的持久化,因为我们知道,对象都是存在于内存中的。但是当我们说跨进程通信的时候,谈论的是两个不同的应用,不同的进程,有着不同的内存空间。所以有时候我们需要将某个对象持久化到存储设备上,然后在另一个进程中取出该对象进行使用,从而达到跨进程通信的目的。

现有的序列化方法一般是实现 Serializable 和 Parcelable 接口。

Serializable 是 Java 提供的接口,空接口,没有方法。实体类只需要实现该接口,就可以完成序列化。但是要注意,最好加上一个 serialVersionUID,否则反序列化时校验 serialVersionUID,如果不一致就会失败;

Parcelable 接口是 Android 提供的,比 Serializable 更高效,但实现起来更复杂一些。

一般来讲,内存层面的序列化都建议使用 Parcelable,如果是将对象序列化到存储设备或通过网络传输时,因为 Parcelable 实现起来太复杂,这两种情况下就可以考虑使用

Binder

说完序列化,接下来我们再说 Binder,Binder 是 Android 平台下的多进程通信的机制。

尽管 Android 基于 Linux 内核,但是并没有采用 Linux 自带的进程间通信的方式,而是重新设计了自己的 Binder 机制。

从进程间通信的角度来看,Binder 可以理解成一个虚拟的物理设备;

从 Android Framework 的角度看,Binder 是应用层 android.os.ServiceManager 连接 framework 各种 Manager 、ManagerService 的桥梁;

从 Android 应用层的角度来看,Binder 又是客户端和服务端的通信媒介,bindService 的时候,服务端给客户端返回一个 Binder 代理,通过该对象,客户端就可以与服务端进行数据通信和交互。

在 Android 开发中,一般在 Serivice 中才会应用到 Binder。

普通的 Service 或者说同一应用内,甚至同一应用不同进程之间的 Service 中的 Binder 机制都涉及不到进程间通信。

只有在真正意义上的,不同应用之间通信,才能涉及到 Binder 机制。

binder机制.png

IPC 的几种方式

Bundle

一般就是四大组件之间进行通信,bundle 实现了 parcelable 接口,天然支持跨进程传输,是一种比较简单的跨进程通信方式。

文件共享

比如 sp 或者 xml 之类。一个进程写,一个进程读,从而实现跨进程通信。但是如果有高并发的问题,比较难处理。

ContentProvider

系统级别提供的,专门用于不同应用间共享数据的方式。通过数据库实现;

Socket

使用 Socket 套接字通过网络来传输字节流,从而实现进程间通信。但实现细节繁琐,也不实用;

Messenger

使用轻量级 IPC 方案 Messenger,Messenger 底层是封装的 AIDL。顾名思义,可以使用它跨进程传递 Message 对象,所以很明显,一般也需要配合 Handler 使用。如果要实现双向通信,重点在于客户端传递到服务端的 Message 对象的 replyTo 属性;

Messenger 的作用主要还是为了传递消息,如果需要调用服务端的方法或者有大量的并发请求,那就还是得手动实现 AIDL。

messenger流程示意.png
messenger示例.png

AIDL

手动实现 AIDL,这种稍微复杂一些。

AIDL 的重点在于实现 AIDL 文件,AIDL 的方法也是声明为接口形式,但是它又不是接口。

在 AIDL 文件中,只能声明方法,不能声明静态常量,而且也不是所有的数据类型都能得到支持,只能支持基本数据类型,String,char,List,Map,parcelable 对象。

如果方法参数不是基本数据类型,还要显示声明 in、out、inout 来标记输入还是输出;

如果方法参数是 parcelable 对象,那还得单独为该对象声明一个同名的 AIDL 文件,声明它是 parcelable;

服务端、客户端要实现完全相同的 AIDL 文件,包名都得完全相同;

AIDL 通信过程

下面单独说一下使用 AIDL 进行进程间通信的整个过程:

首先在 C/S 两端声明相同的 aidl 文件,然后让系统自动生成对应的 java 代码,这个类中会生成 Binder 实现 Stub (服务端使用,用来初始化 Binder 对象)和代理类(客户端使用,用来连接服务端)。

aidl文件构成.png

然后在服务端实现相应的 service,并实例化 binder 对象,通过 onBind 方法返回给客户端;在客户端对远端服务进行绑定,跨进程通信一般需要使用隐式 Intent 调用,然后在 onServiceConnected 回调中,通过 AIDL java 类是 asInterface 方法获取 binder 对象实例,然后客户端与服务端就建立了连接。

如果客户端对服务端发起调用,会通过 Proxy 代理类的方法,调用到服务端 binder 的 transact 方法,入参了 Parcel 对象 reply,然后客户端线程挂起,transact 内部会回调到 onTransact 方法中,然后就会执行服务端的对应实现方法,然后会在 onTransact 方法中对入参的 reply 对象进行写入,完成之后 onTransact 方法返回 true,服务端就会唤醒客户端,继续执行,客户端再将返回结果返回给调用者,完成整个流程的调用。

aidl流程示意.png

AIDL 注意点

需要注意的是,服务端的方法是运行在 Binder 线程池中的,一般需要处理高并发的情况,比如用 CopyOnWriteArrayList 代替 ArrayList,用 ConcurrentHashMap 代替 HashMap,用 AtomaticBoolean 代替普通的 Boolean。在这种情况下,服务端方法本身就可以执行大量耗时操作,所以不需要在服务端方法中再开线程去进行异步任务。

也是因为这一点,如果在 UI 线程访问服务端方法时,由于方法有可能是耗时任务,所以最好不要同时更新 UI,否则容易造成 ANR。如果在客户端有服务端方法的接口回调,也注意不可以直接更新 UI,因为本质上回调方法是执行在 Binder 线程的,非 Looper 线程更新 UI 也会抛出异常。

除此之外,Binder 是有可能意外挂掉的。所以最好事先 binder 的死亡代理,当 binder 死亡,我们可以选择重新建立远程服务连接。

同时,还应该给 AIDL 连接加上权限限制。具体可以在清单文件中声明需要的权限,并在 onBind 或者 onTransact 方法中进行权限验证,从而保证连接安全。

你可能感兴趣的:(Android 中的 Binder 机制)