Android跨进程通信的方式

Android跨进程通信主要有以下方式:

  1. Intent中附加extras来传递消息
  2. 共享文件
  3. Binder方式
  4. 四大组件之一的ContentProvider
  5. Socket

使用Bundle

四大组件中的三大组件(Activity、Service、Receiver)都支持在Intent中传递Bundle数据。Bundle实现了Parcelable接口,当我们在一个进程中启动了另一个进程的Activity、Service、Receiver,可以再Bundle中附加我们需要传输给远程进程的消息并通过Intent发送出去。被传输的数据必须能够被序列化,比如基本类型,实现了Parcelable接口类型的数据,实现了Serarializable接口类型的数据以及一些Android支持的对象。

Bundle的特殊使用场景:A进程进行一个计算,结果要传入B进程,但是结果不支持Bundle.可以通过Intent在B进程启动一个后台服务,由Service完成计算后再启动B进程中的组件。

使用文件共享

  1. 两个进程通过读写同一个文件来交换数据。还可以通过ObjectOutputStream/ObjectInputStream序列化一个对象到文件中,然后在另一个进程从文件中反序列这个对象。注意:反序列化得到的对象只是内容上和序列化之前的对象一样,本质是两个对象。
  2. 文件并发读写会导致读出的对象可能不是最新的,并发写的话那就更严重了(书本原文,个人理解这里的严重应该是指数据丢失。)。所以文件共享方式适合对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读写问题
  3. SharedPreferences底层实现采用XML文件来存储键值对。系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写变得不可靠,面对高并发读/写时SharedPreferences有很大几率丢失数据,因此不建议在IPC中使用SharedPreferences

使用Messenger

Messenger可以在不同进程间传递Message对象,是一种轻量级的IPC方案,底层实现是AIDL。Messenger中有一个Hanlder以串行的方式处理队列中的消息。因此不存在并发访问的问题,所以我们不用考虑线程同步的问题。

具体使用时,分为服务端和客户端:

  1. 服务端:创建一个Service来处理客户端请求,同时创建一个Handler并通过它来创建一个Messenger,然后再Service的onBind中返回Messenger对象底层的Binder即可。
  2. 客户端:绑定服务端的Sevice,利用服务端返回的IBinder对象来创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,消息类型是Message。如果需要服务端响应,则需要创建一个Handler并通过它来创建一个Messenger(和服务端一样),并通过Message的replyTo参数传递给服务端。服务端通过Message的replyTo参数就可以回应客户端了
  3. 总而言之,就是客户端和服务端 拿到对方的Messenger来发送Message。只不过客户端通过bindService而服务端通过message.replyTo来获得对方的Messenger。

注意:
通过Messenger来发送Message进行跨进程传输,Messge中能使用的载体只有what,arg1,arg2,replyTo以及Bundle,不支持object字段。2.2以后,也只有系统提供的实现了Parcelable接口的对象才能通过它来进行跨进程传输。

示例代码:服务端-客户端

使用AIDL

如果有大量的并发请求,使用Messenger就不太适合,同时如果需要跨进程调用服务端的方法,Messenger就无法做到了。这时我们可以使用AIDL。

流程如下:

  1. 服务端需要创建Service来监听客户端请求,然后创建一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
  2. 客户端首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

注意事项:

  1. AIDL支持的数据类型:
    Ⅰ· 基本数据类型、String、CharSequence
    Ⅱ· List:只支持ArrayList,里面的每个元素必须被AIDL支持
    Ⅲ· Map:只支持HashMap,里面的每个元素必须被AIDL支持
    Ⅳ· Parcelable
    Ⅴ· 所有的AIDL接口本身也可以在AIDL文件中使用

  2. 自定义的Parcelable对象和AIDL对象,不管它们与当前的AIDL文件是否位于同一个包,都必须显式import进来。

  3. 如果AIDL文件中使用了自定义的Parcelable对象,就必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
    package com.ryg.chapter_2.aidl; parcelable Book;

  4. AIDL接口中的参数除了基本类型以外都必须表明方向in/out。AIDL接口文件中只支持方法,不支持声明静态常量。建议把所有和AIDL相关的类和文件放在同一个包中,方便管理。
    void addBook(in Book book);

  5. AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接时,管理数据的集合直接采用CopyOnWriteArrayList来进行自动线程同步。类似的还有ConcurrentHashMap

  6. 因为客户端的listener和服务端的listener不是同一个对象,所以RecmoteCallbackList是系统专门提供用于删除跨进程listener的接口,支持管理任意的AIDL接口,因为所有AIDL接口都继承自IInterface接口。
    public class RemoteCallbackList<E extends IInterface>
    它内部通过一个Map接口来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型。当客户端解除注册时,遍历服务端所有listener,找到和客户端listener具有相同Binder对象的服务端listenr并把它删掉。除此之外,还具有以下的优点,当客户端的进程终止后,会自动移除掉客户端注册的listener,而且内部实现了线程同步的功能,不需要我们进行额外的同步的操作。

  7. 客户端RPC的时候线程会被挂起,由于被调用的方法运行在服务端的Binder线程池中,可能很耗时,不能在主线程中去调用服务端的方法。

  8. 当远程服务端需要调用客户端的listener方法的时候,被调用的方法也运行Binder线程池中,只不过是客户端的Binder线程池中。所以同样不可以在服务端调用客户端的耗时的方法。此外,listener中的方法运行在Binder的线程中的,所以我们不能在其中执行更新UI的操作,如果要更新UI的话,需要通过Handler切换到主线程执行。

  9. Binder可能意外的死亡,这个时候需要重新连接,这时候有两种方法,一种方法是在onServiceDisConnected中重新连接,一种是设置DeathRecipent,在其binderDied方法中重新连接。这两种方法的区别就是前者运行在主线程中,而后者运行在客户端的Binder线程池中。

  10. 在AIDL中进行权限认证,只有具有权限的进程才可以远程调用我们的服务。有两种方法来进行权限认证,第一种,在oBind中进行认证,如果认证失败,直接返回null;第二种,在onTransact方法中进行权限认证,如果认证失败返回false。

  11. 在eclipse里面操作时aidl文件和相关的java文件都放在一个包下,客户端使用的时候直接将该包复制到自己的目录下,然后可以另外建另外一个包放其他代码。但在android studio下面这样是不可以的,需要在src单独建一个AIDL文件夹,将aidl文件放在里面,相关的java文件在另外的包下,使用的时候将该AIDL文件夹复制客户端项目中,同时新建一个和服务端相同的包名来存放相关的java类。
    server端如下所示:
    server
    client端如下所示:
    client

Binder连接池(对AIDL的扩展)

AIDL是一种最常用的IPC方式,是日常开发中涉及IPC时的首选。前面提到AIDL的流程是 客户端在Service的onBind方法中拿到继承AIDL的Stub对象,然后客户端就可以通过这个Stub对象进行RPC。
那么如果项目庞大,有多个业务模块都需要使用AIDL进行IPC,随着AIDL数量的增加,我们不能无限制地增加Service,我们需要把所有AIDL放在同一个Service中去管理。
流程是:

  1. 服务端只有一个Service,我们应该把所有AIDL放在一个Service中去管理,不同业务模块之间是不能有耦合的
  2. 服务端提供一个queryBinder接口,这个借口能够根据业务模块的特征来返回响应的Binder对象给客户端
  3. 不同的业务模块拿到所需的Binder对象就可以进行RPC了

示例代码:Server端&&Client端

ContentProvider

ContentProvider

Socket

总结自:《Android开发艺术探索》

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器