Android Serializable、Parcelable和Binder的使用、理解

IPC for Android 系列:
1、IPC for Android
2、本文Serializable、Parcelable、Binder的使用、理解
3、IPC bindService传递Messenger
4、结合PMS源码讲解AIDLPackageManagerService服务框架详解

进程间通信的时候,把一个类的实例对象发送到另外一个进程的时候,就需要对这个类进行序列化和反序列化。实例对象序列化后就可以传输到另外一个进程中。再反序列化就可以得到一个一模一样类的实例对象了。
Serializable和Parcelable都是序列化接口,Binder则是进程间通信的一种工具。以下将详细说明。

Serializable

Serializable是java提供的序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化。
实现方法:
1、继承Serializable接口
2、声明serialVersionUID
demo:
声明一个book类继承Serializable接口,并声明serialVersionUID

public class Book implements Serializable {
    private static final long serialVersionUID =354615249875612456L;

    public String name;

    public Book (String name) {
        this.name = name;
    }

    public String getBookName() {
        return this.name;
    }
}

它的序列化和饭序列化操作也比较简单,只需要用到ObjectOutputStream和ObjectInputStream就可以了

try {
    Book aaa = new Book("aaa");
    ObjectOutputStream outputStream = new ObjectOutputStream(openFileOutput("book.txt", Context.MODE_APPEND));
    outputStream.writeObject(aaa);
    outputStream.close();
} catch (IOException e) {
    e.printStackTrace();
}

try {
    ObjectInputStream inputStream = new ObjectInputStream(openFileInput("book.txt"));
    Book otherAaa = (Book) inputStream.readObject();
    if(otherAaa != null) {
        Log.d("yink","book name = " + otherAaa.getBookName());
    } else {
        Log.d("yink","book is null");
    }
    inputStream.close();
} catch (Exception e) {
    e.printStackTrace();
}

运行结果

04-27 11:20:55.305 15328 15328 D yink    : book name = aaa

重点!!
反序列化得到的otherAaa这个实例对象,和aaa不是同一个,等于新创建的一个Book类。任何序列化反序列化都是这样。不能得到原有的类,只是新创建了一个一模一样的类的实例对象。

这里讲一下声明serialVersionUID的作用 :
在反序列化的时候,系统会对比serialVersionUID这个值,若相同则反序列化成功。
比如Book这个类,如果我不指定serialVersionUID,系统会给它一个默认值,假设系统给的serialVersionUID = 1L;那么序列化的时候serialVersionUID被保存起来,在book.txt保存的序列化文件里serialVersionUID被保存的值就是1L。
当我修改了Book这个类,比如增加了一个变量,系统会重新给serialVersionUID赋值,假设系统给的serialVersionUID = 2L; 这个时候Book类里面的serialVersionUID的值就是2L,当反序列化的时候,系统就会对比serialVersionUID,由于保存的serialVersionUID=1L,反序列化就不能成功了,所以需要制定serialVersionUID这个值

Parcelable

实现Serializable接口后,通过I/O操作完成了序列化和反序列化过程。
实现Parcelable接口后,也是通过通过I/O操作来序列化。
他两的区别就是Parcelable针对安卓优化过,传输更有效率
实现方法:
1、继承Parcelable接口
2、实现Parcelable中的方法
demo:

public class Book implements Parcelable {

    public String name;
    public int page;

    public Book (String name, int page) {
        this.name = name;
        this.page = page;
    }

    public String getBookName() {
        return this.name;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(page);
    }

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

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

    protected Book(Parcel in) {
        name = in.readString();
        page = in.readInt();
    }
}

Parcel:这个类内部包装了可序列化数据,可在binder中自由传输
主要实现三个方法:
1、writeToParcel,完成序列化,通过parcel的write完成
2、CREATOR,反序列化,其中createFroParcel创建一个新的反序列化对象book,并通过parcel的read方法,把数据赋给新的对象Book并返回(对象就反序列化了)。newArray作用为创建指定长度的原始对象数组
3、describeContents,内容描述功能,几乎所有情况都返回0,为1表示当前对象需要作为返回值返回,不能立即释放资源。

Android中有很多类已经实现了Parcelable接口,如Intent,Bundle,Bitmap等,List和Map也支持(需要里边每个元素可序列化)

两个接口比较:
Serializable,java提供的接口,使用简单开销大,需要大量I/O操作。适合用于将对象存储在设备中,或通过网络传输。
Parcelable,android平台提供并推荐使用方法,使用稍麻烦,效率高。主要用在内存的序列化上。

binder

手动实现一个Binder,注意本文目的是理解aidl是怎样通过aidl传输消息的。不是讲解如何写aidl通信。
1、声明一个AIDL性质接口;
2、实现代理类Proxy;

我们先声明一个IBookManager接口,AIDL性质接口只需继承IInterface。

public interface IBookManager extends IInterface{
    static final String DESCRIPTOR = "com.example.android.myapplication.IBookManager";
    static final int TRANSACTION_getBookList = IBinder.FIRST_CALL_TRANSACTION + 0;
    static final int TRANSACTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 1;
    public List<Book> getBookList() throws RemoteException;
    public void addBook(Book book) throws RemoteException;
}

有多少个方法就需要声明多少个id;
DESCRIPTOR,binder的唯一标识,一般用当前binder的类名表示,这里是在接口里定义了;

然后书写Proxy代理类:(也可省略第一步,直接继承IInterface,统一写到BookManagerImpl里)

public class BookManagerImpl extends Binder implements IBookManager {

    public BookManagerImpl() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public static IBookManager asInterface(IBinder obj) {
        if(obj == null) {
            return  null;
        }
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if ((iin != null) && (iin instanceof IBookManager)) {
            return (IBookManager)iin;
        }
        return new BookManagerImpl.Proxy(obj);
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case TRANSACTION_getBookList:
                data.enforceInterface(DESCRIPTOR);
                List<Book> result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(result);
                return true;
            case TRANSACTION_addBook:
                data.enforceInterface(DESCRIPTOR);
                Book arg0;
                if (data.readInt() != 0) {
                    arg0 = Book.CREATOR.createFromParcel(data);
                } else {
                    arg0 = null;
                }
                this.addBook(arg0);
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }

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

    @Override
    public List<Book> getBookList() throws RemoteException {
        return null;
    }

    @Override
    public void addBook(Book book) throws RemoteException {

    }

    private static class Proxy implements IBookManager {
        private IBinder mRemote;

        Proxy(IBinder remote) {
            this.mRemote = remote;
        }

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

        public  String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            List<Book> result;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_getBookList, data, reply, 0);
                reply.readException();
                result = reply.createTypedArrayList(Book.CREATOR);
            } finally {
                reply.recycle();
                data.recycle();
            }
            return result;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                if(book != null) {
                    data.writeInt(1);
                    book.writeToParcel(data, 0);
                } else {
                    data.writeInt(0);
                }
                mRemote.transact(TRANSACTION_addBook, data, reply, 0);
                reply.readException();
            } finally {

            }
        }
    }
}

BookManagerImpl服务端代码,为客户端提供通信方法
分别说下各个方法功能:
asInterface
客户端调用此方法,区分进程,若客户端和服务端同一进程,返回IBookManager本身,若不是同一进程,返回代理类Proxy;
asBinder
返回当前binder对象,即BookManagerImpl
onTransact
此方法运行在服务端Binder线池中,当客户端请求,通过系统底层封装,调用到onTransact(code, data, reply, flags)此方法;
然后执行switch(code),通过之前定义的方法id,去定方法,data传递需要的数据,reply被写入返回值;
此方法若返回false,客户端调用会失败,可以此做一些权限验证等;
getBookList和addBook
函数里什么都没写,只是为了继承IBookManager而写,无实际用处;

Proxy
代理类,用来返回给客户端,这个类关键作用就是实现了客户端对服务端的调用
Proxy#getBookList
1、定义Pacel对象data、replay,List<Book>对象result
2、data中写入binder标识DESCRIPTOR
3、调用transact(调用后,客户端线程挂起,等待调用到服务端onTransact,并执行完毕后返回)
4、reply取出返回结果readException
5、reply取出返回数据
Proxy#addBook
和Proxy#getBookList基本一样,只是没有第五步,因为没有返回值

到此,Binder的使用,调用流程就很明朗了。
初步理解Binder后,建议阅读PackageManagerService服务框架详解对AIDL深一步理解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容