前言
IPC是 Inter-Process Communication 的缩写,含义为 进程间通信 或者跨进程通信,是指两个进程之间进行数据交换的过程。
线程vs进程
线程:
线程是CPU调度的最小单元,同时线程是一种有限的系统资源。
进程:
进程一般指一个执行单元,在 PC 和移动设备上通常指一个程序或者一个应用。
程序、进程、线程关系
通常来讲,一个程序就是一个进程(开启了多进程的程序除外),一个进程可以包含多个线程,当然一个进程也可以只有一个线程(毕竟人家就喜欢所有操作都放一个线程执行嘛,然后来个 ANR 就爽了),即主线程,在 Android 中我们常称之为 UI 线程,只有在 UI 线程中才能操作界面元素。
关系图(借用)
Android 中的多进程模式
Android是基于Linux的操作系统,它有自己的进程间通信方式,Android 通过 Binder 可以轻松的实现进程间通信,当然也可以通过 Socket 来实现任意两个终端之间的通信(这里暂不详细介绍)。
为什么要进程间通信
- 应用自身原因需要采用多进程模式来实现(例如:某些工具类 APP 的系统清理需要运行在单独的进程中)
- 某些操作对内存要求较高,通过开启多进程来获取多份内存空间(Android 对单个应用所使用的最大内存做了限制)
- 一个应用需要向另一个应用获取所需数据,两个应用需要跨进程通信(ContentProvider 就是跨进程通信,通信细节被系统内部屏蔽)
如何开启多进程
在 Android 中使用多进程只有一种方法就是给四大组件(Activity、Service、BroadcastReceiver、ContentProvider)在 AndroidManifest.xml 中指定 android:process 属性,注意:无法为一个线程或者一个实体类去指定运行时所在的线程。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:process=":remote"/>
<activity
android:name=".ThirdActivity"
android:process="com.wiwide.processdemo.remote"/>
命名方式 | 示例 | 类别 | 特点 |
---|---|---|---|
“:”开头 | “:remote” | 当前应用私有进程 | 其他应用的组件不可以与它跑在同一进程中 |
完整命名 | “com.wiwide.processdemo.remote” | 全局进程 | 其他应用可以通过 SharedUID 与它跑在同一进程中 |
我们知道 Android 系统会为每个应用分配一个唯一的 UID,具有相同 UID 的应用才能共享数据。两个应用通过 ShareUID 跑在同一个进程中是有要求的,需要两个应用有 相同的 ShareUID 还得 签名相同 才可以。(>>ShareUID 传送门<<)
如果两个应用满足了相同的 ShareUID 和相同的签名,那么他们就已经可以互相访问对方的私有数据了,例如 data 目录,组件信息等,不管它们是否跑在同一个进程中。当然,如果这两个应用还跑在了同一个进程中,那么除了可以共享 data 目录和组件信息外,还可以共享内存数据了,这时候这两个应用可以看做是一个应用中的两个部分了,是不是很神奇!
多进程引发的问题
Android 为每一个应用分配了一个独立的虚拟机,或者说为每个进程分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,在不同的虚拟机中访问同一个类的对象会产生多份副本。
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences 的可靠性下降
- Application 会被多次创建
静态成员和单例模式、线程同步机制完全失效
新建一个类 UserManager,定义一个 public 的静态成员变量 sUserId = 1,在 MainActivity 的 onCreate 方法中打印这个值,然后修改为 sUserId = 2 之后再次打印,修改成功。打开 SecondActivity (运行在另一个进程中)时同样在 onCreate 方法中再次打印下 sUserId,结果你会惊奇的发现,怎么还是 1 嘞。。
在 MainActivity 所在的进程
com.wiwide.processdemo
和 SecondActivity 所在的进程
com.wiwide.processdemo:remote
中,它们运行在不同的虚拟机中,也就在不同的内存空间中都有一个 UserManager 类,并且这两个类互不干扰,在一个进程中修改 sUserId 的值之后影响当前进程,对其他进程不会造成任何影响。既然都不在同一块内存了,那么单例模式和所谓的线程同步需要用到的锁对象或者锁全局类都已经不是同一个对象了。
SharedPreferences 的可靠性下降
SharedPreferences 不支持多个进程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为 SharedPreferences 底层是通过读写 xml 文件来实现的,并发写显然可能出现问题,甚至并发读/写都可能出问题。
Application 会被多次创建
当一个组件跑在一个新的进程中的时候,由于系统要在创建新的进程的同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。因此,相当于系统又把这个应用重新启动了一边,既然重新启动了,那么自然会创建新的 Application 。即:每个进程都有属于自己的虚拟机和 Application 。
通过 log 可以发现 Application 确实执行了三次 onCreate,并且每次的进程名和进程 id 都不一样。
注意:
笔者刚开始是通过 Android Studio 调试的,结果一直无法打印出来三次启动的 log(搞得我都怀疑是不是我写的代码有问题了),后来无奈换到了 DDMS 上成功打印了,才忽然反应过来 Studio 中的 logcat 默认是选择当前的进程,然而我们打开第二个 Activity 时开启了一个新的进程,这会的 log 当然不会在这里打印了。只需要把进程切换到 System 就可以了,当然 log 比较多,建议过滤一下。
本文参考了《Android 开发艺术探索》的相关章节,关于 IPC 中的多进程知识就先介绍到此,更多深入内容随后再做探讨。如果在阅读中发现哪里有难以理解或者描述不当的地方,欢迎留言指出!谢谢!