一、安卓中IPC简介
ipc:inter process communication的缩写即进程间通信。ipc不是安卓独有,任何一个操作系统都需要有相应的ipc方式。windows 通过剪贴板、管道、和邮槽来进行ipc。 linux 通过共享内存、命名管道、信号量来进行ipc。android 通过Binder、socket、来进行ipc。
二、安卓中的多进程
1、开启多进程
安卓中开启多进程唯一的方式:给四大组件指定android:process属性。非常规方法创建多进程,通过JNI在native层去fork一个新的进程。这种方法属于特殊情况不属于常见创建方式,这里暂且不考虑。正常情况下在安卓中 多进程是指一个应用中存在多个进程的情况,因此这里不总结两个应用之间的多进程情况。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.sunnyday.ipcdemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<!-- 3、运行在com.sunnyday.ipcdemo.test 进程中-->
<activity android:name=".ThirdActivity"
android:process="com.sunnyday.ipcdemo.test"/>
<!-- 2、运行在com.sunnyday.ipcdemo:remote进程中-->
<activity android:name=".SecondActivity"
android:process=":remote"/>
<!-- 1、默认运行在com.sunnyday.ipcdemo 进程中-->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
2、开启结果观看
adb shell ps:查看所有的进程信息。
adb shell ps” |grep abc” 过滤包含abc字符串的进程。
进程号pid是进程的唯一标识。
F:\ASworkpalce\IPCDemo>adb shell ps
USER PID PPID VSZ RSS WCHAN ADDR S NAME
.....
.....
u0_a55 19701 1889 1404004 70304 ep_poll f4e33b39 S com.sunnyday.ipcdemo
u0_a55 19757 1889 1401916 68436 ep_poll f4e33b39 S com.sunnyday.ipcdemo:remote
u0_a55 19791 1889 1414252 71584 ep_poll f4e33b39 S com.sunnyday.ipcdemo.test
.....
.....
3、开启ipc三种写法详解
如上栗子给出了开启进程的三种写法:
1、默认的写法,如MainActivity ,不写process=XXX 这时process 默认为包名。组件运行在默认的进程中。这种进程属于非应用私有进程。
2、process=:xxx的写法,如SecondActivity,这里是一种简写方式,表示进程名为当前包名:xxx 。这种进程属于 应用私有进程 。
3、process=xxx的写法:如ThirdActivity,表示进程名为当前包名xxx 。这种进程属于非应用私有进程 。
4、UID
- 安卓默认为每个app分配一个user id 简称uid。故同一App所有的进程uid都相同。
- 具有相同uid的应用可共享data目录下的数据。
- 两应用可共用一个系统分配的uid。具体做法是两应用通过manifest的shareUid设置相同的字符串值,然后使用相同的apk签名。
三、多进程带来的问题
1、栗子引入
/**
* Create by SunnyDay on 2019/03/30
* 工具类
*/
public class UserManager {
public static int sUserId = 1;
}
<!-- 1、默认运行在com.sunnyday.ipcdemo 进程中-->
<activity android:name=".MainActivity">
<!-- 2、运行在com.sunnyday.ipcdemo:remote进程中-->
<activity android:name=".SecondActivity"
android:process=":remote"/>
操作:
1、MainActivity的oncreate: UserManager.sUserId = 2;打印这个静态值再启动SecondActivity
2、在SecondActivity中我们在打印下这个静态值。
打印结果:MainActivity#sUserId=2,SecondActivity#sUserId=1
传统认知:jvm中静态变量可以所有地方共享的,并且一处修改,处处同步,然而结果并非如此。
正解:android为每一个应用分配了一个独立的虚拟机,或者说为每一个进程都分配了一个独立的虚拟机。不同的虚拟机在内存分配上有不同的地址空间,这就导致不同的虚拟机中访问同一个类的对象会产生多个副本。
2、多进程带来问题
所有运行在不同进程中的四大组件,只要他们之间通过内存来共享数据,都会共享失败。
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- sharedpreference的可靠性下降
- Application会多次创建
线程同步机制完全失效: 这种状况本质和上面的状况类似,既然不是同一快内存那么不管是锁全局类还是锁对象都无法保证线程同步。因为不同进程锁的不是同一个对象。
sharedpreference的可靠性下降: sharedpreference不支持两个进程同时执行写操作,否则导致一定几率数据丢失。其底层是通过读写xml文件实现的,并发写显然是可能出问题的。
Application会多次创建 :这种状况也是显而易见的,当一个组件跑在新的进程中的时候,由于系统要创建新的进程,同时分配新的虚拟机,所以这个过程其实就是启动一个应用的过程,相当于系统重新把这个应用启动了一遍,那么自然会创建新的Application,同理运行在不同进程中的组件属于两个不同的虚拟机和application的。(测试举例如下)
<!-- 3、运行在com.sunnyday.ipcdemo.test 进程中-->
<activity android:name=".ThirdActivity"
android:process="com.sunnyday.ipcdemo.test"/>
<!-- 2、运行在com.sunnyday.ipcdemo:remote进程中-->
<activity android:name=".SecondActivity"
android:process=":remote"/>
<!-- 1、默认运行在com.sunnyday.ipcdemo 进程中-->
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
/**
* 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);
}
}
/**
* 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;
}
}
开启三个activity,打印三个进程值。