IPC 机制(二)之 IPC 基础概念介绍

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sinat_29874521/article/details/82495132

本文主要介绍一些 IPC 中的一些基础概念,主要包含三方面内容:Serializable 接口、Parcelable 接口以及 Binder,只有熟悉这三方面内容后,我们才能更好的理解跨进程通信的各种方式。Serializable 和 Parcelable 接口可以完成对象的序列化过程,当我们需要通过 Intent 和 Binder 传输数据时需要使用 Serializable 或者 Parcelable。还有的时候我们需要把对象持久化到设备上或者通过网络传输给其他客户端, 这个时候也需要使用 Serializable 来完成对象的持久化,下面先介绍如何使用 Serializable 来完成对象的序列化。

1. Serializable 接口

Serializable 是 Java 提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用 Serializable 来实现序列化非常简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。

    private static final long serialVersionUID = 3576197963790100727L;

上面提到,想让一个对象实现序列化,只需要这个类实现一个 Serializable 接口并声明一个 serialVersionUID 即可,实际上,甚至 serialVersionUID 也不是必需的,我们不声明 serialVersionUID 同样也可以实现序列化,但是这将对反序列化的过程产生影响,具体什么影响后边会介绍。我们先来说下如何实现序列化和反序列化,UserBean 类就是一个实现了 Serializable 接口的类,如下所示:

public class UserBean implements Serializable {

    private static final long serialVersionUID = 3576197963790100727L;

    private String name;
    private String sex;
    private int age;
    
    ...
}

通过 Serializable 的方式来实现对象的序列化,实现起来非常简单,几乎所有工作都被系统完成了,如果进行对象的序列化和反序列化也非常简单,只需要采用 ObjectOutputStream 和 ObjectInputStream 即可轻松实现,代码如下:

        //序列化
        UserBean userBean = new UserBean("小明", "男", 18);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("text.txt"));
        out.writeObject(userBean);
        out.close();

        //反序列化
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("text.txt"));
        UserBean user = (UserBean) in.readObject();
        in.close();

上述代码演示了采用 Serializable 方式序列化对象的典型过程,很简单,只需要把实现了 Serializable 接口 UserBean 对象写到文件中就可以快速恢复了,恢复后的对象 user 和 userBean 的内容完全一样,但是两者不是一个对象。

下边我们来回答上面的问题,serialVersionUID 到底有啥用?为什么说不使用 serialVersionUID 会导致反序列化过程出现问题?serialVersionUID 其实是用来辅助序列化和反序列化过程的。原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能正常的被反序列化。序列化的时候系统会把当前类的 serialVersionUID 写入到序列化的文件中(也可能是其他的中介),当反序列化的时候系统会检测文件中的 serialVersionUID,看它是否和当前类的 serialVersionUID 一致,如果一致说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功饭序列化;否则说明当前类和序列化的类反升了某些改变,比如成员变量的数量、类型发生了改变,这个时候是无法正常反序列化的。

一般来说,我们应该手动指定 serialVersionUID的值,比如 1L,也可以让 Android Studio 根据当前类的结构自动生成它的 hash 值,这样序列化和反序列化时两者的 serialVersionUID 值是形同的,因此可以正常进行反序列化。如果不指定 serialVersionUID 的值,反序列化时当前类发生癌变,比如增加或者减少了某些成员变量,那么系统会重新计算当前类的 hash 值并把它赋值给 serialVersionUID,这个时候当前类的 serialVersionUID 就和序列化的数据中的 serialVersionUID 不一致,造成反序列化失败,程序就会出现crash。当我们手动指定了 serialVersionUID 以后,就可以在很大程度上避免了反序列化失败。当然我们还需要考虑一种情况,如果类结构发生了非常规性改变,比如改变了类名,修改了成员变量的类型,这个时候尽管 serialVersionUID 验证通过了,但是反序列化过程还是会失败,因为类结构雷友毁灭性的改变,根本无法从老把那本的数据中还原一个新的类结构的对象。

根据上面的分析,我们已经对 serialVersionUID 有了了解,不过以下两点需要提一下,

(1)静态尘缘变量属于类,不属于对象,所以不会参与序列化过程。

(2)用 transient 关键字标记的成员变量不参与序列化过程。

另外,系统序列化过程是可以改变的,通过实现如下两个方法即可重写系统默认的序列化和反序列化过程,具体怎么去重写这两个方法就是很简单的事情了,这里就不在过多介绍了

    public final void writeObject(Object obj) throws IOException {

    }

    public final Object readObject() throws IOException, ClassNotFoundException {

    }

2 .Parcelable 接口

Parcelable 也是一个接口,只要实现这个接口,一个类的对象就可以实例化并可以通过 Intent 和 Binder 传递。下面的示例是一个典型的用法:

public class UserBean implements Parcelable {

    private String name;
    private String sex;
    private int age;

    private Book book;

    public UserBean(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    protected UserBean(Parcel in) {
        name = in.readString();
        sex = in.readString();
        age = in.readInt();
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }

    public static final Creator<UserBean> CREATOR = new Creator<UserBean>() {
        @Override
        public UserBean createFromParcel(Parcel in) {
            return new UserBean(in);
        }

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

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeString(sex);
        dest.writeInt(age);
        dest.writeParcelable(book, flags);
    }
}

这里先说下 Parcel,Parcel 内部包装了可序列化的数据,可以在 Binder 中自由传输,从上述代码可以看出,在序列化过程中需要实现的有序列化、反序列化和客观描述。序列化功能由 writeToParcel 方法来3完成,最终通过 Parcel 中的一些列 write 方法来完成;反序列化功能由 CREATOR 来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程;内容描述由 describeContents 方法来完成,几乎在所有情况下这个方法都返回 0,仅当当前对象中存在文件描述符时,此方法返回 1。需要注意的是,在 UserBean(Parcel in) 方法中,由于 book 是一个可视化的对象,所以它的反序列化过程需要传递当前线程上下文类加载器,否则会报无法找到累的错误。详细方法说明请参考下图:

Parcelable 的方法说明
              方法                                         功能                           标记位
createFromParcel(Parcel in)

从序列化的对象中创建原始对象

 
newArray(int size)

创建指定长度的原始对象数组

 
UserBean(Parcel in

从序列化后的对象中创建原始对象

 
writeToParcel(Parcel dest, int flags)

将当前对象写入序列化结构中,其中 flag 标识有两种值:0 或 1(参见右侧标记位),为 1 的时候,当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0。

PARCELABLE_WRITE_RETURN_VALUE
describeContents

返回当前对象的内容描述,如果含有文件描述符,返回 1(参见右侧标记位),否则返回 0,几乎所有情况都返回 0。

0C 0ONTENTS_FILE_DESCRIPTOR

系统已经为我们提供了许多实现了 Parcelable 接口的类,他们都可以直接序列化的,比如 Intent、Bundle、Bitmap 等,同时 List 和 Map 也可以序列化,前提是他们里边的每个元素都是可序列化的。

既然 Parcelable 和 Serializable 都能实现序列化并且都可用于 Intent 间的数据传递,那么我们来说说二者的区别:Serializable 是 Java 中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量的 I/O 操作。而 Parcelable 是 Android 中的序列化方式,因此更适合 Android 平台使用,它的缺点就是使用起来稍微麻烦点,但是它的效率很高,这是 Android 推荐的序列化方式,因此我们要首选 Parcelable。Parcelable 主要用在内存序列化上,通过 Parcelable 将对象序列化到存储设备或者将对象序列化后通过网络传输也都是可以的,但是这个过程稍显复杂,因此在这两种情况下建议大家使用 Serializable。

3. Binder

Binder是一个很深入的话题,这里不打算深入探讨 Binder 的底层细节,因为 Binder 太复杂了。这里的侧重点只介绍 Binder 的使用和上层原理,为接下来的几节做铺垫。

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

Android 开发中,Binder 主要用在 Service 中,包括 AIDL 和 Mesenger,其中普通的 Service 中的 Binder 不涉及进程间通信,所以较为简单,无法触及 Binder 的核心,而 Mesenger 的底层其实是AIDL,所以这里选择用 AIDL 来分析 Binder 的工作机制。为了分析 Binder 的工作机制,我们需要搭建一个 AIDL 示例,SDK会自动为我们生产 AIDL 所对应的 Binder 类,然后我们就可以分析 Binder  的工作过程了。新建 Java 包 com.demo.text.demotext.aidl,然后创建三个文件 Book.java,Book.aidl 和 IBookManager.aidl。其中 Book.aidl 和 IBookManager.aidl 需要在 main 文件夹下创建一个 aidl 文件夹,并创建一个 com.demo.text.demotext.aidl 包,将这两个文件放入该包中,代码如下:

public class Book implements Parcelable {

    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.bookName);
        dest.writeInt(this.bookId);
    }

    public Book() {
    }

    protected Book(Parcel in) {
        this.bookName = in.readString();
        this.bookId = in.readInt();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}
package com.demo.text.demotext.aidl;

parcelable Book;
package com.demo.text.demotext.aidl;

import com.demo.text.demotext.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

上面三个文件中,Book.java 是一个表示图书信息的类,Book.aidl 是 Book 类 在AIDL 中的声明,IBookManager.aidl 是我们定义的一个接口,里边有两个方法,其中 getBookList 用于从远程服务端获取图书列表,而 addBook 用于往图书列表中添加一本书,当然这两个方法主要是示例用。下面我们先看下系统为 IBookManager.aidl 生成的 Binder 类,在 build ——> generated ——> source ——> aidl ——> debug 文件夹下,这就是我们要找的类,接下来我们根据系统生成的 Binder 类来分析 Binder 的工作原理。代码如下:

package com.demo.text.demotext.aidl;

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.demo.text.demotext.aidl.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.demo.text.demotext.aidl.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.demo.text.demotext.aidl.IBookManager interface,
         * generating a proxy if needed.
         *
         * 将服务端的 Binder 对象转化成客户端所需的 AIDL 接口类型的对象
         */
        public static com.demo.text.demotext.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.demo.text.demotext.aidl.IBookManager))) {
                //同一进程
                return ((com.demo.text.demotext.aidl.IBookManager) iin);
            }
            return new com.demo.text.demotext.aidl.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        /**
         * 运行在服务端
         * 这个方法运行在服务端的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层交由此方法来处理
         * 服务端通过 code 可以知道客户端所请求的目标方法是什么,接着从 data 中取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕后,就像 reply 中写入返回值
         *
         * @param code
         * @param data
         * @param reply
         * @param flags
         * @return
         * @throws android.os.RemoteException
         */
        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.demo.text.demotext.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.demo.text.demotext.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.demo.text.demotext.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.demo.text.demotext.aidl.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            /**
             * 这个方法运行在客户端
             *
             * @return
             * @throws android.os.RemoteException
             */
            @Override
            public java.util.List<com.demo.text.demotext.aidl.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.demo.text.demotext.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.demo.text.demotext.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.demo.text.demotext.aidl.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public java.util.List<com.demo.text.demotext.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.demo.text.demotext.aidl.Book book) throws android.os.RemoteException;

}

我们可以看到根据 IBookManager.aidl 系统为我们生成了 IBookManager.java这个类,它继承了 IInterface 接口同时它自己也还是个接口,所有可以在 Binder 中传输的接口都需要继承 IInterface 接口。通过它我们可以清楚地了解 Binder 的工作机制。这个类的结构其实很简单,首先,他声明了两个方法 getBookList 和 addBook,显然这就是我们在 IBookManager.aidl 中声明的两个方法,同时它还声明了两个整形的 id 分别用于标识这两个方法,这两个 id 用于标识 transac t过程中客户端所请求的到底是那个方法。接着,它生命了一个内部类 Stub,这个 Stub 就是一个 Binder 类,当客户端和服务端都位于同一个进程的时候,方法调用不会走跨进程的 transact 过程,而当两者位于不同进程时,方法调用需要走 transact 过程,这个逻辑由 Stub 的内部代理类 Proxy 来完成,这么来看,IBookManager 这个接口的逻辑很简单,但是我们也应该认识到,这个接口的核心实现就是他的内部类 Stub 和 Stub 的内部类代理类 Proxy,下边来详细介绍下对这两个类的每个方法的含义:

(1) DESCRIPTOR

Binder的唯一标识,一般用当前Binder的类名表示。

(2) asInterface(android.os.IBinder obj)

用于服务端的 Binder 对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一个进程,那么此方法返回的就是服务端的 Stub 本身,否则返回的就是 Stub.Proxy.

(3) asBinder

此方法返回当前 Binder 对象。

(4) onTransact

这个方法运行在服务端中的 Binder 线程池中,当客户端发送跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法原型为 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端通过 code 可以确定客户端所请求的目标方法是什么,接着从 data 中取出目标方法所需要的参数(如果目标方法有参数),然后执行目标方法。当目标方法执行完毕后,就会向 reply 中写入返回值(如果目标方法有返回值),onTransact 方法的执行过程就是这样。需要注意的是,如果此方法返回 false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限风险验证,毕竟我们不希望随便一个进程都能远程调用我们的服务。

(5) Proxy#getBookList

这个方法运行在客户端,当客户端远程调用次方法时,他的内部实现是这样的:首先创建该方法所需要的输入型 Parcel 对象  _data,输出型 Parcel 对象 _reply 和返回值对象 List,然后把该方法的参数信息写入 _data 中(如果有参数),接着调用transact 方法来完成 RPC(远程过程调用)请求,同时当前线程挂起,然后服务端的 onTransact 方法会被调用,直到 RPC 过程返回后,与当前线程继续执行,并从  _reply 取出 RPC 过程的返回结果,最后返回 _reply 中的数据。

(6) Proxy#addBook

这个方法运行在客户端,他的还行过程和 getBookList 是一样的,addBook 没有返回值,所以它不需要从 _reply 中去除返回值、

通过上边的分析,应该对 Binder 的工作机制有了了解,但是有两点我们还需要注意下:

(1) 当客户端发送请求向服务端请求数据的时候,由于当前线程会被挂起知道服务端返回数据,所以这个过程是非常耗时的,那么就不能在UI线程中请求;

(2) 由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方法实现,因为它已经运行在一个线程中了。

为了更方便的说明 Binder,下面给出一个 Binder 的工作机制图,如下所示:

linkToDeath 和 unlinkToDeath

接下来,我们介绍 Binder 的两个重要的方法 linkToDeath 和 unlinkToDeath。我们知道,Binder 运行在服务端的进程中,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的 Binder 链接断裂(称之为 Binder 死亡),会导致我们的远程调用失败,更为关键的是,如果我们不知道 Binder 链接已经断裂,那么客户端的功能就会受到影响,为了解决这个问题,Binder 中提供了两个被对的方法 linkToDeath 和 unlinkToDeath,通过linkToDeath 我们可以给 Binder 设置一个死亡代理,当 Binder 死亡的时候,我们就会收到通知,这个时候我们就可以重新发起链接请求从而回复链接,那么到底如何给 Binder 设置死亡代理呢?也很简单。

首先我们声明一个 DeathRecipient 对象,DeathRecipient 是一个接口,其内部只有一个方法 binderDied,我们需要实现这个方法,当 Binder 死亡的时候,系统就会调用 binderDied 方法,然后我们就可以移出之前绑定的 Binder 代理并从新绑定远程服务。

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(null == iBookManager) {
                return;
            }
            iBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
            iBookManager = null;
        }
    };

其次,在客户端绑定远程服务成功之后,给 Binder 设置死亡代理。

  private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = IBookManager.Stub.asInterface(service);
            try {
                iBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iBookManager = null;
        }
    };

其中 linkToDeath 的第二个参数是个标记位,我们直接设为 0 即可,经过上面两个步骤,就给我们 Binder 设置课死亡代理,当 Binder 死亡的时候我们就可以收到通知了,另外,通过 Binder 的方法 isBinderAlive 也可以判断 Binder 是否死亡。

猜你喜欢

转载自blog.csdn.net/sinat_29874521/article/details/82495132
IPC