Android 中的 IPC 机制

本文主要从应用开发的角度介绍了 Android 中的 IPC 机制,重点介绍了 AIDL 和 Messenger 进程间通信的方式,文章结构如下所示



一. 简介

IPC 是 Inter Process Communication 的缩写,含义为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程。

进程和线程的大概区别有如下两点:

  • 在 Android 系统中进程通常是指一个应用,一个进程可以包含多个线程,也可以只有一个线程即:UI线程(主线程),其主要功能是更新界面
  • 子线程一般是用来执行大量的耗时任务的,在子线程中是不可以更新界面的

Android 系统是基于 Linux 内核的移动操作系统,在 Android 上不仅支持 Linux 本身就支持的 IPC 方式,包括命名管道、共享内存、信号量等进程间通信方式,而且在 Android 系统上有自己的 IPC 方式,如:Binder 机制和 Socket 机制

IPC 的一些应用场景

  • 某些模块由于特殊原因需要运行在单独的进程中
  • 为了加大一个应用可使用的内存,可以通过创建多个进程的方式获取多分内存空间
  • 当前应用需要和其他应用进行数据交换

二. 多进程模式

首先看一下,在同一个应用中如何开启多进程模式以及其运行机制

2.1 开启方式

  • 在 AndroidManifest.xml 文件中配置四大组件的 process 属性,该组件将会运行在单独的进程中,也就开启了一个新的进程,默认进程名称是当前应用包名
  • 进程命名方式:”包名:进程名称” —- 私有进程(包名可省略),”包名.进程名称” —- 全局进程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity>
    <!-- 全局进程 -->
    <service
    android:name=".messenger.RemoteService"
    android:enabled="true"
    android:exported="true"
    android:process="com.lijiankun24.ipcpractice.remote" />
    <!-- 私有进程 -->
    <service
    android:name=".aidl.RemoteBookService"
    android:exported="true"
    android:process=":aidlRemote">
    <intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <action android:name="com.lijiankun24.ipcpractice.MyService" />
    </intent-filter>
    </service>
    <activity android:name=".AidlActivity"></activity>
    </application>

除了可以在 Android Studio 的 Monitor 选项卡中看到多个运行的进程,还可以通过 adb 名称查看多个运行的进程:adb shell ——> ps | grep (应用包名)

1
2
3
4
LiJiankundeMBP:IPCPractice lijiankun$ adb shell
shell@m2note:/ $ ps | grep com.lijiankun24.ipcpractice
u0_a2106 5797 365 2547628 57192 ffffffff 00000000 S com.lijiankun24.ipcpractice
u0_a2106 5996 365 2168084 22352 ffffffff 00000000 S com.lijiankun24.ipcpractice.remote

2.2 运行机制

  • 通过上面的方式就可以开启多进程了,开启方式比较简单,但是多进程会引起其他的一些问题,问题的根源就要从多进程的运行机制讲起
  • Android 为每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有着不同的地址空间,所以运行在不同进程的四大组件不能通过共享内存来共享数据
  • 一般使用多进程会存在以下几个问题
    1. 静态成员和单例模式完全失效
    2. 线程同步机制完全失效
    3. SharePreferences 的可靠性降低
    4. Application 会创建多次

三. Binder

3.1 如何理解 Binder

可以从以下四个方面来理解 Binder

  • 直观来讲,Binder 是 Android 中的一个类,Binder 实现了 IBinder 的接口
  • 从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式,Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder
  • 从 Android Framework 角度来说,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager)和对应的 ManagerService 的桥梁
  • 从 Android 应用层来看,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以获取服务端提供的服务和数据,这里的服务包括普通服务和基于 AIDL 的服务

3.2 通过 AIDL 理解 Binder

Android 开发中,Binder 主要用在 Service 中,包括 AIDL 和 Messenger,其中普通 Service 不会涉及到进程间通信,无法触及 Binder 的核心。而 Messenger 的底层就是 AIDL,所以我们这里可以通过 AIDL 来分析 Binder 的工作机制

Android Studio 中的 aidl 文件目录结构,如下图所示



上图是一个典型的 AIDL 服务的目录结构,aidl 文件的生成,我就不细说了,网上有很多相关资料

Android Studio 会根据 .aidl 文件自动生成对应的 .java 文件,比如上图中所示的 IBookManager.aidl 生成的对应的 IBookManager.java 在 /app/build/generated/source/aidl/debug 目录下,IBookManager.java 文件的源码如下所示(为了阅读代码方便,对代码格式进行了调整):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/lijiankun/AndroidStudioOpen/IPCPractice/app/src/main/aidl/com/lijiankun24/ipcpractice/IBookManager.aidl
*/
package com.lijiankun24.ipcpractice;
// Declare any non-default types here with import statements
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.lijiankun24.ipcpractice.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.lijiankun24.ipcpractice.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.lijiankun24.ipcpractice.IBookManager interface,
* generating a proxy if needed.
*/
public static com.lijiankun24.ipcpractice.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.lijiankun24.ipcpractice.IBookManager))) {
return ((com.lijiankun24.ipcpractice.IBookManager)iin);
}
return new com.lijiankun24.ipcpractice.IBookManager.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_basicTypes:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List<com.lijiankun24.ipcpractice.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.lijiankun24.ipcpractice.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.lijiankun24.ipcpractice.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.lijiankun24.ipcpractice.IBookManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public java.util.List<com.lijiankun24.ipcpractice.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.lijiankun24.ipcpractice.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.lijiankun24.ipcpractice.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.lijiankun24.ipcpractice.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
public java.util.List<com.lijiankun24.ipcpractice.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.lijiankun24.ipcpractice.Book book) throws android.os.RemoteException;
}

分析一下上述代码:

  1. IBookManager 是个 interface 并且继承了 android.os.IInterface 接口。注:在 Binder 中传输的接口都需要继承 IInterface 接口
  2. 在 IBookManager 接口中,存在一个 Stub 抽象类和两个方法: getBookList() 和 addBook(Book book) 方法,这两个方法是在写 IBookManager.aidl 文件时就申明的
  3. Stub 抽象类是一个内部抽象类,它继承自 android.os.Binder 类,并且实现了 IBookManager 接口
  4. 在 Stub 抽象类内部又有一个内部类 Proxy,并且 Proxy 也实现了 IBookManager 接口
  5. 在生成的 IBookManager 中非常重要的两个类是 Stub 和 Proxy 类,接下来就分析下这两个类中重要的方法

    • Stub 抽象类中声明了两个整型的 id 分别用于标识这两个方法,这两个 id 用于标识在 transact 过程中客户端所请求的到底是哪个方法

      1
      2
      static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
      static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    • Stub 抽象类中声明了一个 String 类型的 DESCRIPTOR,DESCRIPTOR 是 Binder 的唯一标识,一般用当前 Binder 的类名表示,在 IBookManager 中 DESCRIPTOR 中如下所示

      1
      private static final java.lang.String DESCRIPTOR = "com.lijiankun24.ipcpractice.IBookManager";
    • Stub#asInterface(android.os.IBinder obj) 方法用于将服务端的 Binder 对象转换成客户端所需要的 AIDL 接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端对象本身,否则返回的是系统封装后的 Stub.proxy 对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      /**
      * Cast an IBinder object into an com.lijiankun24.ipcpractice.IBookManager interface,
      * generating a proxy if needed.
      */
      public static com.lijiankun24.ipcpractice.IBookManager asInterface(android.os.IBinder obj) {
      if ((obj==null)) {
      return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.lijiankun24.ipcpractice.IBookManager))) {
      return ((com.lijiankun24.ipcpractice.IBookManager)iin);
      }
      return new com.lijiankun24.ipcpractice.IBookManager.Stub.Proxy(obj);
      }
    • Stub 抽象类中的 asBinder() 方法,返回当前 Binder 对象

      1
      2
      3
      4
      @Override
      public android.os.IBinder asBinder() {
      return this;
      }
    • Stub 抽象类中的 onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) 方法是运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
      服务端通过

      • code 确定客户端请求的目标方法
      • 从 data 中取出目标方法所需要的参数(如果目标方法有参数的话)
      • 执行目标方法
      • 目标方法执行完毕后,向 reply 中写入返回值(如果目标方法有返回值的话)
      • 注意:如此方法返回false则客户端的请求会失败,所以我们可以在服务端中重写该方法, 然后利用这个特性来做权限验证。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      @Override
      public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
      {
      switch (code)
      {
      case INTERFACE_TRANSACTION:
      {
      reply.writeString(DESCRIPTOR);
      return true;
      }
      case TRANSACTION_basicTypes:
      {
      data.enforceInterface(DESCRIPTOR);
      int _arg0;
      _arg0 = data.readInt();
      long _arg1;
      _arg1 = data.readLong();
      boolean _arg2;
      _arg2 = (0!=data.readInt());
      float _arg3;
      _arg3 = data.readFloat();
      double _arg4;
      _arg4 = data.readDouble();
      java.lang.String _arg5;
      _arg5 = data.readString();
      this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
      reply.writeNoException();
      return true;
      }
      case TRANSACTION_getBookList:
      {
      data.enforceInterface(DESCRIPTOR);
      java.util.List<com.lijiankun24.ipcpractice.Book> _result = this.getBookList();
      reply.writeNoException();
      reply.writeTypedList(_result);
      return true;
      }
      case TRANSACTION_addBook:
      {
      data.enforceInterface(DESCRIPTOR);
      com.lijiankun24.ipcpractice.Book _arg0;
      if ((0!=data.readInt())) {
      _arg0 = com.lijiankun24.ipcpractice.Book.CREATOR.createFromParcel(data);
      }
      else {
      _arg0 = null;
      }
      this.addBook(_arg0);
      reply.writeNoException();
      return true;
      }
      }
      return super.onTransact(code, data, reply, flags);
      }
    • Stub 中的 asInterface() 中,如果客户端和服务端不是位于同一进程,就会将一个 Proxy 对象返回给客户端调用,接下来分析下 Proxy 的代码

      • Proxy 也实现了 IBookManager 接口
      • 在 Proxy 中主要看一下 getBookList() 和 addBook(Book book) 方法的实现
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      /**
      * 运行在客户端
      * 1.创建该方法需要的输入型Parcel对象 _data
      * 2.创建该方法需要的输出型Parcel对象 _reply
      * 3.把该方法的参数信息写入 _data 中(如果有参数的话)
      * 4.调用transact()方法来发起RPC(远程过程调用)请求,同时当前线程挂起
      * 5.服务端的onTransact()方法被调用,直到RPC过程返回,当前线程继续执行
      * 6.从 _reply 中读取RPC过程返回的结果(如果有返回值的话)
      */
      @Override public java.util.List<com.lijiankun24.ipcpractice.Book> getBookList() throws android.os.RemoteException
      {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      java.util.List<com.lijiankun24.ipcpractice.Book> _result;
      try {
      _data.writeInterfaceToken(DESCRIPTOR);
      mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
      _reply.readException();
      _result = _reply.createTypedArrayList(com.lijiankun24.ipcpractice.Book.CREATOR);
      }
      finally {
      _reply.recycle();
      _data.recycle();
      }
      return _result;
      }
      @Override public void addBook(com.lijiankun24.ipcpractice.Book book) throws android.os.RemoteException
      {
      android.os.Parcel _data = android.os.Parcel.obtain();
      android.os.Parcel _reply = android.os.Parcel.obtain();
      try {
      _data.writeInterfaceToken(DESCRIPTOR);
      if ((book!=null)) {
      _data.writeInt(1);
      book.writeToParcel(_data, 0);
      }
      else {
      _data.writeInt(0);
      }
      mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
      _reply.readException();
      }
      finally {
      _reply.recycle();
      _data.recycle();
      }
      }
    • 通过分析上面的代码,我们可以得知

      • 当客户端发起请求时,当前线程会被挂起,直至服务端进程返回数据,所以如果远程方法很耗时,一定要放在子线程中调用
      • 服务端的Binder方法运行在Binder线程池中,所以Binder方法不管是否耗时都应该采用同步的方式实现
      • 可以大致归结为三个对象(Client对象、Service对象及连接两个对象的Binder对象)和两个方法(在Client中调用的transact()方法和运行在Service中的onTransact()方法)
      • 调用顺序大致是:Client发起远程请求 ——> Binder写入参数data ——> 调用transact(),同时Client线程被挂起 ——> Service调用Binder线程池中的onTransact() ——>写入结果reply,通过Binder返回数据后唤醒Client线程

四. IPC 的方式

4.1 Bundle

  • Bundle适用与不同进程中的四大组件间的进程通信,一般是单向通信,即在某个进程中打开另外一个进程中的组件时通过Intent传递
  • Bundle支持的数据类型有基本类型、实现了Parcelable或Serialzable接口的对象、List、Size等等具体可见Bundle类
  • 还有一种比较特殊的场景是,在 A 进程中得到一个计算结果,但是该计算结果不支持放入 Bundle 中,无法传给 B 进程。这种情况我们可以换一种解决思路,比如 A 进程通过 Intent 启动 B 进程中的一个 Service,在 B 进程的 Service 中去进行计算并得到计算结果,之后再去启动 B 进程中真正要启动的目标组件,由于 Service 已经存在于 B 进程中,计算结果和目标组件是在同一进程中的,所以目标组件可以直接获取到该计算结果,这样就解决了无法通过 Bundle 传递计算结果的问题了

4.2 文件共享

  • 通过将数据序列化到外部存储设备上,然后再从外部设备上进行反序列化来达到通过共享文件进行进程间通信
  • 因为会发生并发读写问题,所以这种方式适合在对数据同步要求不高的进程间进行通信
  • 问题:通过文件共享的方式进行进程间通信时会面对并发读写的问题,怎么处理这个问题?

4.3 Messenger

  • Messenger 是一种轻量级 IPC 方案,它的底层实现是 AIDL,它对 AIDL 做了封装使用起来更简单
  • 通过 Messenger 可以在不同的进程中传递 Message 对象,在 Message 对象中放入我们需要传递的数据
  • 由于 Messenger 一次处理一个请求,因此在服务端我们不用考虑同步的问题
  • Messenger 设计封装的很好,有传递数据的 Messenger,数据载体 Message 和接受处理数据的 Handler
  • 下面可以通过一个例子熟悉 Messenger 的使用

    • 服务端进程:创建一个 RemoteService 类,继承自 Service,并且在 AndroidManifest.xml 文件中注册该 RemoteService,并且将此 RemoteSerice 运行在一个独立的进程中,作为远程服务端

      1
      2
      3
      4
      5
      <service
      android:name=".messenger.RemoteService"
      android:enabled="true"
      android:exported="true"
      android:process=".remote" />

      在服务端进程中创建一个 Handler 接受处理消息,通过该 Handler 创建一个 Messenger 对象,并且在 Service 的 onBind() 方法中返回该 Messenger 对象,代码如下所示

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      /**
      * RemoteService.java
      *
      * Created by lijiankun03 on 2018/5/28.
      */
      class RemoteService : Service() {
      private val tag: String? = "RemoteService"
      private var mMessenger: Messenger? = null
      override fun onCreate() {
      super.onCreate()
      L.i("RemoteService onCreate")
      Utils.printProcessInfo(tag, this)
      val mHandler = RemoteHandler()
      mMessenger = Messenger(mHandler)
      }
      override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
      L.i("RemoteService onStartCommand")
      return super.onStartCommand(intent, flags, startId)
      }
      override fun onBind(intent: Intent): IBinder? {
      L.i("RemoteService onBind")
      return mMessenger?.binder
      }
      class RemoteHandler : Handler() {
      override fun handleMessage(msg: Message?) {
      L.i("RemoteHandler msg.what" + msg?.what)
      when (msg?.what) {
      1 -> {
      val messenger = msg.replyTo
      val sendMsg = Message.obtain()
      sendMsg.what = 99
      messenger.send(sendMsg)
      }
      }
      }
      }
      }
    • 客户端进程:首先绑定服务端的 Service,绑定成功后用服务端返回的 IBinder 对象创建一个 Messenger 对象,通过这个 Messenger 对象就可以向服务端发送消息了,发送消息的类型为 Message

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      class MainActivity : AppCompatActivity(), View.OnClickListener {
      private val tag: String? = "MainActivity"
      private var mMessenger: Messenger? = null
      private var mServiceConnection: ServiceConnection? = null
      private var mReceiveMessenger: Messenger = Messenger(ReceiveHandler())
      override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      initView()
      Utils.printProcessInfo(tag, this)
      L.i("RemoteService onBind")
      }
      override fun onClick(v: View) {
      when (v.id) {
      R.id.btn_bind_service -> {
      L.i("MainActivity BindRemoveService")
      bindService()
      }
      R.id.btn_send_msg -> {
      L.i("MainActivity SendMsg")
      sendMsg()
      }
      }
      }
      private fun bindService() {
      mServiceConnection = LocalServiceConnection()
      val intent = Intent(this, RemoteService::class.java)
      this.bindService(intent, mServiceConnection, android.content.Context.BIND_AUTO_CREATE)
      }
      private fun sendMsg() {
      val msg: Message = Message.obtain()
      msg.what = 1
      msg.replyTo = mReceiveMessenger
      mMessenger?.send(msg)
      }
      private fun initView() {
      findViewById<View>(R.id.btn_bind_service).setOnClickListener(this)
      findViewById<View>(R.id.btn_send_msg).setOnClickListener(this)
      }
      inner class LocalServiceConnection : ServiceConnection {
      override fun onServiceDisconnected(name: ComponentName?) {
      L.i("MainActivity onServiceDisconnected ")
      }
      override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
      mMessenger = Messenger(service)
      L.i("MainActivity onServiceConnected ")
      }
      }
      class ReceiveHandler : Handler {
      constructor() : super()
      override fun handleMessage(msg: Message?) {
      L.i("MainActivity msg.what" + msg?.what)
      }
      }
      }
    • 通过上面的例子就可以明白了 Messenger 的使用流程如下:

      • 绑定服务:将Service返回的Binder转换成IMessenger的同时得到封装好的Messenger
      • Client发起请求:通过Messenger将封装好数据的Message发送到Service,其底层调用的是IMessenger的send()方法
      • Service在Handler的handleMessage()方法中接收到请求并处理
      • 如果需要Service的相应则发起请求时要在Client创建属于自己的Messenger并通过message.replyTo传递给Service
      • Service将返回信息封装到Message中并通过message.replyTo携带过来的Client的Messenger发送相应数据
      • Client在Handler的handleMessage()方法中接收到返回信息并处理

4.4 AIDL

  • 在 Messenger 中,是以串行的方式处理客户端发送到服务端的消息,如果有大量并发的请求,使用 Messenger 并不合适
  • 而且 Messenger 的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这时使用 AIDL 的方法会更加合适
  • 在使用 AIDL 方式进行进程间通信时,可以分为客户端和服务端两个方面,下面分别介绍

    • 服务端:创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中申明,最后在 Service 中实现这个 AIDL 接口,并在 onBind() 方法中返回,过程如下所示

      1. 创建一个 Book 的 javabean 对象

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        /**
        * Book.java
        *
        * Created by lijiankun03 on 2018/5/30.
        */
        class Book(val bookId: Int, val bookName: String) : Parcelable {
        constructor(parcel: Parcel) : this(
        parcel.readInt(),
        parcel.readString()
        )
        override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.writeInt(bookId)
        dest?.writeString(bookName)
        }
        override fun describeContents(): Int {
        return 0
        }
        companion object CREATOR : Parcelable.Creator<Book> {
        override fun createFromParcel(source: Parcel): Book {
        return Book(source)
        }
        override fun newArray(size: Int): Array<Book?> {
        return arrayOfNulls<Book>(size)
        }
        }
        }
      2. 创建一个 Book.aidl 文件

        1
        2
        3
        4
        5
        6
        // Book.aidl
        package com.lijiankun24.ipcpractice;
        // Declare any non-default types here with import statements
        parcelable Book;
      3. 创建一个 IBookManager.aidl 的文件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        // IBookManager.aidl
        package com.lijiankun24.ipcpractice;
        // Declare any non-default types here with import statements
        import com.lijiankun24.ipcpractice.Book;
        interface IBookManager {
        /**
        * Demonstrates some basic types that you can use as parameters
        * and return values in AIDL.
        */
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
        double aDouble, String aString);
        List<Book> getBookList();
        void addBook(in Book book);
        }
      4. 创建一个 RemoteBookService 远程服务

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        class RemoteBookService : Service() {
        private val tag: String? = "RemoteBookService"
        private var bookList: List<Book>? = null
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Utils.printProcessInfo(tag, this)
        bookList = ArrayList()
        return super.onStartCommand(intent, flags, startId)
        }
        override fun onBind(intent: Intent?): IBinder {
        return MyBinder()
        }
        class MyBinder : IBookManager.Stub() {
        override fun basicTypes(anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String?) {
        }
        override fun getBookList(): MutableList<com.lijiankun24.ipcpractice.Book> {
        return bookList
        }
        override fun addBook(book: com.lijiankun24.ipcpractice.Book) {
        bookList?.add(book)
        }
        }
        }
      5. 在 AndroidManifest.xml 文件中申明此 RemoteBookService,并将其运行在单独的进程中

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <service
        android:name=".aidl.RemoteBookService"
        android:exported="true"
        android:process=":aidlRemote">
        <intent-filter>
        <category android:name="android.intent.category.DEFAULT" />
        <action android:name="com.ryg.sayhi.MyService" />
        </intent-filter>
        </service>
    • 客户端,首先要绑定服务端的 Service,绑定成功后,将服务端返回的 Binder 对象转出 AIDL 接口所属的类型,接着就可以调用 AIDL 中的方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      class AidlActivity : AppCompatActivity() {
      private var iMyAidlInterface: IBookManager? = null
      private val aidlServiceConnection = AidlServiceConnection()
      override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_aidl)
      findViewById<View>(R.id.btn_start_aidl).setOnClickListener({ bindAidl() })
      }
      override fun onDestroy() {
      unbindService(aidlServiceConnection)
      super.onDestroy()
      }
      private fun bindAidl() {
      val intent = Intent(this, RemoteBookService::class.java)
      bindService(intent, aidlServiceConnection, BIND_AUTO_CREATE)
      }
      inner class AidlServiceConnection : ServiceConnection {
      override fun onServiceDisconnected(name: ComponentName?) {
      }
      override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
      iMyAidlInterface = IBookManager.Stub.asInterface(service)
      L.i("AidlActivity getName " + iMyAidlInterface?.bookList?.size)
      val book = Book(100, "Red")
      iMyAidlInterface?.addBook(book)
      L.i("AidlActivity getName " + iMyAidlInterface?.bookList?.size)
      }
      }
      }

4.5 ContentProvider

ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,所以它天生适合进程间通信,底层实现也是Binder。下面是自定义Provider时的一些注意事项:

  • 服务端定义ContentProvider时新建类extends ContentProvider然后重写onCreate()、insert()、delete()、update()、query()和getType()方法。onCreate()是运行在主线程,其它CRUD方法运行在Binder线程
  • 然后在Manifest中注册provider,注意两个属性:android:authorities是指定provider唯一属性的,android: permission可选是指定访问此provider必须要申请的相应权限,也可以单独指定android:readPermission或android:writePermission
  • 可以使用SQLiteOpneHelper结合数据库完成ContentProvider对数据的操作
  • 可以使用UriMatcher来匹配查询的Uri,UriMatcher.addURI()初始化添加支持的URI,UriMatcher.match()匹配查询的uri
  • 在insert,delete和update方法中因为数据发生了变化要调用ContentResolver方法的notifyChange()
  • 要观察一个ContentProvider中的数据改变情况,可以通过ContentResolver的registerContentObserver方法来注册观察者,通过unregisterContentObserver方法来解除观察者
  • 客户端使用定义好的android:authorities属性值指定Uri,然后利用ContentResolver进行CRUD操作,如果服务端的provider注册时有声明权限,客户端必须在manifest中申请相应权限

4.6 Socket

  • Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层的TCP和UDP协议。
  • TCP协议:面向连接的协议,提供稳定的双向通信功能,连接的建立需要经过“三次握手”才能完成,提供了超时重传机制,稳定性很高。
  • UDP协议:无连接的,提供不稳定的单向通信功能,性能上效率更好,但是不能保证数据一定能正确传输,特别是在网络拥塞的情况下。
  • Socket不仅仅可以实现进程间的通信,而且还可以实现设备间的通信,不过需要设备之间的IP地址互相可见。虽然如此,但是使用Socket的业务场景一般是在客户端和服务端通信比较频繁,需要建立长时间稳定的连接的时候,所以在Android跨进程通信的实际运用场景较少。

4.7 IPC方式适用场景总结

  • Bundle:四大组件间的进程间通信,简单易用,但是只能传输Bundle支持的数据类型
  • 共享文件:无并发访问情形,交换简单的数据实时性不高的场景,简单易用,但是不适合高并发场景,且无法做到进程间的即时通信
  • AIDL:一对多通信且有RPC(远程过程调用)需求,功能强大,支持一对多并发通信,支持实时通信,但是使用稍复杂,需注意处理好线程同步
  • Messenger:低并发的一对多即时通信,无RPC需求,或者无需返回结果的RPC需求,功能一般,支持一对多串行通信,支持试试通信,但不能很好处理高并发情形,不支持RPC,数据通过Message传输,因此只能传输Bundle支持的数据类型
  • ContentProvider:一对多的进程间的数据共享,在数据访问方面功能强大,支持一对多并发数据共享,可以通过Call方法扩展其他操作,但是可以理解为受约束的AIDL,主要提供数据源的CRUD操作
  • Socket:网络数据交互,功能强大,可以通过网络传输字节流,支持一对多并发实时通信,但是实现细节稍微有点麻烦,不支持直接的RPC