美文网首页Android知识Android开发Android技术知识
深入理解Android-理解IPC 机制(2 - Seriali

深入理解Android-理解IPC 机制(2 - Seriali

作者: 289346a467da | 来源:发表于2017-07-16 16:09 被阅读512次

    原创 2017-7-16 代码

    56264e6e9cd7a (1).jpg

    技术前沿

    给大家推荐一个最近很火的下拉刷新库 SmartRefreshLayout 不止强大而且非常智能,实现了各种刷新动画,支持所有View,还支持多层嵌套的视图结构。

    来个段子压压惊

    一美女晚上下班回家,在路上遇上一醉酒汉便起了色心!美女连忙对酒汉说:我有艾滋病!大汉微微一笑:巧了,我也有,那我们就互相伤害吧!5分钟后,美女诡异一笑:你完了,我真的有艾滋,大汉顺间满脸惊恐,转而恢复平静,又对美女诡异一笑…

    思考

    在讲解多进程通信之前,我们需要理解IPC的基础概念即Serializable、Parcelable、Binder。Serializable和Parcelable 接口可以完成对象的序列化和持久化的过程,下面我们通过三个例子来分别理解。

    Serializable 理解

    Serializable 是Java 提供的序列化接口,它是一个空的接口,Serializable 实现也相当简单,我们通过一个例子来理解实现序列化和反序列化的过程。

    项目代码用的是上一篇文章的代码深入理解IPC机制(1 - 理解多进程)

    我们创建一个User 类,实现Serialilzable 接口

    public class User implements Serializable {
        public String userId;
        public String userNames;
        public String isMalss;
    
        public User(String userId, String userName, String isMale) {
            this.userId = userId;
            this.userNames = userName;
            this.isMalss = isMale;
        }
    }
    
    

    如何进行序列化呢?实现很简单,只需要采用ObjectOutputStream 和 ObjectInputStream,我们在MainActivity 的 onCreate 方法中进行序列化,看下面代码

        //序列化
            try {
                User user = new User("0", "jake", "1");
                ObjectOutput output = new ObjectOutputStream(new FileOutputStream(new File(getFilesDir(), "tours.txt")));
                output.writeObject(user);
                output.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    

    这就是序列化的整个过程,很简单就是实现了,只需要把User对象写到文件中。
    这里有一个小问题说一下,在创建文件的时候创建失败报错了,原因是我们需要给个写文件的权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    

    还有不能直接new FileOutputStream( "tours.txt") 这样回报错误:
    failed: EROFS (Read-only file system) when creating a File

    解决方案 已在 Stack Overflow 找到

    image.png

    代码一定要多敲,run 起来,看一百遍,不如写一遍。

    下面如何反序列化User对象,相信这个大家猜也能猜到,我们在Ipc1Activity 另一个进程中进行反序列化过程,看看能不能拿到User 对象的数据。

      //反序列化
            try {
                ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File(getFilesDir(), "tours.txt")));
                User newUser = (User) inputStream.readObject();
                Log.d("Ipc1Activity", "newUser:" + newUser.userNames);
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    

    我们运行一下项目,看一下log

    07-15 22:52:51.495 19548-19548/ipctest.linksu.com.androidipc:remote D/Ipc1Activity: newUser:jake
    

    完美,我们拿到了User对象的数据,需要注意的是MainActivity 和 Ipc1Activity 中User对象两者并不是同一个对象。

    实现serializable 类的时候会有一个标识 serialVersionUID,serialVersionUID 工作机制是这样的: 序列化的时候系统会把当前类的serialVersionUID写入序列化文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量。类型发生了改变,这个时候就无法正常序列化了。

    下面我们看一个例子,看看serialVersionUID 在序列化的过程中有什么作用。
    把User 类的成员变量的名字更改isMalss --> isMale

    public class User implements Serializable {
    
        public String userId;
        public String userNames;
        public String isMale;
    
        public User(String userId, String userName, String isMale) {
            this.userId = userId;
            this.userNames = userName;
            this.isMale = isMale;
        }
    }
    

    运行项目,看一下log,可以看到项目报错了,也就是说反序列化失败了。当我们改变一个成员变量时系统自动将serialVersionUID 的值改变了,导致反序列化失败

    java.io.InvalidClassException: ipctest.linksu.com.androidipc.User; Incompatible class (SUID): ipctest.linksu.com.androidipc.User: static final long serialVersionUID =-3787110665656653118L; but expected ipctest.linksu.com.androidipc.User: static final long serialVersionUID =2141326308829761030L;
    
    

    我们进行手动设置serialVersionUID

        private static final long serialVersionUID = 1314564;
    

    重新进行序列化,然后再改变一下成员变量,运行项目,看下log

    D/Ipc1Activity: newUser:jake
    

    ok,反序列化成功,从上述的例子中可以看出,如果不手动指定serialVersionUID 的值,反序列化时当前类有所改变,比如增加、删除、更改某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID ,serialVersionUID 不一致反序列化失败,serialVersionUID 的作用就很明显了,我们手动指定serialVersionUID 可以很大程度的避免反序列化失败,最大限度的回复数据。

    注 : 如何类的结构发生了改变,比如修改了类名、成员变量的类型,尽管指定了serialVersionUID 反序列化还是会失败的。

    Parcelale 理解

    Parcelale 是Android 中的序列化方式,它的效率很高,缺点就是使用起来稍微麻烦。Serializable 是Java 中的序列化接口,使用起来简单但是开销很大,序列化和反序列化过程需要大量的I/O操作。

    package ipctest.linksu.com.androidipc;
    
    import android.os.Parcel;
    import android.os.Parcelable;
    
    /**
     * Created by 17604 on 2017/7/16.
     */
    
    public class UserPar implements Parcelable {
        public String userId;
        public String userNames;
        public String isMalss;
    //    public Info info;
    
        protected UserPar(String userId, String userNames, String isMalss) {
            this.userId = userId;
            this.userNames = userNames;
            this.isMalss = isMalss;
        }
    
        private UserPar(Parcel in){
            userId = in.readString();
            userNames = in.readString();
            isMalss = in.readString();
        }
    
        public static final Creator<UserPar> CREATOR = new Creator<UserPar>() {
            @Override
            public UserPar createFromParcel(Parcel in) {
                return new UserPar(in);
            }
    
            @Override
            public UserPar[] newArray(int size) {
                return new UserPar[size];
            }
        };
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(userId);
            dest.writeString(userNames);
            dest.writeString(isMalss);
    //        dest.writeParcelable(info, 0);
        }
    }
    
    

    如上述代码,UserPar 类实现了Parcelable 接口,就可以实现序列化,通过Intent 和 Binder 传递。

    Parcelable 的方法说明:

    1. UserPar(Parcel in)从序列化后的对象中创建原始对象。
    2. describeContents() 返回当前对象的内容描述。如果含有文件扫描符,返回 1,否则返回0.几乎所有情况都返回0。
    3. writeToParcel(Parcel dest, int flags) 将当前对象写入序列化结构中,其中flags 为1 是标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0.
    4. createFromParcel(Parcel in) 从序列化后的对象中创建原始对象。
    5. newArray(int size) 创建指定长度的原始对象数组。

    下面我们在介绍下Parcel

    Parcle 内部包装了可序列化的数据,可以在Binder 中自由传输。从代码中可以看出序列化是writeToParcel 完成的 dest.writeString ;反序列化功能是通过CREATOR完成的read方法。

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

    推荐 ![好用的翻墙工具] (https://my.yizhihongxing.com/aff.php?aff=6247)

    参考:Android 开发艺术探索


    198750-106.jpg

    相关文章

      网友评论

        本文标题:深入理解Android-理解IPC 机制(2 - Seriali

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