原创 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 的方法说明:
- UserPar(Parcel in)从序列化后的对象中创建原始对象。
- describeContents() 返回当前对象的内容描述。如果含有文件扫描符,返回 1,否则返回0.几乎所有情况都返回0。
- writeToParcel(Parcel dest, int flags) 将当前对象写入序列化结构中,其中flags 为1 是标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0.
- createFromParcel(Parcel in) 从序列化后的对象中创建原始对象。
- 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
网友评论