Android IPC:IPC简介和安卓中的多进程模式
Android IPC要点
一、安卓中ipc简介
1、相关概念:
1、 IPC:inter process communication的缩写,进程间的通信,即两个进程间进行数据交换。
2、线程:线程是cpu调度的最小单元,同时线程是一种稀有的系统资源。
3、进程:进程一般指一个执行单元,在pc或者移动设备上一般指一个程序或者一个应用。
2、进程与线程关系:
一个进程可以包含多个线程。最简单情况下一个进程可以只有一个线程,即主线程。
ps:安卓中的主线程也叫作UI线程,只有在ui线程里面才能进行更新UI操作。安卓中不能在主线程中进行耗时操作,否则会造成界面无法响应(ANR),耗时操作要放到子线程中。
3、ipc及其使用条件
ipc不是安卓独有,任何一个操作系统都需要有相应的ipc方式。
栗子:
windows 通过剪贴板、管道、和邮槽来进行ipc。
linux 通过命名管道、共享内存、信号量来进行ipc。
android 通过Binder(最特色的)、socket、等。
ps:对于安卓来说,他是一种基于Linux内核的移动操作系统,通信方式不完全继承自Linux。他有自己的ipc
ipc:使用条件
说到ipc使用场景,就必须提到多进程,只有面对多进程条件下才考虑ipc
哪些情况下可以使用多进程?
1、一个应用由于自身的原因需要采用多进程模式来实现。
比如有些模块由于特殊原因需要单独运行在进程中。再比如加大应用的可使用内存空间。(安卓对单个应用使用的最大内存做了限制早期版本为16M)
2、当前应用需要向其他应用获取数据。由于是两个应用不同必须采用跨进程方式通信。
比如常见的contentProvider只是通信细节被系统包装了,我们表面上无法看出。
二、安卓中的多进程模式
安卓中开启多进程唯一的方式,给四大组件指定android:process属性,开启方式十分简单,但是使用时却
暗藏杀机。多进程远远没我们想想那么简单。
1、开启多进程模式
正常情况下在安卓中 多进程是指一个应用中存在多个进程的情况,因此这里不总结两个应用之间的多进程情况。
我们知道 安卓中开启多进程唯一的方式,给四大组件指定android:process属性,除此之外没有其他方法, 也就是说我们无法给一个线程,或者实体类指定其所在的进程。
ps:非常规方法创建多进程,通过JNI在native层去fork一个新的进程。这种方法属于特殊情况不属于常见创建方式,我们暂且不考虑。
2、简单开启栗子:
如上图:我们注册了三个activity:mainActivity,main2Activity,main3Activity,并且mainActivity有个按钮打开跳转main2Activity,main2Activity有个按钮打开跳转main3Activity。于是我们运行代码,并把三个activity都打开。
执行adb命令查看进程:(使用ddms也可以查看)
- adb shell ps(一般使用下面的命令快速定位)
查看所有进程 - adb shell ps |grep 应用包名
查看带有指定包名的进程
注意:这里使用 “adb shell ps |grep 应用包名” 时adb可能报错不识别的命令。我们只需这样使用即可: adb shell“ps|grep 应用包名”
如上图我们发现有三个进程(一个默认的两个我们开启的)证明我们已经成功开启。
我们发现:我们在开启进程时main2activity和main3Activity开启方式不同:
- main2Activity采用方式
android:process=":remote" - main3Activity采用方式
android:process=“com.example.administrator.androiddevelopmentart.remote”
1、其实:的含义就是在当前的进程名(remote)前加上当前应用的包名。通过上图我们也可以看出完整进程名:com.example.administrator.androiddevelopmentart:remote,冒号就是一种简写的方法
2、对于main3Activity我们使用了一个字符串,其实这个字符串我们可以任意写比如:com.example.test 我们在查看进程名时就是com.example.test了不以冒号方式写的不会附加包名信息
说白了区别就是是否使用冒号方式
":"还有一个重要的含义
1、进程名以":“开头的进程属于当前应用私有进程,其他应用的组件不可以和他跑在同一个进程中。
2、进程名不以”:"开头的进程属于全局进程,其他应用通过ShareUID 方式可以和他跑在同一个进程中。
3、有关UID
安卓系统会为每个应用分配唯一的uid,具有相同uid的应用才能共享数据。
两个应用通过ShareUID 跑在同一个进程中是有要求的:需要整两个应用具有相同的ShareUID 并且签名相同才可以。这种情况下他们可以互相访问对方的私有数据,比如data目录组件信息等。
4、多进程模式的运行机制
一句话形容多进程“当应用开启多进程后,各种奇怪的问题就出现了”真是看似简单实际是深坑哈。
案例引申:
首先我们创建一个类:UserManager(activity还是使用我们前面的)
package com.example.administrator.androiddevelopmentart;
/**
* Create by SunnyDay on 2019/03/30
*/
public class UserManager {
public static int sUserId = 1;
}
进行如下操作:
1、mainactivity的oncreate中添加代码 UserManager.sUserId = 2;打印这个静态值再启动main2activity
2、在main2Activity中我们在打印下这个静态值。
按照正常逻辑,静态变量可以所有地方共享的,并且一处修改,处处同步。然而我们看打印结果(如下图)和我们想想的不一样,我们想的时mainactivity打印2没错,main2Activity应该也打印2,怎么这里打印了1?这里我们应该明白了多进程的坑了吧,不仅仅是指定一个android:process那么简单。
上述原因解释:
main2activity运行在一个单独的进程,我们知道android为每一个应用分配了一个独立的虚拟机,或者说为每一个进程都分配了一个独立的虚拟机。不同的虚拟机在内存分配上有不同的地址空间,这就导致不同的虚拟机中访问同一个类的对象会产生多个副本。拿我们上面栗子来说 mainActivity运行在默认的进程中(进程名为应用包名),main2Activity运行在新的进程(com.example.administrator.androiddevelopmentart:remote)中,两个进程中都存在UserManager类,并且这两个类互不干扰,在一个进程中修改sUserId 只会影响当前的进程,对其他进程不会造成影响。这样就解释为啥值不同了吧!
5、多进程带来问题
多进程带来主要影响:所有运行在不同进程中的四大组件,只要他们之间通过内存来共享数据,都会共享失败。(一般我们使用多进程都是来共享数据的,不共享数据情况较少,不共享数据就不会有如上问题了)
使用多进程带来的问题(一般来说)
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- sharedpreference的可靠性下降
- Application会多次创建
静态成员和单例模式完全失效: 这个在上面的案例就已经分析了。
线程同步机制完全失效: 这种状况本质和上面的状况类似,既然不是同一快内存那么不管是锁全局类还是锁对象都无法保证线程同步。因为不同进程锁的不是同一个对象。
sharedpreference的可靠性下降: sharedpreference不支持两个进程同时执行写操作,否则导致一定几率数据丢失。其底层是通过读写xml文件实现的,并发写显然是可能出问题的。
Application会多次创建 :这种状况也是显而易见的,当一个组件跑在新的进程中的时候,由于系统要创建新的进程,同时分配新的虚拟机,所以这个过程其实就是启动一个应用的过程,相当于系统重新把这个应用启动了一遍,那么自然会创建新的Application,同理运行在不同进程中的组件属于两个不同的虚拟机和application的。(测试举例如下)
Application会多次创建的栗子:
首先我们在application中打印当前的进程名字:
package com.example.administrator.androiddevelopmentart;
import android.app.Application;
import android.os.Process;
import android.util.Log;
import com.example.administrator.androiddevelopmentart.utils.MyUtils;
import static com.example.administrator.androiddevelopmentart.MainActivity.TAG;
/**
* Create by SunnyDay on 2019/03/27
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
String processName= MyUtils.getProcessName(getApplicationContext(),Process.myPid());
Log.i(TAG, "onCreate: "+processName);
}
}
这里使用到了一个工具类根据上下文,uid打印当前进程名
package com.example.administrator.androiddevelopmentart.utils;
import android.app.ActivityManager;
import android.content.Context;
import android.app.ActivityManager.RunningAppProcessInfo;
import java.util.List;
/**
* Create by SunnyDay on 2019/03/30
*/
public class MyUtils {
/**
* @function 根据 context 用用pid来获取进程名
* @param cxt 上下文
* @param pid 应用pid
* */
public static String getProcessName(Context cxt, int pid) {
ActivityManager am = (ActivityManager) cxt
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps == null) {
return null;
}
for (RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
return procInfo.processName;
}
}
return null;
}
}
进行如下操作:
运行app,打开main2activity,main3activity。(运行结果如下图)
ps:main2activity,main3activity我们设置了新的进程。还是前面配置好的activity。
可以看到:application 的oncreate执行了三次,并且每次进程都不一样。他们的进程名和我们指定的activity的process值一样,这也证实了:在多进程模式中不同进程的组件的确会拥有独立的虚拟机,application,以及内存空间。这会给实际开发带来很多困扰。或许我们可以这么理解同一个应用中的多进程:他就相当于两个不同的应用采用了SharedUID的模式这样更加理解多进程的本质。
小结:
多进程带来的很多问题,但是我们不能因为有问题就不去正是他,为了解决这个问题,系统提供了很多跨进程通信方式。虽说不能直接的共享内存但是跨进程通信我们还是可以实现数据交互的。
实现跨进程方式很多:比如通过Intent(传递bundle等)、共享文件、sharedpreference,以及基于Binder的Messenger和AIDL以及Sockrt等。
详情下节见:
Android IPC(二) 待续。。。。。。
The end
本文来自<安卓开发艺术探索>笔记总结