Android进阶之IPC机制(一)

IPC是Inter-Proess Communication的缩写,意思是跨进程通信,即两个进程之间进行数据交换的过程。今天我们就来聊聊Android中的IPC机制。

IPC基础

多进程

进程和线程

线程是CPU调度的最小单元,是一种有限的系统资源。而进程则一般指一个执行单元,在PC和移动设备上指一个程序或应用。

一个进程可以包含多个线程,在Android中,主线程为UI线程,只有在UI线程中才能操作界面元素。

进程和线程之间的关系其实就相当于公司和部门之间的关系一样。一个进程就相当于一个公司,线程就相当于公司里的部门,各司其职,可以并行工作。最小的公司可能只有一个部门,即最基础的进程只有一个线程。至于多进程也很好理解,有些公司很庞大,下属又有好几个小公司,就像一个庞大的应用可能会有多个进程一样。

开启多进程模式的方式

常用方法为在AndroidMenifest中给四大组件指定android:process属性。(用JNI在native层fork一个新的进程也可以实现,但不属于常规方法)

有两种效果不同的命名方法:

  1. 省略包名,如android:process=":remote",表示进程名为com.example.myapplication:remote。属于当前应用的私有进程,其他进程的组件不能和他跑在同一进程中。
  2. 完整命名的进程,如android:process="com.example.myapplication.remote"。属于全局进程,其他应用可以通过ShareUID方式和他跑在用一个进程中。

关于UID

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

两个应用通过ShareUID跑在同一进程的条件:ShareUID相同且签名也相同。

满足上述条件的两个应用,无论是否跑在同一进程,它们可共享data目录,组件信息。

若跑在同一进程,它们除了可共享data目录、组件信息,还可共享内存数据。它们就像是一个应用的两个部分。

多进程产生的问题

  1. 静态成员和单例模式失效;
  2. 线程同步机制失效;
  3. SharePreferences的可靠性下降;
  4. Application会重复创建。

不同的进程就是不同的JVM虚拟机,那么就会产生问题1和问题2。由于SharePreferences底层是基于XML文件的读写实现的,那么并发读写肯定很容易出问题,即问题3。Android系统会为新的进程分配独立虚拟机,相当于系统又把这个应用重新启动了一次,于是就出现了问题4。

序列化

进程中通信会涉及到序列化的相关内容,序列化表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。

两种类型的变量不会参与序列化:

  1. 静态成员变量属于类,不属于对象。
  2. 用transient关键字标记的成员变量。

Java原生的序列化接口为 Serializable ,Android独有的序列化接口为 Parcelable。它们之间的区别是什么呢?

Serializable的作用是为了保存对象的属性到本地文件、数据库、网络流以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的。而Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体。

它们的优劣如何?

Parcelable的性能比Serializable好,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如activity间传输数据,而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable,因为android不同版本Parcelable可能不同,所以不推荐使用Parcelable进行数据持久化。

Serializable

Serializable是Java提供的序列化接口,使用起来很简单,只需要在类的声明中指定一个serialVersionUID即可(不指定也行),代码如下:

public class Person implements Serializable {

    private static final long serialVersionUID = 44260788630571004L;
    
    public int id;
    public String name;
    
    ...
}

serialVersionUID是用于在反序列化时进行校验,相当于是一个版号的作用,如果serialVersionUID不同,说明类可能被改动过了,没法反序列化。

序列化的过程都由系统自动完成了。我们只需使用ObjectOutputStream和ObjectInputStream即可完成序列化和反序列化的操作。代码如下:

public static void main(String[] args) {
        Person person = new Person(1,"Jerry");
        try {
            //序列化
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cache.txt"));
            outputStream.writeObject(person);
            outputStream.close();
            
            //反序列化
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cache.txt"));
            Person myPerson = (Person) inputStream.readObject();
            inputStream.close();
            
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    

Parcelable

Parcelable接口是Android根据应用场景(多进程通信)提供的一种序列化方式。只要实现这个接口,一个类的对象就可以实现序列化并通过Intent和Binder传递。

相对Serializable,Parcelable就要复杂一些,需要实现writeToParcel、describeContents函数以及静态的CREATOR变量,实际上就是将如何打包和解包的工作自己来定义,而序列化的这些操作完全由底层实现。

public class MyParcelable implements Parcelable {
     private int mData;
     private String mStr;

     public int describeContents() {
         return 0;
     }

     // 写数据进行保存
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mData);
         out.writeString(mStr);
     }

     // 用来创建自定义的Parcelable的对象
     public static final Parcelable.Creator CREATOR
             = new Parcelable.Creator() {
         public MyParcelable createFromParcel(Parcel in) {
             return new MyParcelable(in);
         }

         public MyParcelable[] newArray(int size) {
             return new MyParcelable[size];
         }
     };

     // 读数据进行恢复
     private MyParcelable(Parcel in) {
         mData = in.readInt();
         mStr = in.readString();
     }
 }

由于Parcelable针对android平台进行过优化,效率要高于Serializable,缺点就是使用起来麻烦一些。

Binder

概念

  1. 从API角度:是一个类,实现IBinder接口。
  2. 从IPC角度:是Android中的一种跨进程通信方式。
  3. 从Framework角度:是ServiceManager连接各种Manager和相应ManagerService的桥梁。
  4. 从应用层:是客户端和服务端进行通信的媒介。客户端通过它可获取服务端提供的服务或者数据。

Android是基于Linux内核基础上设计的,大家都知道,Linux内核有多种进程间通信的方式:管道/消息队列/共享内存/Socket等,为啥还要搞个Binder 出来呢?

性能方面:

Binder相对于传统的Socket方式,更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式又比较复杂。

安全方面:

Binder机制从协议本身就支持对通信双方做身份校检,从而大大提升了安全性。

一些理解

感觉网上好多大佬都讲过Binder机制,长篇大论,内容不可谓不细致,不过对于新手而言有点难以理解,我一开始看的时候就感觉云里雾里的。由于细致的讲解网上到处都是,我这种菜鸟也不可能讲的更好,所以下面我还是来谈谈我粗浅的认识吧。

首先是Binder机制的四个组件

Client、Server、ServiceManager以及Binder驱动。

Binder通信采用C/S架构,那么一开始的问题就是哪个是客户端哪个是服务端呢?

答案是不一定。乍一看,Client、Server一个客户端一个服务端,似乎挺合理的,但其实在Binder机制中Server有可能也是客户端,而服务端是ServiceManager。

至于Binder驱动,则可以看成是一个连接客户端和服务端的桥梁,相当于一个虚拟的硬件设备。

进程间通信其实就是交互数据对吧,那么Client就是要获取数据的进程,Server就是提供数据的进程。

我的理解是:Binder 机制就是 Client 和 Server 在 ServiceManager 进程的管理下,通过Binder驱动进行通信。

先从ServiceManager说起

ServiceManager 是系统级的服务,单独运行在一个进程中,从字面意思来看,它是用来管理 Service 的。Service 就是 Server 提供的服务。那么它是怎么管理的呢?

首先,假设如果让我们自己管理一堆服务我们会怎么办呢?

直接用服务的名字么?大部分人应该会选择给每个服务分配一个独一无二的ID吧,其实 ServiceManager 也是这么做的。

只不过ServiceManager用的是句柄。句柄是啥?英文Handle,简单点说就是一个整数值,可以看作是 service 的ID。

ServiceManager录了所有系统service所对应的Binder句柄,它的核心功能就是维护好这些句柄值。因此对于Server来说,在ServiceManager中注册就是创建自己的句柄值,而Client查询也是查询目标服务的句柄值。通过句柄值,Client 就可以获取对应的 Service 的Binder 代理,从而完成通信。

那么,之前提到 ServiceManager 也是一个单独的进程,那么其他进程是如何获取它的句柄值的呢?

ServiceManager 本身的句柄值固定为0,即handle=0。因此,无论是Client,还是 Server 都可以通过这个固定值直接获得 ServiceManager 的Binder代理,从而和ServiceManager进行通信。

另外,句柄值都是ServiceManager 维护的,不同进程中指代相同Binder实体的句柄值可能是不同的,即A进程拿到的句柄值和B进程拿到的句柄值可能不同,但指代的是同一个 Serivce。

Binder 机制的流程

  1. Android系统启动时会启动,ServiceManager进程。ServiceManager 注册自己的handle值为0。
  2. ServiceManager在死循环中,不停地去读内核中binder driver,看是否有新的请求(注册Service或查询Service)
  3. Server 创建一个handle为0的代理binder,向ServiceManager注册自己。
  4. Server 进入无限循环,不停地去读内核中binder driver的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回。
  5. Client 创建一个handle为0的代理binder,向ServiceManager 查询Service 的handle值。
  6. Client 通过请求到的 handle值,获得该service 的代理binder ,通过代理binder调用service的方法。

你可能感兴趣的:(Android进阶之IPC机制(一))