目录
3.1 IPC全称Inter-Process Communication
5.4 Serializable 与 Parcelable对比
1.每日一句
岂能尽如人意,但求无愧于己
2. 作者简介
🏡个人主页:XiaoChen_Android
📚学习专栏:Android
🕒发布日期:2022/9/18
本文是基于Android开发艺术探索中IPC机制所写,就当做做笔记啦
3、Android IPC简介
3.1 IPC全称Inter-Process Communication
含义为进程间通信或跨进程通信,是指在两个进程之间进行数据交换。那么什么是进程呢?它与我们常说的线程之间的关系又是什么?
按照操作系统的描述,线程是CPU调度的最小单元,而进程一般是指一个执行单元,在Android中进程一般是指一个应用程序,一个进程可以包含一个或多个线程。每个Android应用都有一个主线程,也就是我们常说的UI线程,假如我们试图在非主线程上更新UI,系统会抛出CalledFromWrongThreadException异常。
很多时候,一个进程中需要执行大量的耗时任务,如果这些任务都放在主线程中去执行,就会造成界面无法响应,严重影响用户的体验,这种情况在PC系统和移动系统都存在,在Android中有一个特殊的名字叫ANR(Application Not Responding),即无响应。解决这个问题就需要用到线程,把一些耗时任务放在线程中。
3.2 Android独特的IPC机制
IPC不是Android独有的,任何一个操作系统都有相应的IPC机制,比如Windows上可以通过剪贴板、管道和邮槽等来进行进程间通信;Linux上可以通过命名管道、共享内存、信号量等来进行进程间通信。
对于Android来说,它是一种基于Linux内核的移动操作系统,它的进程间通信方式并不能完全继承自Linux,相反,他有自己的进程间通信方式,其中最独特的就莫过于Binder了,通过它可以轻松地实现进程间通信。
使用多进程的情况一般都是这两种:
- 第一种情况就是一个应用因为某些原因自身需要采取多进程模式来实现,至于原因可能就比较多了,比如有些模块由于特殊原因需要运行在单独的进程中,又或者为了一个加大一个应用可使用的内存,所以需要通过多进程来获取多份内存空间
- 第二种情况就是当前应用需要向其他应用获取数据,由于是两个应用,所以必须采用跨进程的方式来获取所需的数据,甚至我们通过ContentProvider去查询数据的时候其实也是一种进程间通信,只不过通信细节被系统内部屏蔽了。
总之,如果采取了多进程的方式,就必须处理好进程间通信的各种问题
4.Android中的多进程
4.1 开启多进程
正常情况下,在Android中多进程是指一个应用中存在多个进程的情况,因此这里不讨论两个应用之间的多进程情况。首先在Android中使用多进程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process属性,除此之外没有别的办法,下面是一个示例:
<activity
android:name=".SecondActivity"
android:exported="false"
android:process=":remote"/>
当SecondActivity启动时,系统会为它创建一个单独的进程,进程名称为"包名:remote"。其他没有指定android:process属性的Activity,会运行在默认进程中,默认进程名称与包名相同。我们可以在Android Studio中查看当前应用运行的进程列表。
属性android:process=":remote"中":"的含义是指要在当前进程名前面加上应用包名,这是一种简写的方法,对于SecondActivity来说,它的完整的进程名是com.example.ipc:remote。其次,进程名以":"开头的进程属于当前应用的私有进程,其他应用的组件不能和它运行在同一个进程中,不以这方式命名的进程属于全局进程,其他组件可以通过ShareUID与它运行在同一进程中。
4.2 多进程运行模式机制
Android系统为每个进程都分配一个独立的虚拟机,每个虚拟机在 内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多个副本。因此运行在不同进程的组件,是不能通过内存共享数据的,必须要借助一些中间层来实现数据共享。
一般来说,使用多进程会造成下面的几个问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreferences的可靠性下降
- Application会多次创建
第一个问题和第二个问题本质上是类似的,既然都不是一块内存了,那么不管是锁对象还是锁全局内都无法保证线程同步,因为对于不同进程,锁的不是一个同一个对象。第三个问题是因为SharedPreferences不支持两个进程同时去执行写操作,否则会有一定几率的数据丢失,这是因为SharedPreferences底层是通过读/写XML文件来实现的,并发写肯定是有可能出现问题的,甚至并发读/写都有可能出现问题。第四个问题,当一个组件跑在一个新的进程中时,由于系统在创建新的进程同时要分配独立的虚拟机,所以这个过程其实就是启动了一个应用的过程,很容易验证,只要在同一个Activity中运行三个不同的进程,看onCreate()方法是否被调用了三次即可
5.IPC基础概念介绍
主要介绍Serializable接口、Parcelable接口以及Binder,只有熟悉这三方面的内容才能更好的理解跨进程通信的各种方式
5.1 Serializable接口
Serializable时Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作,使用Serializable来实现序列化很简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程
private static final serializableUID = 8711368828010083044L;
捷径: Android studio安装GenerateSerialVersionUID插件可以帮助开发者迅速生成SerialVersionUID
通过Serializable方式来实现对象的序列化,实现起来非常简单,几乎所有的工作系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可,如下
//序列化过程
User user = new User(0 , "XiaoChen" , true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close;
//反序列化过程
ObjectInputStrea in = new ObjectInputStream(new FileOutputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();
只需要把实现了Serializable 接口的User对象写入到文件中,反序列化的时候就可以直接从文件中读取对象数据,恢复后的newUser对象与之前的对象虽然内容相同,但是并不是同一个对象。
5.2 Parcelable接口
Parcelable也是一个接口。只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递,下面展示一个典型用法
package com.example.ipc;
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
protected User(int userId , String userName , boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel out, int i) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book, 0);
}
private User(Parcel in){
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
}
先说一下Parcel,Parcel内部包装了可序列化的数据,可以在Binder中自由传输,从上述代码中可以看出,在序列化过程中需要实现的功能有序列化,反序列化和内容描述。序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的。反序列化由CREATOR来完成,其内部表明了如何创建序列化数组和对象,并通过Parcel的一系列read方法来完成反序列化过程;内容描述功能由describeContents方法来完成,几乎所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时才返回1.
5.3 Parcelable方法说明
5.4 Serializable 与 Parcelable对比
- Serializable 是Java中的序列化接口,使用起来简单;Parcelable是Android中的序列化方式,使用起来稍微复杂一些。
- Serializable 在序列化操作的时候会产生大量的临时变量,从而导致GC的频繁调用(原因是使用了反射机制);Parcelable是以Ibinder作为信息载体的,在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable。
- 将对象序列化到存储设备上或者序列化后通过网络传输,使用Parcelable 会稍显复杂,因此这两种情况建议使用Serializable。
由于Binder是很深的概念,这里不做过多介绍,下一篇将介绍Binder
推荐阅读书籍:《Android开发艺术探索》