美文网首页Android开发
Android进阶之IPC机制(一)

Android进阶之IPC机制(一)

作者: 不会游泳的金鱼_ | 来源:发表于2019-08-21 14:20 被阅读1次

    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<MyParcelable> CREATOR
                 = new Parcelable.Creator<MyParcelable>() {
             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机制(一)

        本文链接:https://www.haomeiwen.com/subject/daagqctx.html