Android IPC(一)Serializable、Parce

2018-04-01  本文已影响110人  锋Plus

开启多进程

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

多进程产生的问题

  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是一致的,反序列化还是会失败,因为类的结构有毁灭性的改变,根本无法从来版本的数据中还原出一个新的类结构对象。

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

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中传输,在上面代码中,我们可以看出,序列化过程需要实现序列化、反序列化和内容描述。

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

Serializable和Parcelable的区别

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


Binder

Binder从不同角度上的定义:

Binder机制具体有两层含义:

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。
下面详细介绍针对这两个类的每个方法的定义:

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

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

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

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

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

说明一下:

  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是否死亡。


上一篇下一篇

猜你喜欢

热点阅读