Android IPC机制基础知识

Android IPC简介

IPC的含义

IPC是Inter-Process Communication的缩写,翻译过来是进程间通信的意思,指至少两个进程或线程间传送数据或信号的一些技术或方法(安卓一般是指两个进程之间进行数据交换的过程)。

线程和进程

  • 线程是CPU调度的最小单位,进程是计算机系统分配资源的最小单位(严格说来是线程)。
  • 进程一般指一个执行单位,在PC和移动设备上指一个程序或者一个应用。
  • 每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。
  • 一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。

安卓中何时使用多进程

  • 一个应用因为某些原因需要采用多进程模式,原因有很多,比如
    1. 加大应用可使用的内存。在早期android系统只为一个单进程的应用分配了16M的可用内存,随着手机的硬件的提升和android系统的改进,虽然可分配内存越来越多,但仍旧可以通过开启多进程来获取更多的内存来处理自己App的业务
    2. 有些模块因为特殊的原因需要运行在单独的进程中
    3. 运行一些”不可见人”的操作,比如获取用户的隐私数据,比如双守护进程来防止被用户杀掉
  • 当应用需要向其他应用获取数据,由于是两个应用,所以必须采用IPC来获取所需的数据

Android中的多进程模式

开启多进程模式

正常情况下,在Android中多进程指的是一个应用中存在多个进程。
通常,在Android中使用多进程只有一种方法,那就是给四大组件在AndroidManifest.xml文件中指定android:process属性。
具体用法如下图所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".SecondActivity"
android:process="com.feng.multiprogresstest.remote"/>

<service
android:name=".MyService"
android:enabled="true"
android:exported="true"
android:process=":remote"/>

  • MainActivity没有指定process属性,那么它运行在默认进程中,默认进程的进程名是包名(示例中的包名是com.feng.multiprogresstest)。
  • SecondActivity启动时(没有启动时不会创建进程),系统会为它创建一个名为“com.feng.multiprogresstest.remote”的进程。
  • 同样,MyService启动时,系统也会为它创建一个名为“com.feng.multiprogresstest:remote”的进程。

查看当前进程的方法

用USB线连接手机和电脑后,在电脑的“运行”中输入“cmd”,打开后使用命令“adb shell ps com.feng.multiprogresstest(当前包名)”就可以查看当前应用正在运行的进程。
如下图
image

process属性的两种命名方式

上述示例中有两种命名:一种是“:remote”,另一种是“com.feng.multiprogresstest.remote”,他们的区别如下

  • “:remote”是一种简写的方法,其进程名应是“com.feng.multiprogresstest:remote”,为包名加上当前命名。而“com.feng.multiprogresstest.remote”则是全称,进程名也是该名字。
  • 已“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。而另外一种命名的进程则属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

一般我们都是有私有进程,很少使用全局进程。

ShareUID

Android系统会为每个应用分配一个唯一的UID。只有两个应用的ShareUID相同并且签名相同(此时它们可以访问对方的data目录、组件信息等,不管它们是否在同一进程)才可以跑在同一进程中。在同一进程中的两个应用可以互相访问对方的私有数据(data目录、组件信息、内存数据等)。

使用多进程引发的问题

Android多线程虽然启动很简单,只需要设置process属性。但是启动后却会引发很多问题。

  1. 静态成员和单例模式完全失效

    由于Android会为每一个进程都分配一个独立的虚拟机,不同的虚拟机的内存分配不同,这就导致了同一个类在不同的虚拟机中会有不同的副本。我们修改其中一个进程中某个类的静态变量时,另一个进程的该静态变量并不会修改。

  2. 线程同步机制完全失效

    这也跟第一点的原因一样,既然都不在同一块内存了,那么线程锁根本就锁不住其他进程的该对象。

  3. SharedPreferences的可靠性下降

    SharedPreferences不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失,这是因为SharedPreferences底层是通过读/写XML文件实现的,并发写显然是可能出问题的,甚至并发读都有可能出问题。

  4. Application会多次创建

    这是因为不同进程拥有独立的Application,这会引发一些问题,因为一些人可能喜欢在Application中做初始化操作以及数据的传递操作,这显然是不妥当的,解决的方法就是得到每个进程的名称,如果进程的名称和我们应用的进程名称相同则做我们应用的操作,如果不是则做其他进程的操作。

总的来说,不同进程的组件会拥有独立的虚拟机、Application和内存空间,在实际开发中需要注意有这引发的一些问题。我们也可以这样理解一个应用间的多进程就相当于两个不同的应用采用了SharedUID的模式。

IPC基础概念介绍

前言

IPC的基础概念主要是这三方面:Serializable接口、Parcelable接口以及Binder。

  • Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。
  • 需要把对象持久化到存储设备上或者通过网络传输给其他客户端时也需要使用Serializable来完成对象的持久化。

Serializable接口

Serializable是Java所提供的一个序列化接口,使用方法比较简单

1
2
3
4
5
public class User implements Serializable {
private static final long serialVersionUID = 1L;

// 其他的都是一般的实体类操作
}

就多了一个serialVersionUID,这个serialVersionUID是用来辅助序列化和反序列化的。

手动指定serialVersionUID的作用

当我们删除了类中的某个成员变量或增加了新的成员变量后,仍能够保证我们的反序列化过程能成功,程序仍能最大限度的恢复数据。如果我们不指定serialVersionUID,那么此时程序就会崩溃。这是因为当我们的类有所改变时,系统会重新计算当前类的hash值并把它赋给serialVersionUID,这时类的serialVersionUID和序列化时的serialVersionUID不一致,导致反序列化失败,而手动指定serialVersionUID就不会有这个问题。当然,如果类结构发生了非常规性改变,例如修改类名、修改了成员变量的类型,这个时候尽管serialVersionUID一致,但反序列化还是会失败,因为此时的类结构有了毁灭性的改变。

哪些成员变量不参与序列化

  • 静态成员变量属于类而不属于对象,所以不参与序列化过程
  • 用transient关键字修饰的成员变量不参与序列化过程

Parcelable接口

实现Parcelable接口比实现Serializable接口要复杂,但Parcelable接口的代码可以通过插件快速生成。这里简单说明下各个方法的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 内容描述,几乎所有情况都返回0,仅当当前对象存在文件描述符时返回1
* @return
*/
@Override
public int describeContents() {
return 0;
}

/**
* 实现序列化功能
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.userId);
dest.writeString(this.userName);
dest.writeByte(this.isLike ? (byte) 1 : (byte) 0);
}

/**
* 完成反序列化
* @param in
*/
private User(Parcel in) {
this.userId = in.readInt();
this.userName = in.readString();
this.isLike = in.readByte() != 0;
}


/**
* 通过Creator来完成反序列化功能
* 其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化
*/
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}

@Override
public User[] newArray(int size) {
return new User[size];
}
};

Binder

Binder是什么

  • 从类来看,Binder是Android中的一个类,它实现了IBinder接口
  • 从IPC角度来看,Binder是Android中的一种跨进程通信方式
  • 从物理设备来看,Binder是一种虚拟的物理设备,它的设备驱动是/dev/binder
  • 从Android Framework角度来看,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等等)和相应ManagerService的桥梁
  • 从Android应用层来说,Binder是客户端和服务端进行通信的媒介。当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端可以获取服务端提供的服务和数据,这里的服务包含普通服务和基于AIDL的服务。

通过AIDL来分析Binder的工作机制

由于这段分析较长,详情请看我的这篇文章

参考

-------------    本文到此结束  感谢您的阅读    -------------
0%