Android 学习笔记--IPC(进程间通信)

Author Avatar
ChihoPang 10月 09, 2017
  • 在其它设备中阅读本文章

IPC,Inter-Process Communication,进程间通信。因 Android 为不同的进程分配了独立虚拟机,不同虚拟机在内存分配上有不同的地址,访问同一个类对象时会产生多个副本,造成数据不同步。故在多进程间通信时,需要使用特殊的方式进行通信,即 IPC 机制。
知识点有:多进程相关知识、使用 IPC 的原因、Android 系统中 IPC 的方式、Messenger、AIDL,思维导图已提供知识点总览。
阅读本文需要了解 Service 中绑定服务的相关知识。

参考资料

《Android 开发艺术探索》第二章

思维导图

img

多进程相关

使用多进程的原因

  • 解决应用内存的问题,分担主进程的压力。
  • 独立于主进程,确保某些任务的执行和完成。

开启多进程模式

在 AndroidManifest 中,为四大组件添加 android:process 属性,即可开启多进程。
应用的默认进程名为包名,android:process 属性值则为新开启的进程名。
可通过指定 android:process=“:xxx” ,将新开启的进程设置为当前应用的私有进程,此时其他应用的组件不可和它运行在同一进程当中。

使用 IPC 的原因

因 Android 为不同的进程分配了独立虚拟机,不同虚拟机在内存分配上有不同的地址,访问同一个类对象时会产生多个副本,造成数据不同步。

Android IPC 方式

  • Bundle:启动时直接传递
  • 文件共享
  • Binder:AIDL、Messenger
  • ContentProvider
  • Socket

Binder

普通 bindService 中,我们用 Binder 来进行同进程内客户端与服务的通信。涉及到进程间通信时,AIDL 与 Messenger 同样使用 Binder 来实现通信。

Messenger

底层实现为 AIDL,Messenger 进行了封装,使用简单,适合一次只处理一个请求的服务,不适用于并发服务。

客户端向服务端发送通知
服务端

Service 内先创建一个 Handler,用于通信时接收客户端信息。再利用此 handler 创建 Messenger 对象包裹,并在 onBind() 返回 mMessenger.getBinder() 供客户端通信。

public class MessengerService extends Service {
    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {...}
    }

    final Messenger mMessenger = new Messenger(new MyHandler());

    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
客户端

客户端通过 onServiceConnected() 返回的 IBinder 创建 Messenger 对象,并通过 mMessenger.send(msg) 向服务端发送通知,注意捕获 RemoteException。

private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mMessenger = new Messenger(service);
        }
    };

public void sayHello(View v) {
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
        try {
            mMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
服务端向客户端发送通知

上述方法为客户端向服务端发送通知,下面讲解如何让服务端向客户端发送通知。
客户端先通过 Handler 创建 Messenger,并传入到 msg.replyTo 变量。服务端接收到 msg 后,取得 msg.replyTo 的 Messenger,调用 clientMessenger.send(msg) 实现服务端向客户端发送通知。代码实现如下:

//客户端
private Messenger mGetReplyMessenger=new Messenger(new MsgHandler());

public void sayHello(View v) {
        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
          msg.replyTO= mGetReplyMessenger;
        try {
            mMessenger.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
//服务端
public class MessengerService extends Service {
    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
                Messenger clientMessenger=msg.replyTo;
                try {
                    clientMessenger.send(newMsg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
          }
    }
Messenger 原理

img

AIDL(Android 接口定义语言)

利用它定义客户端与服务端进行相互通信时都认可的编程接口,应用需要跨进程通信、服务需要并发能力时有必要使用。
只需要跨进程、服务不需要并发能力时,可直接使用 Messenger 实现,简化操作。
应用不需要跨进程时,直接实现 Binder 类就可进行通信。

使用 AIDL
创建 .aidl 文件

Android Studio 支持直接创建 aidl 文件,在 moudle 层级右键 New -> AIDL -> AIDL File。
其编写语法与 Java 接口语法相同,并默认支持以下类型:

  • 基本数据类型
  • String、CharSequence
  • List、Map

当需要接入自定义类型时,需要将模型类实现 Parcelable 接口,并创建同名 AIDL 文件,在同名 AIDL 文件内使用 parcelable 关键字标明模型类名。再在需要使用的 AIDL 文件中进行 import 并使用。或者模型类其本身就为 AIDL 接口,则可直接 import 并使用。
非基本类型参数都需要指示数据走向的方向标记。可以是 in、out 或 inout。

package top.chihopang.ipclearing.aidl;

import top.chihopang.ipclearing.aidl.Book;//引入模型类
import top.chihopang.ipclearing.aidl.IOnNewBookArrivedListener;//引入 aidl 接口

interface IBookManager {
      //AIDL 方法定义
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
// Book.aidl
package top.chihopang.ipclearing.aidl;
parcelable Book;

// IOnNewBookArrivedListener.aidl
package top.chihopang.ipclearing.aidl;
import top.chihopang.ipclearing.aidl.Book;
interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

上述第一段代码,则为一个 AIDL 文件,其引入了 Book 模型类和同为 AIDL 接口的 IOnNewBookArrivedListener,并定义了四个接口方法。Book 模型类已在 java 包中定义。
第二段代码由两个文件组成,第一个文件为引入 Book 模型类的同名 AIDL 文件,第二个文件则为声明的 AIDL 接口,供 IBookManager 使用。
编写完上述代码后,SDK 工具会自动生成对应的同名 Java 文件,内含对应的处理代码,位于 app/build/generated/source/aidl 文件夹内。

实现接口

在服务中,我们继承自动生成的 Java 文件中的 Stub 子类(抽象类),实现 Binder 功能,并在 onBind() 方法中返回此 Binder 对象供客户端通信。
SDK 自动生成的 IBookManager 为继承 android.os.IInterface 接口的接口,其包含 AIDL 文件定义的方法以及 Stub 类。
IBookManager.Stub 类为抽象类,扩展 Binder 类并接入 IBookManager 接口,其内部还有一个 Proxy 代理类实现 IBookManager 接口。
实现接口时注意按照并发规范。

private Binder mBinder=new IBookManager.Stub(){

    @Override public List<Book> getBookList() throws RemoteException {
      return mBookList;
    }

    @Override public void addBook(Book book) throws RemoteException {
      mBookList.add(book);
    }

    @Override public void registerListener(IOnNewBookArrivedListener listener)
        throws RemoteException {
      mListenerList.register(listener);
    }

    @Override public void unregisterListener(IOnNewBookArrivedListener listener)
        throws RemoteException {
      mListenerList.unregister(listener);
    }
  };

@Nullable @Override public IBinder onBind(Intent intent) {
    return mBinder;
  }
客户端接收 Binder

客户端在 ServiceConnection 的回调方法中可以获取对应 AIDL 接口对象。
注意客户端需要通过 IBookManager.Stub.asInterface()方法进行获取,此方法将根据两端进程是否相同,服务端的 Binder 对象转化为 AIDL 接口对象:若进程相同,则直接返回服务端的 Stub 对象,反之则返回系统封装后的 Stub.proxy 对象。
客户端使用 AIDL 对象时需要捕获 RemoteException 异常。

private ServiceConnection connection=new ServiceConnection() {
    @Override public void onServiceConnected(ComponentName name, IBinder service) {
      IBookManager manager=IBookManager.Stub.asInterface(service);
      bookManager=manager;
      try {
        List<Book> bookList=manager.getBookList();
        Log.d(MainActivity.this.getClass().getSimpleName(),bookList.toString());
        manager.addBook(new Book(3,"开发艺术探索"));
        List<Book> newList=manager.getBookList();
        Log.d(MainActivity.this.getClass().getSimpleName(),newList.toString());
        manager.registerListener(mListener);
      } catch (RemoteException e) {
        e.printStackTrace();
      }
    }

    @Override public void onServiceDisconnected(ComponentName name) {
      bookManager=null;
    }
  };
AIDL 工作解析
客户端与服务端处于同一进程

服务与客户端同进程情况下,Stub.asInterface() 直接返回服务端的自定义 Stub 实例,调用时直接操纵服务数据。

客户端与服务端处于不同进程

不同进程情况下,Stub.asInterface() 返回 Stub.Proxy 代理对象。Stub.Proxy 为 SDK 工具根据 aidl 文件自动生成的实现了 aidl 接口的代理类,作为两端通信的枢纽,协助两端进行数据交互。Proxy 的工作机制如下:
img