简介
什么是IPC?IPC是Inter-Process Communication 的缩写,即进程间通信,用于两个进程来进行数据交换。
进程和线程
进程就是一个执行单元,即一个应用或者一个App,而线程是CPU调度的最小单元;进程包含线程,一个进程可以有一个或多个线程。在Android中应用本身包含了一个主线程(即UI线程),用于界面的绘制。如果在主线程中执行耗时操作,由于主线程需要更新界面,耗时操作导致界面无法及时更新,便引发了一个常见的“界面无法响应”(ANR)错误,创建子线程来执行耗时操作可以解决ANR问题,这是多线程的一个普遍应用场景。那么,多进程什么时候用到呢?多进程应用场景可分为两种,第一种是在单应用内使用多进程,由于业务需要,App内某些模块需要运行在单独的进程中,或者可以采取多进程来突破Android系统对当个应用的内存限制等到;第二种情况就是跨应用的数据交换,即一个App需要用到另一个App提供的数据,例如四大组件之一的ContentProvider就是使用了跨进程通信,只不过是谷歌的开发人员给我们封装好了,不需要我们自己去实现快进程的细节。
Android 中如何使用多进程
这里仅讨论常用的单应用多进程的情况。开启多进程的方法是通过在AndroidMenifest中为四大组件指定android:process属性,或者非常规方法在native层fork新进程。
<activity android:name=".SecoondActivity"
android:process=":remoteprocess">
</activity>
<activity android:name=".ThirdActivity"
android:process="com.ws.ipcdemo.remote">
</activity>
上面我们就为SecoondActivity和ThirdActivity指定了多进程的procee属性,系统在启动App时会默认开启一个新的进程,进程名为包名,然后当SecoondActivity启动的时候,由于指定了process属性,系统也会为它新建一个进程,名字为包名后面加上":remoteprocess",同样当ThirdActivity启动时一样会新建进程,它的包名就是指定的process后面的字符串了。
Android studio显示的进程
那么这两种命名方式有什么区别呢?仅仅是名字不一样吗?当然不是,最主要的一点就是进程权限,通过:方法开启的进程属于私有进程,即只有当前应用的组件可以和它跑在同一进程,而不以:开头来开启的进程属于全局进程,其他应用可以通过ShareUID方式和它跑在同一进程。
多进程带来的问题
从前面的使用过程来看,多进程也不过如此,不就是指定一个process嘛,毫无难度可言。确实,单单这样使用看起来是没啥难度,不过,当你真正用起来的时候就会发现它那巨大无比的坑。这里我们先列出常见的几个大坑,即多进程会造成的一些问题。
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences的可靠性降低
- Application不再唯一
下面我们来逐一分析上面的几个问题。首先是以静态成员为例。我们前面已经创建了三个Activity来测试多进程的启动方法,现在直接使用这三个进程来测试。我们需要新建一个类,这个类仅包含一个静态成员变量
public class TestStatic {
public static int ID=0;
}
然后分别在三个Activity 的onCreate里对这个静态变量执行加一操作,
TestStatic.ID++;
Log.d("TAG",this.getPackageName()+" ID="+TestStatic.ID);
然后先后打开这三个Activity,如果在单进程应用里,毫无疑问,TestStatic.ID变量会一直自加上去最后等于3,不过现在可以看下我们的结果:
06-02 09:11:26.586 15293-15293/? D/TAG: com.ws.ipcdemo ID=1
06-02 09:11:38.326 15454-15454/com.ws.ipcdemo:remoteprocess D/TAG: com.ws.ipcdemo ID=1
06-02 09:12:03.206 15693-15693/com.ws.ipcdemo.remote D/TAG: com.ws.ipcdemo ID=1
嗯~出问题了吧,并没有加上去。这个原因就是多进程搞的鬼。因为Android为每个进程都分配了一个独立的虚拟机,不同的虚拟机内存分配肯定是在不一样的地址的,所以一个类在多个虚拟机上会存在多份副本,各自的修改并不会影响到别的虚拟机里的类,这就解释了为什么我们的自增不起作用了。解释清楚了第一点,第二点也就显而易见了,既然内存地址都不一样了,对象和类当然也就不是同一个了,这就导致了锁会失效,因为你锁的对象或者类不是同一个。然后第三点是因为SharedPreferences本质是是讲数据写在xml文件上来进行数据存储的,而xml的并发读写显然有可能发生预期之外的结果。最后一个问题其实也很好理解,让我们先“show me the code”吧,仍然使用前面的工程,创建一个类继承Application,在onCreate中打印当前进程名(记得在AndroidMenifest文件中配置Application)。
06-02 09:44:10.981 31790-31790/com.ws.ipcdemo D/TAG: com.ws.ipcdemo ID=1
06-02 09:44:37.228 32131-32131/com.ws.ipcdemo D/TAG: 当前线程名:com.ws.ipcdemo
06-02 09:44:37.394 32131-32131/com.ws.ipcdemo D/TAG: com.ws.ipcdemo ID=1
06-02 09:44:47.575 32276-32276/? D/TAG: 当前线程名:com.ws.ipcdemo:remoteprocess
06-02 09:44:47.682 32276-32276/? D/TAG: com.ws.ipcdemo ID=1
06-02 09:44:53.728 32349-32349/? D/TAG: 当前线程名:com.ws.ipcdemo.remote
06-02 09:44:53.800 32349-32349/com.ws.ipcdemo.remote D/TAG: com.ws.ipcdemo ID=1
从打印的信息可以看到,启动三个Activity时,application都会执行onCreate方法,所以application不再在这个App内唯一了。
小结
本文初步探索了Android中的IPC机制,解释了什么是IPC,什么是进程和线程,Android中多进程的简单使用,以及使用多进程会带来的一系列问题,初步来看,多进程带来的麻烦远比它们的好处大,那为什么还要去研究它们呢。所谓存在即合理,如果没用,谷歌就不会在Android中保留IPC机制了,本系列接下来的文章会逐一去探索IPC的应用以及如何解决它带来的一系列问题。
网友评论