多进程

Posted by ooftf on April 12, 2021

Process

什么是进程

进程系统进行资源分配和调度的基本单位,是程序在内存中的实例;进程之间内存相互独立

多进程应用的优点

  • Android 对单个进程是有内存大小限制的,多进程可以申请更多的总内存,减少单个进程占用内存的大小
  • 进程间崩溃不会相互影响
  • APP保活(前台进程优先级比较高;进程优先级越高、单个进程占用内存越小被杀死的优先级越小)

常见的多进程案例

  • 将 WebView 放入单独进程
  • 图片选择器放入单独进程
  • 将功能比较独立的功能放入单个进程,比如 网络日志查看、崩溃日志查看等功能

多进程会存在那些问题

  • 进程间内存相互独立,无法相互访问
    • ActivityLifeCycleCallbacks 只能拿到各自进程Activity的生命周期
    • 单例等静态资源会存在多份
    • 多进程会初始化多个Application
    • sp多进程无法及时同步
  • 多进程在同时读写文件的时候会出并发安全问题
    • sp
    • db
    • 文件读写
  • WebView cookie 问题
    • 原生 CookieManager cookie 可以同步
    • X5 CookieManger cookie 不可以
  • A 进程 startActivity 新 Activity 在哪个进程中
    1
    
      <activity android:multiprocess="true"> 
    
    • 默认情况下,未指定进程就是主进程,如果指定了进程就会指定进程中
    • multiprocess = true 的情况下从哪个进程启动就存在于哪个进程
  • SharePreferences跨进程 解决方案 ContentProvider 或者 Binder 桥接 或者 MMKV

如何使用多进程

1
2
<service android:name=".remote.RemoteService" android:process=":remote"/>
<service android:name=".remote.RemoteService" android:process="com.xxx.xxx.remote"/>
  • 包含 : 的进程属于当前应用的私有进程,其他应用的组件不可以和他跑在同一个进程中
  • 不包含 : 的进程属于全局进程,其他应用通过 ShareUID 方式可以和它跑在同一个进程中

Android 系统会为每一个应用分配一个唯一的 UID,具有相同 UID 的应用才能共享数据。两个应用通过 ShareUID 跑在同一个进程中是有要求的,需要这两个应用有相同的 ShareUID 并且签名相同才可以。在这种情况下,它们就在同一个进程,可以互相访问对方的私有数据比如 data 目录、组件信息、共享内存数据。

Android IPC (Inter-Process Communication) 的几种方式

  • Binder
    • ContentProvider(基于Binder)
    • Messenger (轻量级AILD,使用方便)(基于Binder),如果不需要处理多线程,可以使用 Messenger 来实现接口;
  • Intent
  • 共享文件
  • Socket
  • 共享内存

6 种 IPC 方式优劣势比较

6 种 IPC 方式优劣势比较

Messenger

Messenger 比使用 AIDL 更简单,因为 Messenger 会将所有服务调用加入队列。纯 AIDL 接口会同时向服务发送多个请求,那么服务就必须执行多线程处理。 对于大多数应用,服务无需执行多线程处理,因此使用 Messenger 可让服务一次处理一个调用。如果您的服务必须执行多线程处理,请使用 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
private const val MSG_SAY_HELLO = 1

class MessengerService : Service() {

    private lateinit var mMessenger: Messenger

    internal class IncomingHandler(
            context: Context,
            private val applicationContext: Context = context.applicationContext
    ) : Handler() {
        override fun handleMessage(msg: Message) {
            when (msg.what) {
                MSG_SAY_HELLO ->
                    Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show()
                else -> super.handleMessage(msg)
            }
        }
    }

    override fun onBind(intent: Intent): IBinder? {
        Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show()
        mMessenger = Messenger(IncomingHandler(this))
        return mMessenger.binder
    }
}

客户端代码

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
class ActivityMessenger : Activity() {
    private var mService: Messenger? = null
    private var bound: Boolean = false
    private val mConnection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            mService = Messenger(service)
            bound = true
        }
        override fun onServiceDisconnected(className: ComponentName) {
            //当与服务的连接意外中断时,例如服务崩溃或被终止时,Android 系统会调用该方法。当客户端取消绑定时,系统不会调用该方法。
            mService = null
            bound = false
        }
    }
    fun sayHello(v: View) {
        if (!bound) return
        val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0)
        try {
            mService?.send(msg)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }

    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
    }

    override fun onStart() {
        super.onStart()
        Intent(this, MessengerService::class.java).also { intent ->
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        if (bound) {
            unbindService(mConnection)
            bound = false
        }
    }
}

Binder

  • 同一个进程使用 Binder 可以使用 (service as LocalBinder).speak() 强转的方式,如果是不同进程使用强转就会报错 java.lang.ClassCastException: android.os.BinderProxy cannot be cast to xxx.xxx.xxx.LocalBinder
  • 如果是同一进程中的 Binder 调用,调用处和被调用处在同一线程中,如果是跨进程调用,服务端是在 Binder 线程池中执行的
  • AIDL方法是在服务端的 Binder 线程池中执行
  • 如果知道服务端方法是耗时的,那么就要避免在客户端的UI线程中访问这个远程方法
  • 使用了 一次 copy_from_user 和 mmap 完成的跨进程操作 客户端将数据通过 copy_from_user Native 方法将数据从客户端进程 copy 到系统空间,然后通过 mmap 将系统空间和服务端进程映射到同一块内存中。完成客户端到服务端的数据传输
  • 如果客户端多次发送同一个对象到服务端,服务端接收到的是不同对象,但是对象调用 asBinder() 获取到的对象是同一个,如果发送的是不同的对象 asBinder() 也是不同的

IBinder 方法介绍

  • linkToDeath,unlinkToDeath 监听服务端关闭,这种监听和 ServiceConnection.onServiceDisconnected 时机相同,不同点在于 ServiceConnection.onServiceDisconnected 在客户端 UI 线程中执行,而 binderDied 在客户端 Binder 线程执行
  • getSuggestedMaxIpcSizeBytes 获取传输数据最大 size
  • pingBinder 如果服务端进程已经被杀死返回 false,如果服务端进程存活,返回值和服务端 pingBinder 返回值相同(默认情况下是 true)
  • isBinderAlive 检查服务端 binder 所在的线程是否存活
  • getInterfaceDescriptor 当前 Binder 的 AIDL 接口的描述
  • queryLocalInterface 检查 Descriptor 在本进程是否有对应的 AIDL 接口
  • transact 跨进程传输数据
  • Context.unbindService 并不会触发 ServiceConnection.onServiceDisconnected 和 binderDied

Binder 的权限验证(主要用于跨 APP 通讯 )

  1. 使用 Permission 机制
    服务端: ```xml
1
2
3
4
5
6
7
8
9
  ```java
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.ryg.chapter_2.
        permission.ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return mBinder;
    }

客户端:

1
2
  <uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_
        SERVICE" />
  1. 在服务端的 onTrasact 方法中进行权限验证,如果验证失败就直接返回 false,服务端不会终止执行 AIDL 中的方法从而达到保护服务端的效果 具体验证方法有很多,可以采用 permission、包名、Uid、Pid 来做验证,getCallingUid 和 getCallingPid 可以拿到客户端所属应用的 Uid 和 Pid
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    // 采用 permission、包名 认证的示例代码
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.
        ACCESS_BOOK_SERVICE");
        if (check == PackageManager.PERMISSION_DENIED) {
            return false;
        }
    
        String packageName = null;
        String[] packages = getPackageManager().getPackagesForUid(getCalling-
        Uid());
        if (packages ! = null && packages.length > 0) {
            packageName = packages[0];
        }
        if (! packageName.startsWith("com.ryg")) {
            return false;
        }
    
        return super.onTransact(code, data, reply, flags);
    }
    
  2. 为 Service 指定 android:permission 属性等

    AIDL 语法

    支持数据类型

  • 基本数据类型(int、long、char、boolean、double 等)
  • String 和 CharSequence
  • List :只支持 ArrayList , 里面每个元素都必须能够被 AIDL 支持
  • Map : 只支持 HashMap ,里面的每个元素都必须被 AIDL 支持,包括 key 和 value
  • Parcelable : 所有实现了 Parcelable 接口的类
  • AIDL : 所有的 AIDL 接口本身也可以在 AIDL 文件中使用

特性

  • AIDL 文件同一个interface 不可以有两个同名方法,即使参数不同也不可以
  • 如果 AIDL 文件中用到了自定义的 Parcelable 对象,那么必须新建一个和它同名同包的 AIDL 文件,并在其中声明它为 Parcelable 类型
  • AIDL 中除了基本数据类型,其他类型必须标上方向:in、out、inout;in 表示输入性参数,out表示输出型参数,inout 表示输入输出型参数。in out 更像是以服务端的视角看待数据流向in out 详解
  • AIDL 接口中只支持方法,不支持声明静态变量

AIDL 生成代码分析(下面都以 SimapleRemoteService 为例)

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
/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.ooftf.demo.ipc.remote;
public interface SimapleRemoteService extends android.os.IInterface
{
  /** Default implementation for SimapleRemoteService. */
  public static class Default implements com.ooftf.demo.ipc.remote.SimapleRemoteService
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public boolean register(com.ooftf.demo.ipc.remote.ICallback callback) throws android.os.RemoteException
    {
      return false;
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.ooftf.demo.ipc.remote.SimapleRemoteService
  {
    private static final java.lang.String DESCRIPTOR = "com.ooftf.demo.ipc.remote.SimapleRemoteService";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.ooftf.demo.ipc.remote.SimapleRemoteService interface,
     * generating a proxy if needed.
     */
    public static com.ooftf.demo.ipc.remote.SimapleRemoteService asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.ooftf.demo.ipc.remote.SimapleRemoteService))) {
        return ((com.ooftf.demo.ipc.remote.SimapleRemoteService)iin);
      }
      return new com.ooftf.demo.ipc.remote.SimapleRemoteService.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
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_register:
        {
          data.enforceInterface(descriptor);
          com.ooftf.demo.ipc.remote.ICallback _arg0;
          _arg0 = com.ooftf.demo.ipc.remote.ICallback.Stub.asInterface(data.readStrongBinder());
          boolean _result = this.register(_arg0);
          reply.writeNoException();
          reply.writeInt(((_result)?(1):(0)));
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.ooftf.demo.ipc.remote.SimapleRemoteService
    {
      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 boolean register(com.ooftf.demo.ipc.remote.ICallback callback) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        boolean _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
          boolean _status = mRemote.transact(Stub.TRANSACTION_register, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().register(callback);
          }
          _reply.readException();
          _result = (0!=_reply.readInt());
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.ooftf.demo.ipc.remote.SimapleRemoteService sDefaultImpl;
    }
    static final int TRANSACTION_register = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.ooftf.demo.ipc.remote.SimapleRemoteService impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.ooftf.demo.ipc.remote.SimapleRemoteService getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public boolean register(com.ooftf.demo.ipc.remote.ICallback callback) throws android.os.RemoteException;
}

总结:Binder 调用流程

Binder 调用流程

  1. 系统空间接收到链接请求后,利用 mmap 将部分系统空间和服务端用户空间,映射到同一块内存中
  2. 通过和服务端 Service 建立 ServiceConnection 链接,获取到代理 IBinder 实际是 android.os.BinderProxy 对象
  3. 通过 SimapleRemoteService.Stub.asInterface(service) 方法生成 SimapleRemoteService.Proxy 对象
  4. 通过 SimapleRemoteService.Proxy 调用 SimapleRemoteService 接口的方法
  5. 生成 _data 和 _reply 两个 Parcel, _data 封装入参,_reply 封装返回值
  6. 调用 BinderProxy.transact 方法,传入 方法标识、_data、_reply 和 flag 四个字段
  7. 调用 Native 方法 BinderProxy.transactNative(code, data, reply, flags);
  8. Binder 跨进程数据传输
    通过 copy_from_user 方法将数据从客户端进程 copy 到 《系统进程和服务端进程的共享内存中》
  9. 服务端: Binder.execTransact 接收到跨进程数据 -> Binder.execTransactInternal -> Stub.onTransact
  10. Stub.onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) 通过 code 找到对应的方法,从 data 中读取调用方法的参数,执行方法调用,获取调用返回值写入 replay
  11. Binder 跨进程数据传输

mmap (Memory Map)

通常我们说的内存是虚拟内存,默认情况下系统会将虚拟内存映射到 RAM( 也就是内存条 ) 我们平常操作文件的流程是,虚拟内存映射到 RAM 通过 read 方法从 Store 读取数据到虚拟内存,修改虚拟内存中的数据后,通过 write 方法将数写入到 Store 中。 使用 mmap 可以将虚拟内存直接映射到文件所在的 Store 位置,直接操作硬盘.无需再将文件读取到 RAM ,也不需要将数据从 RAM 写入到 Store 也可以将,两个进程的内存空间同时映射到同一块 RAM 或者 Store 中

mmap实现

mmap 的实现主要是先建立一个vm_area_struct的数据结构,这个数据结构表示了进程虚拟地址空间中的一段。然后这个结构把磁盘文件的区域和虚拟地址空间中的一段连接了起来

(AIDL(Android Interface Definition Language)是什么)[https://developer.android.google.cn/guide/components/aidl.html?hl=zh-cn]

为实现跨进程进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。 简单来说,AIDL是一种语言,这种语言可以用简单的AIDL语句,生成复杂的可用于跨进程的 Binder Java 类。(不使用AIDL 手写 Bindler 也是可以跨进程的,所以说真正用于跨进程的是 Binder 的实现,AIDL 只是生成这种实现的一种简便工具)