学习笔记:Android中的IPC机制

返回本站首页

“Android中的IPC机制” 知识点学习梳理,包括了Android中常见的几种IPC的总结和对比。

用XMind画的思维导图,导出为HTML格式了,点开页面可以查看清晰的图片,以及各部分的导出文字。
因为图片太大,如果渲染为与本站一致的主题会导致图片看不清,所以本页面未进行样式渲染,比较丑,凑合着看吧。😊

点击这里下载 Android中的IPC机制 XMind原始文件






Android IPC机制



多进程机制



 开启多进程方法:为四大组件在AndroidManifest中指定android:process属性



  指定完整进程名,如com.google.zxing.myproc或com.google.zxing.remote等。



  以“:”开头,比如设置为“:remote”。
则系统会在实际运行的进程名前加上应用程序的包名,即变成“com.google.zxing:remote”。



 Android为每个进程分配一个独立的虚拟机,不同的虚拟机有不同的内存空间。
使用多进程一般会造成以下4个问题



  静态成员和单例模式完全失效



  线程同步机制完全失效



  SharedPreferences的可靠性下降:本质是读/写XML文件



  Application会多次创建:每个进程是一个新的Application



IPC概念



 序列化



  Serializable接口



   最好指定serialVersionUID,以防止可能由于类变动而引起的反序列化失败



   静态成员变量属于类不属于对象,所以不参与序列化过程



   用transient关键字标记的成员变量不参与序列化过程



  Parcelable接口



   序列化功能由writeToParcel方法来完成;反序列化功能由CREATOR来完成;内容描述功能由describeContents方法来完成,几乎所有时候都返回0,除非当前对象中存在文件描述符时才返回1



  Serializable是Java中的序列化接口,使用简单但是开销很大,序列化和反序列化过程需要大量I/O操作。
Parcelable是Android中的序列化方式,更适合用Android平台上,使用起来稍微麻烦,但是效率高,是Android推荐的序列化方式,因此首选Parcelable。

Parcelable主要用在内存序列化上,但是把它用在 序列化到设备 或 通过网络传输 会比较麻烦,这两种场景下建议使用Serializable。


(Serializable接口, Parcelable接口)



 Binder



  什么是Binder



   Binder是Android中的一个类,它实现了IBinder接口



   从IPC角度来说,Binder是Android中的一种跨进程通信方式



   Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有



   从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowsManager等等)和相应ManagerService的桥梁



   从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当binderService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。



  工作机制如图
用AIDL只是简化了Binder的开发流程,让系统帮我们生成了很多代码。其实我们完全可以不写AIDL,而纯手写Binder,并在服务端的Service的onBinde方法中返回正确的对象。





   当客户端发起远程请求时,由于当前线程会被挂起直到服务端进程返回数据,所以如果一个远程方法很耗时,那么就不能在UI线程中发起此次远程请求



   由于服务端Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现。



 各种IPC方式



  使用Bundle。四大组件中的三大组件(Activity、Service、Receiver)都支持在Intent中传递Bundle数据。



  使用文件共享。



   存在并发读写问题



   SharedPreferences是个特例,虽然它底层是一个XML文件,但是系统对它的读/写有一定的缓存策略,即在内存中有一份SharedPreference文件的缓存,因此在多进程模式下系统对它的读/写变得不可靠,有很大的几率会丢失数据,所以不建议在进程间通信中使用SharedPreferences。



  使用Messenger。
它实际是对AIDL做了封装,底层实现其实是一个Binder。由于它一次处理一个请求,因此在服务端不用考虑线程同步的问题,因为服务端中不存在并发执行的情形。





   服务端进程



    Service:处理客户端的连接请求



    Handler:通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。



   客户端进程



    绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息。



    如果需要服务端能够回应客户端,则与服务端类似,在客户端创建一个Handler和一个新的Messenger,把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端就可以通过这个replyTo参数回应客户端了。



  使用AIDL。



   服务端:创建Service监听客户端请求,创建AIDL文件声明需要暴露给客户端的接口,在Service中实现这个AIDL接口。



   客户端:绑定服务端Service,将服务端返回的Binder对象转成AIDL接口所属类型,就可以调用AIDL中的方法了。



   AIDL支持的数据类型有限



   自定义的Parcelable和AIDL对象必须显示import,不管是不是与当前AIDL在同一个包中。



   如果AIDL中用到了自定义Parcelable对象,那么必须创建一个和它同名的AIDL文件,并声明拓为Parcelable类型。比如用到了Book类,则必须创建Book.aidl文件。



   AIDL中除了基本数据类型,其他类型的参数必须标明方向:in、out或者inout。
要根据实际情况指定,不要一概使用out或inout,因为底层实现有开销。



   为了方便把AIDL文件从服务端拷到客户端,应该在开发时把所有和AIDL相关的类和文件放入同一个包。



   在服务端,AIDL的方法在Binder线程池中执行,当多个客户端同时连接时,存在多线程同时访问的情形,要做好线程同步。



   如果要通过AIDL实现观察者模式,让服务端在数据变化时通知客户端,需要客户端利用Binder调用接口的注册方法来向服务端注册自己的回调函数。但这样会导致一个问题,就是服务端无法unregister这些回调函数,因为在跨进程传输时interface是被序列化数据重建的,在客户端和服务端不是同一个对象。因此,要通过AIDL在服务端使用客户端的回调函数,就需要用到RemoteCallbackList类来注册和反注册。
对RemoteCallbackList中任何数据访问,都必须配对使用beginBroadcast和finishBroadcast方法,即使是获取其中的元素个数。



   在用Binder绑定远程服务时,客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,不能在里面直接调用服务端的耗时方法,因为客户端会挂起等待结果。
服务端的方法本身就运行在服务端的Binder线程池中,所以服务端本来就可以执行大量耗时操作,不要在服务端方法中开线程去进行异步任务。
服务端如果回调客户端的某个方法,也是类似的,如果服务端直接在UI线程回调客户端的耗时方法,会导致服务端无法响应。
总之,通过Binder调用远程方法,调用者是同步执行,会挂起线程等待结果;被调用者是自动在Binder线程池中异步执行。



  使用ContentProvider。
底层实现其实是Binder。



   需要实现6个抽象方法:
onCreate:由系统回调,并运行在主线程。
query、update、insert、delete、getType:由外界回调,并运行在Binder线程池中。



   update、insert、delete方法会引起数据源的改变,这时需要通过ContentProvider的notifyChange方法来通知外界当前ContentProvider中的数据已经发生改变。



   要观察一个ContentProvider中的数据改变情况,可以通过ContentResolver的registerContentObserver方法来注册观察者,通过unregisterContentObserver方法来解除观察者。



  使用Socket。
分为TCP和UDP。



进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。



Android系统为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。
两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,它们可以相互访问对方的私有数据,比如data目录、组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。



6种IPC方式对比图