美文网首页
Android IPC(一)Serializable、Parce

Android IPC(一)Serializable、Parce

作者: 锋Plus | 来源:发表于2018-04-01 14:58 被阅读110次

    开启多进程

    AndroidMenifest.xml中给四大组件指定android:peocess属性。

    • 进程名以:开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
    • 完整命名方式(全局进程),其他应用通过shareUID方式可以和它跑在同一进程中。

    多进程产生的问题

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

    IPC基础

    Serializable接口

    Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。

    想让一个对象实现序列化,只需要实现Serializable接口并指明一个serialVersionUID即可,实际上,甚至这个serialVersionUID也不是必须的,我们不需要声明这个UID同样也可以实现序列化,但是这将会对反序列化过程产生影响,具体什么影响后面在介绍。

        public class User implements Serializable {
            private static final long serialVersionUID = XXXXL;
            ....
        }
    

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

        //序列化过程
        User user=new User(0,"amao",true);
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("cache.txt"));
        out.writeObject(user);
        out.close();
    
        //反序列化
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("cache.txt"));
        User newUser=(User)in.readObject(user);
        in.close();
    

    serialVersionUID的意义

    UID的详细工作机制是这样的:序列化的时候系统会把当前类的UID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的UID,看它是否和当前类的UID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以反序列化成功;否则就说明当前类和序列化的类相比发生了某些变化,比如成员变量的数量,类型可能发生改变,这个时候无法正常序列化,回报UID不同的错误。

    一般来说,我们应该手动指定UID的值,这样在序列化和反序列化是两者的UID是相同,可以正常的进行反序列化。如果不手动指定UID,当你增加或删除某些成员变量的时候,那么系统会重新计算当前类的hash值并把它赋予给UID,这个时候当前的UID就和反序列化的UID就不同了,于是反序列化失败,程序crash掉了,但指定了就不会crash。当然,如果类的结构发生的改变,比如修改了类名,修改了成语变量的类型,这个时候尽管UID是一致的,反序列化还是会失败,因为类的结构有毁灭性的改变,根本无法从来版本的数据中还原出一个新的类结构对象。

    最后,以几两点需要特别提一下

    • 反序列化获取的对象与之前的对象数据完全一样,但是它们并不是同一个对象。
    • 静态成员变量属于类不属于对象,所以不参加序列化过程
    • 用transient关键字标记的成员变量不参与序列化过程。

    Parcelable接口

    Parcelable同样也是一个接口,通过Parcelable我们同样可以实现对象的序列化,而且远比Serializable高效,不够其过程就要复杂点了。


    Parcelable

    我们只要点击 Add Parcelable Implementation,AS会自动帮我们实现其方法,如下

    public class Person implements Parcelable {
    
        private int id;
        private String name;
        private boolean sex;
    
        private Class cls;
    
    
        protected Person(Parcel in) {
            id = in.readInt();
            name = in.readString();
            sex = in.readByte() != 0;
            cls = in.readParcelable(Class.class.getClassLoader());
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(id);
            dest.writeString(name);
            dest.writeByte((byte) (sex ? 1 : 0));
            dest.writeParcelable(cls, flags);
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        public static final Creator<Person> CREATOR = new Creator<Person>() {
            @Override
            public Person createFromParcel(Parcel in) {
                return new Person(in);
            }
    
            @Override
            public Person[] newArray(int size) {
                return new Person[size];
            }
        };
    }
    

    Parcel内部包装了可序列化的数据,可在Binder中传输,在上面代码中,我们可以看出,序列化过程需要实现序列化、反序列化和内容描述。

    • writeToParcel完成序列化 ,通过Parcel的一系列write方法完成;

    • CREATOR完成反序列化,内部标明了如何创建序列化对象和数组,通过Parcel的一系列read方法来完成反序列过程;

    • describeContents完成内容描述,几乎所有的情况下都返回0,仅当当前对象中存在文件描述符时,该方法返回1。

    • Persion(Parcel in)中,因为Class是另外一个可序列化的对象,所以它的反序列化过程需要传递当前线程的上下文加载器,否则会报无法找到类的错误。

    其实Android系统有很多实现了Parcelable接口的类,如Intent、Bundle、Bitmap等等,我们可以直接序列化它们,如果List和Map中的元素都是可序列化的,那List和Map也是可以序列化的。

    Serializable和Parcelable的区别

    最大的区别就是存储媒介的不同:
    Serializable使用IO读写存储在硬盘上;
    Parcelable是直接在内存中读写,内存的读写速度远大于IO读写,所以Parcelable高效。

    • 在使用内存时,Parcelable比Serializable性能高,推荐使用Parcelable;
    • Serializable在序列化时会产生大量的临时变量,会引起频繁的GC;
    • Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcleable不能很好的保证数据的持续性在外界有变化的情况下,尽管Serializable效率低点,也不提倡使用,但在这种情况下,还是建议使用Serializable。

    Binder

    Binder从不同角度上的定义:

    • 直观来说,Binder是Android中的一个类,它实现了IBinder接口;
    • 从IPC角度来说,Binder是Android中的一种跨进程通信方式,该通信方式在Linux中没有;
    • 从Android Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁;
    • 从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据了。

    Binder机制具体有两层含义:

    • Binder是一种跨进程通信(IPC,Inter-Process Communication)的手段;
    • Binder是一种远程过程调用(RPC,Remote Procedure Call)的手段。

    Binder主要使用在Service中,包括AIDL和Messenger(Messenger的底层其实是AIDL)。

    AIDL

    AIDL:Android Interface Definition Language,安卓接口定义语言。

    Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。

    通俗地讲,AIDL为我们提供一种快速实现Binder的工具。

    AIDL文件代码如下:

    // Book.aidl  
    package com.cooffee.studypro.aidl;  
      
    parcelable Book;  
    
    // IBookManager.aidl  
    package com.cooffee.studypro.aidl;  
      
    import com.cooffee.studypro.aidl.Book;  
    interface IBookManager {  
        List<Book> getBookList();  
        void addBook(in Book book);  
    }  
    

    系统自动帮我们生成IBookManager接口的java代码文件(gen目录下),代码结构如下:

    
    interface IBookManager extends IInterface {  
          
            abstract class Stub implements IBookManager {  
                asInterface() {...};  
                asBinder() {...};  
                onTransact() {...};  
                  
                abstract class Proxy implements IBookManager {  
                    asBinder() {...};  
                    getInterfaceDescriptor() {...};  
                    getBookList() {...};  
                    addBook() {...};  
                }  
            }  
          
        getBookList();  // 抽象方法  
        addBook();      // 抽象方法  
          
        // 两个方法标记ID的声明...  
    }  
    

    IBookManager接口的核心是它的内部类Stub和Stub的内部代理类Proxy。
    下面详细介绍针对这两个类的每个方法的定义:

    • DESCRIPTOR
      Binder的唯一标识,一般用当前Binder的类名标识,比如“com.example.aidl.IBookManager”。

    • asInterface(android.os.IBinder obj)将服务器端的Binder对象转换成客户端所需的AIDL接口类型的对象。

    该转换过程是区分进程的:

    如果客户端和服务端位于统一进程,此方法返回服务端的Stub对象本身;

    否则返回的是系统封装的Stub.proxy对象。

    • asBinder()用于返回当前Binder对象。

    • onTransact()此方法运行在服务器端的Binder线程池,当客户端发起跨进程请求时,远程请求会通过系统低层封装后交由次方法来处理。

    • Proxy#getBookList:

    此方法运行在客户端,当客户端远程调用此方法时,其内部实现如下:

    1. 创建该方法的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;
    2. 把该方法的参数信息写入_data(如果有参数);
    3. 调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;
    4. 服务器端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;
    5. 返回reply中的数据。
    • Proxy#addBook执行过程与getBookList过程相同,但是该方法没有返回值。

    说明一下:

    1. 当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是耗时的,不能在UI线程发起此远程请求

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

    Binder

    Binder死亡代理

    Binder运行在服务端进程中,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。而且我们不知道Binder连接是什么时候断裂的,那么客户端的功能会受到影响。

    Binder提供了两个配对的方法linkToDeathunlinkToDeath,通过linkToDeath我们可以为Binder设置一个死亡代理,当Binder死亡时,我们会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。

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

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {  
        @Override  
        public void binderDied() {  
            if (mBookManager == null)  
                return;  
            mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);  
            mBookManager = null;  
            // TODO: 重新绑定远程service  
        }  
    };  
    

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

    mService = IMessageBoxManager.Stub.asInterface(binder);  
    binder.linkToDeath(mDeathRecipient, 0);  
    

    其中linkToDeath的第二个参数是个标记位,我们直接设为0即可。

    另外,Binder的方法isBinderAlive也可以判断Binder是否死亡。


    相关文章

      网友评论

          本文标题:Android IPC(一)Serializable、Parce

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