Denua 博客

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

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

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<Book> 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<Book> 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<Book> 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<IOnBookListener> 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<IOnBookListener> 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