系列文章
Android 进程间通信(一) – Android 多进程模式
Android 进程间通信(二) – 理解 Binder 的机制
Android 进程间通信(三) --通过 AIDL 理解Binder,并手写Binder服务
一、IPC 简介
IPC 为 Inter-Process Communication 的缩写,意思为 进程间通信或跨进程通信,是指两个进程之间的数据交换的过程。
说起进程间的通信,首先要先认识进程和线程;
按照操作系统中的描述,线程是 CPU 调度的最小单元,同时线程是一种悠闲的系统资源;而进程一般指一个执行单元,在Android 中指一个应用。一个进程可以只有一个线程,也可以包含多个线程;
在Android中,主线程也叫UI线程,一些界面元素,都在该线程中操作,所以,一般一些的耗时操作都应该放在其他线程,不然会有 ANR。
对于 Android 来说,它是基于 Linux 操作系统的,但是却不是用管道,信号量等来进行进程通信,而是自己特色的 Binder ,通过 Binder 就能轻松实现进程间的通信。
当然,除了使用 Binder,也可以使用 socket,广播、contentprovider 等手段进行进程间通信。
二、Android 中的多进程模式
在进行跨进程通信之前,需要先学习一下 Android的多进程模式。
首先在 Android 通过给四大组件指定 android:process 属性,除此之外没有其他办法;也就是说我们无法给一个线程或者一个实体类指定其运行时所在的进程。淡然,其实还有一种非 常规的多进程办法,就是通过 JNI 在 native 层去fork一个新的进程,但这种比较特殊,也不是常见的创建多进制的方式,因此这里暂时不考虑。
如何在 Android 创建多进程呢?看下面的例子,这里新建两个 activity,并在 android:process 明明成不同的进程:
在 SecondActivity 的时候,process为 “:remote” ,首先 “:” 冒号是指在 当前的进程名前面加上当前的包名;当系统启动 SecondActivity 时,会创建一个单独的进程,进程名为:com.example.ipcdemo:remote;
而在 ThirdActivity 的时候,则是直接填写完成进程名 com.example.ipcdemo.remote 。
当我们把两个activity 都启动之后,我们用 ps | grep com.example.ipcdemo 看看:
可以看到,进程确实是不一样的。
上面说到,进程以 “:” 开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同个进程中,而进程名不以 “:” 开头的进程属于全局进程,其他应用通过 ShareUID方法就可以和它泡在同一个进程中。
我们知道,Android 系统会为我们的应用分配一个唯一的 UID,具有相同 UID 才能共享数据,但要共享数据,除了 UID 要相同,签名也要一致才行;这种情况下,他们可以互相访问对方的私有数据,比如 data 目录,组件信息等。
啥意思呢?比如你其中一个 moudle 有个私有字符串在 data/data/ 目录下。其他进程一般情况下是不能访问这个目录的,但是如果你的其中一个 moudle 的 shareUID 和它相同,且签名相同,虽然进程不相同,却能正常访问的。
关于 ShareUID 共享数据的,可以参考这篇文章:https://blog.csdn.net/yanjianjunaaa/article/details/13095087
2.1 多进程模式的限制
上面中,我们已经通过 process 开启了多进程,接着,我们写一个工具类,里面有一个成员变量
public class UserManager {
public static int testNum = 1;
}
我们在 mainactivity 的时候,设置为 2 :
UserManager.testNum = 2;
Log.d(TAG, "zsr onCreate: "+UserManager.testNum);
然后在 SecondActivity 的时候,再打印一下这个值:
可以看到,多进程下,数据并不能共享,为啥是这样?
原因是 SecondActivity 是在另外的进程里,我们知道 Android 为每一个应用程序都分配了一个单独的虚拟机,或者说为每一个进程都分配了一个独立的虚拟机,每个虚拟机在内存上分配上有不同的地址空间,这就导致不同的虚拟机中访问同一个对象会产生多个副本。
就上面的问题,com.example.ipcdemo 和 com.example.ipcdemo:remote 这两个进程都存在一个 UserManager 的类,mainActivity 在 com.example.ipcdemo 这个进程,修改 testNum 只影响当前进程的,而 secondActivity 属于com.example.ipcdemo:remote 进程,相互之间不影响。
一般来说,使用多进程会造成如下几方面的问题:
- 静态成员和单例模式失效 : 因为属于不同内存地址空间了,肯定不一样
- 线程同步机制完全失效 :因为对象都不同了,线程同步的锁对象或者锁全局都无法保证线程同步了。
- SharePreferences 的可靠性下降 :因为SharePreferences 底层是通过读/写 XML 文件的,并发可能会出现数据不同步问题。
- Application 会多次创建 : 这个也好理解,都不同进程了,虚拟机都要为它重新分配内存,所以 application 会多次创建。
2.2 Serializeble 和 Parcelable
在进程的通知中,我们还可以使用 Serializeble 和 Parcelable ,他们都能完成对象象的序列化过程。
2.2.1 Serializeble
需要注意的是 Serializeble ,它是 java 的提供一个序列化接口,它是一个口接口,实现起来比较简单,只要在 数据类中继承它即可:
public class UserBean implements Serializable {
public static final long serialVersionUID = 8308345L;
public String name;
public int age;
}
但为什么有个 serialVersionUID 呢?其实这个不写也可以,也可以实现序列化,只不过在反序列化的时候会有问题,这个等一下说。
如何进行序列化和反序列化呢?其实也很简单,只要使用 ObjectOutPutStream 和 ObjecInputStream 即可:
try {
String path = Environment.getExternalStorageDirectory().getAbsolutePath();
File file = new File(path,"cache.txt");
if (file.exists()){
file.delete();
}
/**
* 序列化
*/
UserBean zhangsan = new UserBean("zhangsan", 100);
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
//写入数据
outputStream.writeObject(zhangsan);
outputStream.flush();
outputStream.close();
/**
* 反序列化
*/
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
UserBean bean = (UserBean) ois.readObject();
Log.d(TAG, "zsr onCreate: "+bean.toString());
} catch (Exception e) {
Log.d(TAG, "zsr onCreate: "+e.toString());
e.printStackTrace();
}
刚才说到 serialVersionUID 会影响反序列化,那其实我们把写入数据的屏蔽掉,然后去掉 serialVersionUID ,再加上个字段 lastName:
//public static final long serialVersionUID = 8308345L;
public String name;
public int age;
public String lastName;
发现报了以下的错误:
local class incompatible: stream classdesc serialVersionUID = 8308345, local class serialVersionUID = 6474696687691921132
但是,把 serialVersionUID 还原,又能正常反序列化了。
所以,从这里知道了,在当前类序列化的时候,会把 serialVersionUID 写入到序列化的文件中(也可能是其他中介),当反序列化的时候,系统就会去检测,serialVersionUID 是否一致,如果一致 则说明这个是可以被序列化的。
2.2.2 Parcelable
由于 Serializeble 是Java 层的接口,且每次都是 IO 流,开销过大。 所以 Android 团队也开发了属于Android 自身的序列化接口 Parcelable,这个写法会比较复杂一点,但是现在使用 Android studio ,基本都可以帮助我们自己实现,而Parcelable是在内存上去序列化的,速度比Serializeble 快些。
这里就不过多介绍了。
下一章我们通过 AIDL 来学习 Binder 的机制。
参考Android艺术开发探索第二章