Denua 博客

Android IPC 机制,进程间通信笔记

发布时间: 2018-10-09 15:09   分类 : 笔记    标签: Android 浏览: 44   


IPC 是 Inter-Process Communication 的缩写,意思是进程间通信。Android 中的主线程(main)中不能进行耗时操作,否则会出现 ANR (Application Not Responding)了,由于 Android 对单个应用当我们有大量耗时操作和耗内存计算时可选择多进程。Android 中实现 IPC 的方法有很多,例如 Bundle,AIDL,Messenger,ContentProvider 等等。 # Android 如何使用多进程 Android 中开启多进程只能通过在清单文件中声明 process 属性值这种方式,可以通过两种方式,process=":remote", process="com.example.demo.remote" ,这两种方式的区别在于 冒号,“:” 的意思是在进程名前面附加当前包名,这种进程属于私有进程,不可与其他应用组件分享,而完整命名方式则属于全局进程,可以通过 ShareUID 在同一个进程中运行。 Android 系统会为每一个应用分配一个唯一了 UID,拥有相同的 UID 并且签名一致 的应用才能共享数据,可以互相访问私有数据,比如 data 目录组件信息等。 # 多进程的运行机制 在 Android 中,每个进程都会分配一个独立的虚拟机,不同虚拟机在内存分配上都有不同的地址,这将导致在不同虚拟机中访问同一个类的对象会产生多份副本,所以无法通过内存来共享数据。 多进程带来的影响 1. 静态成员和单例模式失效 2. 线程同步机制完全失效 3. SharedPreferences 可靠性下降 4. Application 会多次创建 # IPC 基本概念 ## Serializable 接口 Serializable 是 Java 提供的一个序列化接口,它是一个空接口。在实现了该接口的类中,可以指定 一个 serialVersionUID,该值时用于反序列化时对比是否为同一个类。未指定该值时,系统会计算当前类的 hash 值赋值给 serialVersionUID,如果类发生了改变则该值不同,则反序列化失败。 静态成员变量不属于对象,用 transient 关键字标记的成员变量都不参与序列化过程。 可以通过实现 writeObject(ObjectOutputStream out) 和 readObject(ObjectInputStream in) 这两个方法改变系统默认的序列化和反序列化过程。 ## Parcelable Parcelable 接口比 Serializable 接口稍微复杂一些,我们需要手动实现一些代码比如写入对象和读出对象。Parcelable 序列化效率比 Serializable 高,在内存序列化上主要使用 Parcelable,而要将对象存储到硬盘或网络传输则选择 Serializable。 # Binder 太难,略 # Bundle Bundle 实现了 Parcelable 接口 在 intent 中放入 bundle 实现进程间通信,只能单向通信,使用简单。 # Messenger Messenger 是一种轻量级的 IPC 方案,底层实现是 AIDL,它属于串行处理,不存在并发的问题。 服务端定义在 Service 中,创建一个 handler 继承与 Handler ,重写它的 handleMessage 方法,再将这个 handler 传入一个 Messenger 中。在 onBind 中返回该 Messenger 的 getBinder(); 在客户端的 onServiceConnected 中我们就可以通过实例化一个 Messenger ,在 Messenger 的构造方法中传入 IBinder 即可得到一个 Messenger。 Messenger(IBinder binder) Messenger(Handler handler) Message Message.obtain(Handler handler, int what) void Message.setData(Bundle bundle) void Messenger.send(Message msg) IBinder Messenger.getBinder() Messenger # AIDL 接口 AIDL (Android Interface Definition Language) Android 接口定义语言实现进程间通信是基于 Binder。AIDL 文件只是便于我们使用 Binder 而提供的一种模板,我们只需要简单的声明我们的接口即可为我们生成相应的 Binder 类。 AIDL 功能比较强大,支持一对多并发通信,支持实时通信。 ## 如何创建 AIDL 例如,src/main/aidl/com.example.aidl/IBookManager.aidl import com.example.aidl.Book; interface IBookManager{ List getBooks(); void addBooks(in Book book); } ADIL 支持的数据类型是有限的: 1. 基本数据类型 2. String 和 CharSequence 3. List:ArrayList 元素也需被 AIDL 支持 4. Map:HashMap 5. 实现了 Parcelable 接口的对象 6. AIDL 接口 其中在使用 Parcelable 和 ADIL 时不管是否在同一个包都需要使用 import 引入。 使用 Parcelable 对象时必须创建一个同名的 AIDL 文件并且声明为 Parcelable 类型。 例,src/main/aidl/com.example.com/Book.aidl parcelable Book; 其中除了基本类型之外都要在参数前标明方向,in, out, inout. ## 使用 AIDL 让在单独进程的 Service 与 Activity 通信 首先我们需要创建一个 IBinder 在 Service 的 onBind 方法中返回,这个类需要是 IBookManager.Stub 这个类的子类或者实例。 Binder mBinder = new IBookManager.Stub(){ // 这里需要实现我们在 AIDL 文件中定义的方法 .... } public IBinder onBind(Intent intent){ return mBinder; } 在 Activity 中绑定该 Service,从 onServiceConnection 中获取 onBind 返回的 Binder 实例,我们即可像访问同进程下的方法一样调用 AIDL 中定义的方法。同时,我们需要在 AndroidManifest 中对 Service 设置 process=":remote" 属性,表示这个 Service 在一个新的进程中运行。 public void onCreate(Bundle bundle){ ... bindService(new Intent(this, MyService.class), this, Context.BIND_AUTO_CREATE); } public void onServiceConnected(ComponentName name, IBinder binder){ IBookManager iBookManager = IBookManager.Stub.asInterface(binder); iBookManager.addBook(new Book(1, "A breif history of time")); List books = iBookManager.getBooks(); ... } 我们在 IBookManager.Stub 的实例和 Activity 中实现相应的通信代码逻辑便完成了 Activity 向 Service 进程间通信。 ### 如何让 Service 向 Activity 主动推送消息 在上面的例子中,只实现了 Activity 向 Service 调用方法,而反过来也是差不多,我们需要将 AIDL 定义好,在 Activity 中实例化该 AIDL 的 Stub 类,再将该对象传给 Service 即可。 定义用于Service 向 Activity 推送消息的 aidl import com.example.aidl.Book; interface IOnBookListener { void onNewBook(in Book book); } 修改 IBookManager import com.example.aidl.Book; import com.example.aidl.service.IOnBookListener; interface IBookManager { List getBookList(); void addBook(in Book book); void registerListener(IOnBookListener listener); void unRegisterListener(IOnBookListener listener); } 在 实例化 IOnBookListener 和 onServiceConnection 中注册该接口 public void onServiceConnected(ComponentName name, IBinder service){ IBookManager iBookManager = IBookManager.Stub.asInterface(binder); try { iBookManager.registerListener(iOnBookListener); } catch (RemoteException e) { e.printStackTrace(); } } private IOnBookListener iOnBookListener = new IOnBookListener.Stub(){ public void onNewBook(Book book) throws RemoteException { Log.d(TAG, "onNewBook: " + book.getName()); } }; 重写 Service 中的 IBookManager.Stub,并每隔两秒发布一本新书 private CopyOnWriteArrayList onBookListeners = new CopyOnWriteArrayList<>(); ... private void newBook(){ new Thread(new Runnable() { @Override public void run() { try { while (true){ for (IOnBookListener onBookListener:onBookListeners){ onBookListener.onNewBook(new Book(12, "New Book Tom And Jerry")); } Thread.sleep(2000); } } catch (InterruptedException | RemoteException e) { e.printStackTrace(); } } }).start(); } ... private Binder m = new IBookManager.Stub(){ public void registerListener(IOnBookListener listener) throws RemoteException { if (onBookListeners.contains(listener)){ Log.e(TAG, "registerListener: Already exist this listener" ); }else{ onBookListeners.add(listener); } } public void unRegisterListener(IOnBookListener listener) throws RemoteException { if (!onBookListeners.contains(listener)) { Log.e(TAG, "unRegisterListener: Doesn't exist listener"); }else{ onBookListeners.remove(listener); } } }; 如此一来,就可以在多个 Activity 中收到来自该 Service 的新书推送. 但是实践之后会发现,其中在一个 Activity 的 onDestroy 方法中 UNRegister 一个 listener 的时候,是无法实现的,原因就在于 AIDL 实现进程间通信的方法是将对象序列化的,对象序列化之前和反序列化之后的对象根本不是同一个对象,所以 onBookListeners.contains(listener) 永远返回一个 false; ### RemoteCallbackList 管理 AIDL 接口 这个类就是专门用于跨进程删除 listener 的,它是一个泛型类 RemoteCallbackList, 支持管理任何 AIDL 接口。 我们定义一个RemoteCallbackList ,重写 registerListener 和 unRegisterListener RemoteCallbackList listeners = new RemoteCallbackList<>(); ... public void registerListener(IOnBookListener listener) throws RemoteException { onBookListenerRemoteCallbackList.register(listener); } public void unRegisterListener(IOnBookListener listener) throws RemoteException { onBookListenerRemoteCallbackList.unregister(listener); } ... 同时,遍历 listener 的时候也需要重写;获取 listener 的大小只能通过 RemoteCallbackList.beginBroadcast() 这个方法获取,并且这个方法必须与 finishBroadcast 配对使用。 int n = onBookListenerRemoteCallbackList.beginBroadcast(); for (int i=0; i小于n; i++){ IOnBookListener i1 = onBookListenerRemoteCallbackList.getBroadcastItem(i); i1.onNewBook(new Book(12, "New Book Tom And Jerry")); } onBookListenerRemoteCallbackList.finishBroadcast(); ### 需要注意的点 - 在客户端调用服务端方法时,客户端会被挂起,如客户端是ui线程,服务端执行的又是耗时操作,则会出现 ANR,同样服务端调用客户端方法时,如果是 ui 线程,也不允许耗时操作; - 调用客户端 listener 中的方法时,不能对 ui 进行操作,必须转换为 ui 线程中操作; - 为了防止服务端进程意外死亡,我们需监听服务端的状态, 给 Binder 设置 DeathRecipient或者在 onServiceDisconnected中重连服务端,两种方法区别在于一个前者在 Binder 线程池中运行后者在 ui 线程运行; # Socket Socket 也叫 ‘套接字’, 分为两种,'流式套接字' 和 '用户数据报套接字',分别用于网络的'传输控制层' 的 TCP 和 UPD协议。 TCP 是面向连接的协议,提供双向稳定可靠的传输, UDP 是面向无连接的,提供不稳定的单向传输,其性能高于TCP 但不可靠。 # 各种 IPC 方案的优缺点 1. Bundle : - 简单易用 - 只能传输 Bundle 支持的类型 - 用于四大组件通信 2. 文件共享 - 简单易用 - 不适合高并发,即时通信 - 适合无并发,简单交换数据 3. AIDL - 功能强大,支持一对多并发、实时通信 - 实现复杂,需线程同步 - 用于一对多 RPC 需求场景 4. Messenger - 一对多串行通信,实时通信 - 不适用高并发,不支持 RPC,只能支持Bundle支持的类型 - 无需返回结果的RPC需求,简单使用 5. ContentProvider - 数据源访问方面强大,支持一对多并发数据共享 - 受约束的 AIDL,主要用于数据源的 CRUD 操作 - 用于一对多进程数据共享 6. Socket - 功能强大,支持一对多实时通信 - 细节繁琐 - 适用于网络数据交换 (完)

评论    

Copyright denua denua.cn