Android查缺补漏(IPC篇)-- 进程间通讯基础知识热身

时间:2023-03-08 15:54:20
Android查缺补漏(IPC篇)-- 进程间通讯基础知识热身

本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8479282.html

在Android中进程间通信是比较难的一部分,同时又非常重要,针对进程间通信,博主会用四篇文章来介绍,本篇文章为IPC系列的开篇,主要介绍一些IPC中用到的一些概念、基础等,目的是让读者朋友们在学习IPC之前对一些必要的知识有一个大体的把握。在Android中进程间通讯的方式有很多种,在后续的三篇中会分别介绍每一种方式的实现过程已经各自的优缺点。

进程间通讯篇系列文章目录:

IPC是什么?

IPC(全称:Inter-Process Communication)为进程间通讯,指至少两个进程间传递数据或信号的一些技术活方法。

注:进程间通讯是至少两个进程之间发生的事情,我们通常习惯性的会把一方称为客户端,一方称为服务端,在后续的文章也会多次出现客户端和服务端,没接触过进程间通信的童鞋可能一开始会不太习惯,这里要注意一下。

为什么要使用IPC?

无论是在计算机系统还是Android系统中每个进程都有自己一部分独立的系统资源,彼此是隔离的,为了能是不同的进程互相访问资源并协同工作,就需要用到进程间通讯。

RPC是什么?

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。(-来自百度百科)

在后面介绍AIDL时会用到RPC的概念,在这里简要说明一下RPC在Android的进程间通讯所扮演的角色,以博主本人的理解,简单来说RPC机制就是指在本地即可调用远程进程中的方法,而不需要关心其底层实现。

在Android中IPC有哪几种实现方式?

  • Bundle
  • 文件共享
  • ContentProvider
  • Messager
  • AIDL
  • Socket

如何开启一个进程

在四大组件的AndroidManifest配置中配置process属性

比如这个:

<service
android:name=".messager.MessengerService"
android:exported="true"
android:process=":remote" />

“:”开头和不带“:”的有什么区别:

“:”开头的进程属于当前应用的私有进程,其他应用的组件不能和它跑在同一进程下。

不带“:”的进程属于全局进程,其他应用可以通过ShareUID和它跑在同一进程下。

Android系统会为每一个应用分配一个UID,具有相同的UID才能共享数据。

通过ShareUID跑在同一进程中需要两个应用有相同的ShareUID并且有相同的签名才可以。

Android系统为每一个进程分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致不同的虚拟机访问同一个类的对象会产生多个副本。

使用多进程会导致如下问题:

  1. 静态变量和单例失效
  2. 线程同步机制失效
  3. SharePreference可靠性下降
  4. Application多次创建

IPC中涉及到的基础概念

  • Serializable
  • Parcelable
  • Binder

Serializable

使用Serializable进行序列化很简单,只需要实现Serializable接口,然后为类指定一个serialVersionUID即可。

Serializable中的serialVersionUID工作机制:

  1. 序列化时系统会把当前类的serialVersionUID写入序列化的文件中(或其他中介)
  2. 反序列化时系统去检测文件中的serialVersionUID,对比是否和当前类的seralVersionUID一致。
  3. 一致就说明序列化的类的版本和当前类的版本是相同的,可以成功反序列化,否则就说明当前类和序列化的类相比发生了某些转换,就会报错(java.io.InvalidClassException)
  • 静态变量属于类不属于对象,不参与序列化过程
  • 用transient关键字标记的成员变量不参与序列化过程

Parcelable

使用Parcelable进行序列化比Serializable要麻烦一些,需要实现Parcelable接口,并实现一些必要方法,其通常形式如下:

public class Contact implements Parcelable {
public int phoneNumber;
public String name;
public String address; public Contact(int phoneNumber, String name, String address) {
this.phoneNumber = phoneNumber;
this.name = name;
this.address = address;
} public Contact() {
} @Override
public int describeContents() {
return 0;
} @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(phoneNumber);
dest.writeString(name);
dest.writeString(address);
} public void readFromParcel(Parcel parcel) {
phoneNumber = parcel.readInt();
name = parcel.readString();
address = parcel.readString();
} public final static Creator<Contact> CREATOR = new Creator<Contact>() {
@Override
public Contact createFromParcel(Parcel source) {
return new Contact(source);
} @Override
public Contact[] newArray(int size) {
return new Contact[size];
}
}; public Contact(Parcel parcel) {
phoneNumber = parcel.readInt();
name = parcel.readString();
address = parcel.readString();
}
}

一个类只要实现了Parcelable接口,其对象就可以实现序列化并可以通过Intent和Binder传递。

  • Parcelable中的Parcel内部包含了可序列化的数据,可以在Binder中*传输。
  • 序列化功能:writeToParcel实现,最终是通过Parcel中的一系列write方法完成。
  • 反序列化:CREATOR完成,通过Parcel的一系列read方法来完成,内部表明了如何创建序列化对象和数组。
  • 内容描述:describeContents:仅当当前对象中存在文件描述符时返回1,其余所有情况返回0。
  • 反序列化过程需要传递当前线程的上下文类加载器,否则会报找不到类的错误。

Serializable和Parcelable的区别:

  • Serializable是java中的序列化接口,使用简单,但开销很大,序列化和反序列化过程需要大量IO操作。
  • Parcelable是Android中的接口,使用麻烦,但效率高,首选。
  • Parcelable主要适用于内存序列化上,但通过Parcelable将对象序列化到设备中或序列化后通过网络传输也可以,但稍微复杂,建议这种情况用Serializable。

Binder的使用及上层原理

  • Binder是Android中的一个类,实现了IBinder接口
  • 从IPC角度来说,Binder是一种跨进程通讯方式
  • 从android framework角度来说,Binder是ServiceManager链接各种Manager(ActvitiyManager、WindowManager等等)和相应ManagerService的桥梁;
  • 从android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService时,服务端会返回一个包含了服务端业务调用的Binder对象

AIDL中自动生成的Binder接口类的一些方法:

  1. DESCRIPTOR:Binder的唯一标识,一般用类名
  2. asInterface(IBinder obj):用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。(如果客户端和服务端位于同一进程,此方法返回的就是服务端的Stub对象本身,否则返回Stub.proxy)
  3. asBinder:返回当前的Binder对象
  4. onTransact:运行在服务端的Binder线程池中

Binder运行在服务端进程,如果服务端进程被异常终止,Binder链接就会断裂,导致我们远程调用失败。但是此时我们并不知道Binder链接已经中断,为了解决这个问题,Binder中提供了两个配对的方法:

  • linkToDeath:通过它可以给Binder设置一个死亡代理,当Binder死亡后我们就会收到通知。

如何设置Binder死亡代理:

首先声明一个DeathRecipeint对象,然后通过binder.linkToDeath()方法将其绑定到binder上。在DeathRecipeint内部有一个方法binderDied,当Binder死亡后,系统就会回调binderDied方法。

示例代码如下:

private ServiceConnection serviceConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIContactsManager = IContactsManager.Stub.asInterface(service);
Log.i(TAG, "onServiceConnected: mIContactsManager=" + mIContactsManager);
try {
// 给service设置死亡代理
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
...
}; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// 当binder挂掉后就会执行此方法
if (mIContactsManager == null) {
return;
}
// 首先移除之前绑定的死亡代理
mIContactsManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mIContactsManager = null; // 然后重新绑定远程服务
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
};

进程间通讯的基础知识就先介绍到这里,接下来将开始针对每种进程间通讯方式作出详细的介绍。


最后想说的是,本系列文章为博主对Android知识进行再次梳理,查缺补漏的学习过程,一方面是对自己遗忘的东西加以复习重新掌握,另一方面相信在重新学习的过程中定会有巨大的新收获,如果你也有跟我同样的想法,不妨关注我一起学习,互相探讨,共同进步!

参考文献:

  • 《Android开发艺术探索》

源码地址:本系列文章所对应的全部源码已同步至github,感兴趣的同学可以下载查看,结合代码看文章会更好。源码传送门

本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8479282.html