Android 之 IPC 进程通信全解析
本篇博客的框架
什么是IPC
IPC(Inter-Process Communication) 进程间通信,是指两个不同进程之间数据交换的过程。
在明确其之前,需要先搞懂几个概念:
- 线程:CPU可调度的最小单位,是程序执行流的最小单元;线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
- 进程: 一个执行单元,在PC 和移动设备上一般指一个程序或者应用,一个进程可以包含多个线程。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
在Android程序中,一般情况下一个程序就是一个进程(在无特别的代码实现下),UI线程即主线程。如果有耗时操作,则会导致主线程堵死。而在Android中主线程负责UI,和用户交互,如果堵死肯定影响用户的使用度。所以Android要求不能再主线程中有耗时操作,这时就要将耗时操作放在子线程中。
IPC 的使用场景
- 程序因为自身原因,需要采用多进程模式来实现。
- 有些模块由于特殊原因需要运行运行在单独的进程中。
- 为了加大一个应用可使用的内存所以需要通过多进程来获取内存空间。
- 当前应用需要向其他应用获取数据。由于是两个应用,即两个进程。
在Android 中,每一个应用可使用的内存大小有限制,早起的一些版本在16M左右,不同的设备有不同的大小。可以通过多进程获取多份内存空间。
Android中的多进程
如何开启多进程
Android中开启多进程只有一种方法,便是给四大组件(Activity
,Receiver
,ContentProvider
,Service
)指定android
属性,初次之外没有其他方法。请注意,不能指定某一个线程或者实体类指定其所运行的进程
:process
通过jni调用底层去开启多进程也是一种方法,但属于特殊情况,不进行考虑;
首先编写三个Activity
,并在清单文件中做如下注册:
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.example.ipc.SecondActivity"
android:process=":remote" />
<activity
android:name="com.example.ipc.ThirdActivity"
android:process=".remote" />
对MainActivity
不进行指定,则默认为当前进程。
对SecondActivity
指定属性android:process=":remote"
。
对ThirdActivity
指定属性android:process=".remote"
。
注意SencodActivity
和ThirdActivity
的进程参数不同。
把三个页面都打开,通过DDMS可以看到三个进程的开启
启动了三个进程:分别是
com.example.ipc
:默认的程序进程。和包名相同。com.example.ipc:remote
:SecondActivity
所在的进程。.remote
:ThirdActivity
所在的进程。
那么2和3 ,进程名不同有什么区别吗;
- 如果进程名以:开始,表示是要在当前的进程名前附加上当前的包名,表示该进程是本应用的私有进程,其他应用不可以和其跑在同一个进程。
- 如果进程名不以:开始,表示不需附加包名信息,是一个完全的命名。同时该进程是全局进程,其他应用可以通过
ShareUID
和其跑在同一个进程中。
开启多进程存在的问题
通过如上方式,很简单的变开启了多进程,但是,如果仅仅这样的话,会有大问题。
看下面一个例子:
添加一个公有的类,添加静态字段:
/**
* 类似平常开发中的工具类等
* @author MH
*
*/
public class PublicContant {
public static int m = 1;
}
在MainActivity
中Log一下并修改字段
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("info", PublicContant.m+"");
PublicContant.m++;
}
在SecondActivity
中Log一下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Log.i("info", PublicContant.m+"");
}
根据上面的逻辑,Log信息应该是1,和2 。但是呢,不是这样的。
两个都是1,我靠。。先不问原因,看结果就知道是错的。多进程这么不靠谱,肿么回事??
Android 为每一个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同虚拟机中访问同一个类对象会产生多个副本。
对于当前来说,进程com.example.ipc
和com.example.ipc:remote
都存在一个PublicContant
类,并且这两个类是相互不干扰的,一个进程中修改了该值的对象,对其他进程中的该值不会造成任何影响。
运行在同一个进程中的组件是属于同一个虚拟机和同一个
Application
的。同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application
的。
根据如上所述,多进程所造成的问题分为如下几个方面:
- 静态成员和单例模式完全失效
- 如上分析,创建了不同的内存,多个对象,当然单例什么的都无效了。
- 线程同步机制完全失效
- 不是一块内存区域,线程锁当然无效了。
SharedPreference
的可靠性下降
sharedPreference
的底层实现是通过读写XML文件,两个进程去读写,并发显然是可能出现问题的。
Application
会多次创建
序列化和反序列化
在了解多进程通信之前,我们需要了解两个基础的概念,序列化和反序列化。
- 序列化:将对象转化为可保存的字节序列。(注意,是对象)。
- 反序列:将字节序列恢复为对象的过程。
序列化和反序列的用途:
- 以某种存储形式使自定义对象序列化。
- 将对象从一个地方传递到另一个地方。
- 通过序列化在进程间传递对象。
在Android
中实现序列化的方式有两种,Serializable
和Parcelable
。
Serializable
Serializable
是Java提供的一个序列化接口,他是一个空接口,是类实现该接口即可实现序列化。
/**
* Serializable 序列化对象
* @author MH
*
*/
public class Book implements Serializable {
/**
* 序列化和反序列的关键
*/
private static final long serialVersionUID = 1L;
public int bookId;
public String bookName;
}
在实现Serializable
时候,编译器会提示,让我们添加serialVersionUID
字段,该字段是一个关键的字段,后面会说。
相应的实现好了,那么如何写入和读取呢?
- 写入
public void writeSerializable() {
try {