文章目录
  1. 1. Android IPC简介
  2. 2. Android中的多进程模式
    1. 2.1. 开启多进程模式
    2. 2.2. 多进程模式的运行机制
  3. 3. IPC基础概念介绍
    1. 3.1. Serializable接口
    2. 3.2. Parcelable接口
    3. 3.3. Binder
  4. 4. Android中的IPC方式
    1. 4.1. 使用Bundle
    2. 4.2. 使用文件共享
    3. 4.3. 使用Messenger
    4. 4.4. 使用AIDL
    5. 4.5. 使用ContentProvider
    6. 4.6. 使用Socket
  5. 5. Binder连接池
  6. 6. 选用合适的IPC方式
  7. 7. 分享与支持

来源:JiongBull’s Blog
微博:@JiongBull
GitHub:JiongBull

Android IPC简介

在Android平台上可以通过Binder实现进程间通信,通过Socket实现任意两个终端之间的通信。

Android中的多进程模式

开启多进程模式

  • 在Android中使用多进程的唯一方法,就是给四大组件在AndroidManifest中指定android:process属性。
  • adb shell ps | grep com.jiongbull.art.note可以查看包名对应的进程信息。
  • 进程名以:开头的进程属于当前应用的私有进程,其他应用的进程不可以和它跑在同一个进程中,而不以:开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
  • Android会为每个应用分配一个唯一的UID,具有相同shareUID且签名一致的应用运行在同一个进程中,它们可以共享数据。

多进程模式的运行机制

  • Android为每个进程都分配了独立的虚拟机,不同的虚拟机在内存上有不同的地址空间,这样在不同的虚拟机中访问同一个类的对象会产生多份副本。
  • 所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败。
  • 使用多进程可能导致的问题:
    1. 静态成员和单例模式失效。
    2. 线程同步机制完全失效,不同进程锁的不是同一个对象。
    3. SharedPreferences的可靠性下降,SharedPreferences不支持并发读写操作。
    4. Application会多次创建。

IPC基础概念介绍

Serializable接口

  • Serializable是Java提供的一个序列化接口,为对象提供标准的序列化和反序列化操作。
  • 实现Serializable接口并声明一个serialVersionUID即可实现对象的序列化。
  • serialVersionUID是用来辅助序列化和反序列化的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。
  • serialVersionUID的详细工作机制:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会检测文件中的serialVersionUID,看它是否和当前类中的一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功的反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化。
  • 我们应该手动指定serialVersionUID的值,例如1L,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常的反序列化。
  • 使用Serializable接口需要注意:
    1. 静态成员变量属于类不属于对象,不会参与序列化过程。
    2. 使用transient限定符标记的成员变量不参与序列化过程。

Parcelable接口

  • Parcel内部包装了可序列化的数据,需要实现序列化、反序列化和内容描述,序列化功能由writeToParcel方法来完成,最终通过Parcel中的一系列write方法来完成,反序列化功能由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel中的一系列read方法来完成反序列化,内容描述功能通过describeContents方法完成,一般情况下返回0,仅当当前对象存在文件描述符时才返回1。
  • Serializable是Java中的序列化接口,使用简单但开销大,序列化和反序列化过程需要大量的IO操作。而Parcel是Android中的序列化方式,适用Android平台,效率高。

Binder

  • 从IPC角度来说,Binder是Android中的一种跨进程通信方式,可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁,从Android应用层来说,Binder是客户端和服务器进行通信的媒介,当bindService的时候,客户端就可以获取服务端提供的服务或者数据。
  • 系统会把我们书写的.aidl接口文件解析成对应的java接口文件,主要包含这几个部分:
    1. 接口方法。
    2. 内部类Stub和它的代理类Proxy,Stub继承了Binder类,当客户端和服务端位于同一个进程中时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Proxy来完成,Stub内部还声明了一些整型ID,在transact过程中用来标识客户端请求的是哪个接口方法。
  • 生成的接口的核心实现是内部类Stub和Stub的内部代理类Proxy,下面介绍一些核心方法的含义:
    1. DESCRIPTOR,Binder的唯一标识,一般用当前Binder的全限定类名表示。
    2. asInterface(...),将服务端的Binder对象转换成客户端AIDL接口对象,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。
    3. asBinder(),返回当前的Binder对象。
    4. onTransact(...),运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过方法参数code来确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行目标方法,方法执行完毕后向reply中写入返回值。如果返回true,表示请求执行成功,如果返回false表示请求失败,可以根据这个特性做权限校验。
    5. Proxy#method,运行在客户端中,当客户端调用此方法时,首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象,然后把该方法的参数信息写入到_data中,接着调用transact()方法来完成RPC请求,同时当前线程挂起,然后服务端的onTransact(...)方法会被调用,直到PRC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。
    6. 客户端发起请求时,当前线程会被挂起直至服务端进程返回数据,所以不适合在UI线程中发起远程请求。
    7. 服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式实现,保证线程安全。
  • Binder有两个重要的方法linkToDeath(...)unlinkToDeath(...),当服务端程序异常终止时,客户端到服务端的连接就会断裂(Binder死亡),导致远程调用失败,更为关键的是,如果我们不知道Binder连接已经断裂,客户端的功能会受到影响。通过linkToDeath(...)可以给Binder注册死亡代理,当Binder死亡时,客户端就会收到通知,这时候可以重新发起连接请求从而恢复连接。

Android中的IPC方式

使用Bundle

Activity、Service和Receiver都支持在Intent中传递Bundle数据,由于Bundle实现了Parcelable接口,所以它可以方便的在不同的进程中进行传输。

使用文件共享

文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。

使用Messenger

Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。Messenger一次处理一个请求,因此不用考虑线程同步的问题。

使用AIDL

  • AIDL可以并行的处理客户端的请求,使用请参考AIDL
  • AIDL支持基本数据类型、String和CharSequence、ArrayList和HashMap(每个元素都必须被AIDL支持)、Parcelable、AIDL对象。
  • 自定义的AIDL对象和Parcelable必须显示import,不管它们是否和AIDL文件位于同一个包内。
  • 除了基本数据类型,其他类型的参数必须标上方向:in、out或inout。
  • AIDL只支持方法,不支持静态常量。
  • 服务端可以使用CopyOnWriteArrayList和ConcurrentHashMap来处理并发请求,客户端接收到的仍是ArrayList和HashMap。
  • 客户端和服务器跨进程监听,服务端需要使用RemoteCallbackList,否则无法解除注册。
  • 客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端的线程会被挂起,这时候如果服务端的方法执行比较耗时,就会导致客户端线程长时间的阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,所以要避免在客户端的UI线程中去访问远程方法。由于客户端的onServiceConnected()onServiceDisconnected()方法都运行在UI线程中,所以也不能在它们里面直接调用服务端的耗时方法。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身可以执行大量耗时操作,不需要在服务端方法中开线程去执行异步任务。
  • 当远程服务端调用客户端的listener中的方法时,被调用的方法运行在客户端的Binder线程池中,所以,不可以在服务端中UI线程里调用客户端的耗时方法。

使用ContentProvider

  • 继承ContentProvider后需要实现onCreate()getType()insert()update()query()delete()6个方法,除了onCreate()由系统回调并运行在主线程中,其他5个方法均由外部回调并运行在Binder线程池中。
  • 要观察一个ContentProvider中数据变化,可以通过ContentResolver的registerContentObserver()方法注册观察者,通过unregisterContentObserver()卸载观察者,并在ContentProvider中会引起数据变化的insert()update()delete()中调用ContentResolver的notifyChange()通知观察者。
  • 事例代码

使用Socket

  • 使用Socket通信需要声明以下权限:
    1. android.permission.INTERNET
    2. android.permission.ACCESS_NETWORK_STATE
  • Socket支持跨进程通信,也支持跨设备通信

Binder连接池

如果有许多业务模块都需要使用AIDL,这时候使用大量的Service就不明智了,会占用大量系统资源,最好只使用给一个Service来管理所有的AIDL,所以推荐使用Binder连接池,工作机制是这样的,每个业务模块创建各自的AIDL接口并实现Binder,向服务端注册Binder对象,服务端向客户端提供queryBinder()接口,客户端通过业务编码向客户端获取对应的Binder就能远程方法调用了。代码参考

选用合适的IPC方式

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件间的进程通信
文件共享 简单易用 不适合高并发场景,并且无法做到进程间的即时通信 无并发访问情形,交换简单的数据实时性不高的场景
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用稍复杂,需要处理好线程同步 一对多通信且有RPC需求
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求
ContentProvider 在数据源访问方便功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 一对多的进程间数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微繁琐,不支持直接的RPC 网络数据交换

分享与支持

  • 如果这篇文章对你有帮助,请分享下面的链接让更多人受益。
  • 更多支持,请点这里
文章目录
  1. 1. Android IPC简介
  2. 2. Android中的多进程模式
    1. 2.1. 开启多进程模式
    2. 2.2. 多进程模式的运行机制
  3. 3. IPC基础概念介绍
    1. 3.1. Serializable接口
    2. 3.2. Parcelable接口
    3. 3.3. Binder
  4. 4. Android中的IPC方式
    1. 4.1. 使用Bundle
    2. 4.2. 使用文件共享
    3. 4.3. 使用Messenger
    4. 4.4. 使用AIDL
    5. 4.5. 使用ContentProvider
    6. 4.6. 使用Socket
  5. 5. Binder连接池
  6. 6. 选用合适的IPC方式
  7. 7. 分享与支持