Android知识Android开发Android技术知识

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

2017-07-16  本文已影响512人  289346a467da

原创 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
上一篇 下一篇

猜你喜欢

热点阅读