经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Android序列化问题与思考
来源:cnblogs  作者:jimuzz  时间:2020/11/16 10:05:36  对本文有异议

今天再来谈谈Android中的对象序列化,你了解多少呢?

序列化指的是什么?有什么用

序列化指的是讲对象变成有序的字节流,变成字节流之后才能进行传输存储等一系列操作。
反序列化就是序列化的相反操作,也就是把序列化生成的字节流转为我们内存的对象。

介绍下Android中两种序列化接口

Serializable

Java提供的一个序列化接口,是一个空接口,专门为对象提供序列化和反序列化操作。具体使用如下:

  1. public class User implements Serializable {
  2. private static final long serialVersionUID=519067123721561165l;
  3. private int id;
  4. public int getId() {
  5. return id;
  6. }
  7. public void setId(int id) {
  8. this.id = id;
  9. }
  10. }

实现Serializable接口,声明一个serialVersionUID

到这里可能有人就问了,不对啊,平时没有这个serialVersionUID啊。没错,serialVersionUID不是必须的,因为不写的话,系统会自动生成这个变量。它有什么用呢?当序列化的时候,系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候会去检测这个serialVersionUID,看他是否和当前类的serialVersionUID一致,一样则可以正常反序列化,如果不一样就会报错了。

所以这个serialVersionUID就是序列化和反序列化过程中的一个标识,代表一致性。不加的话会有什么影响?如果我们序列化后,改动了这个类的某些成员变量,那么serialVersionUID就会改变,这时候再拿之前序列化的数据来反序列化就会报错。所以如果我们手动指定serialVersionUID就能保证最大限度来恢复数据。

Serializable的实质其实是是把Java对象序列化为二进制文件,然后就能在进程之间传递,并且用于网络传输或者本地存储等一系列操作,因为他的本质就存储了文件。可以看看源码:

  1. private void writeObject0(Object obj, boolean unshared)
  2. throws IOException
  3. {
  4. ...
  5. try {
  6. Object orig = obj;
  7. Class<?> cl = obj.getClass();
  8. ObjectStreamClass desc;
  9. desc = ObjectStreamClass.lookup(cl, true);
  10. if (obj instanceof Class) {
  11. writeClass((Class) obj, unshared);
  12. } else if (obj instanceof ObjectStreamClass) {
  13. writeClassDesc((ObjectStreamClass) obj, unshared);
  14. // END Android-changed: Make Class and ObjectStreamClass replaceable.
  15. } else if (obj instanceof String) {
  16. writeString((String) obj, unshared);
  17. } else if (cl.isArray()) {
  18. writeArray(obj, desc, unshared);
  19. } else if (obj instanceof Enum) {
  20. writeEnum((Enum<?>) obj, desc, unshared);
  21. } else if (obj instanceof Serializable) {
  22. writeOrdinaryObject(obj, desc, unshared);
  23. } else {
  24. if (extendedDebugInfo) {
  25. throw new NotSerializableException(
  26. cl.getName() + "\n" + debugInfoStack.toString());
  27. } else {
  28. throw new NotSerializableException(cl.getName());
  29. }
  30. }
  31. }
  32. ...
  33. }
  34. private void writeOrdinaryObject(Object obj,
  35. ObjectStreamClass desc,
  36. boolean unshared)
  37. throws IOException
  38. {
  39. ...
  40. try {
  41. desc.checkSerialize();
  42. //写入二进制文件,普通对象开头的魔数0x73
  43. bout.writeByte(TC_OBJECT);
  44. //写入对应的类的描述符,见底下源码
  45. writeClassDesc(desc, false);
  46. handles.assign(unshared ? null : obj);
  47. if (desc.isExternalizable() && !desc.isProxy()) {
  48. writeExternalData((Externalizable) obj);
  49. } else {
  50. writeSerialData(obj, desc);
  51. }
  52. } finally {
  53. if (extendedDebugInfo) {
  54. debugInfoStack.pop();
  55. }
  56. }
  57. }
  58. public long getSerialVersionUID() {
  59. // 如果没有定义serialVersionUID,序列化机制就会调用一个函数根据类内部的属性等计算出一个hash值
  60. if (suid == null) {
  61. suid = AccessController.doPrivileged(
  62. new PrivilegedAction<Long>() {
  63. public Long run() {
  64. return computeDefaultSUID(cl);
  65. }
  66. }
  67. );
  68. }
  69. return suid.longValue();
  70. }

可以看到是通过反射获取对象以及对象属性的相关信息,然后将数据写到了一个二进制文件,并且写入了序列化协议版本等等。
而获取·serialVersionUID·的逻辑也体现出来,如果id为空则会生成计算一个hash值。

Parcelable

Android自带的接口,使用起来要麻烦很多:需要实现Parcelable接口,重写describeContents(),writeToParcel(Parcel dest, @WriteFlags int flags),并添加一个静态成员变量CREATOR并实现Parcelable.Creator接口

  1. public class User implements Parcelable {
  2. private int id;
  3. protected User(Parcel in) {
  4. id = in.readInt();
  5. }
  6. @Override
  7. public void writeToParcel(Parcel dest, int flags) {
  8. dest.writeInt(id);
  9. }
  10. @Override
  11. public int describeContents() {
  12. return 0;
  13. }
  14. public static final Creator<User> CREATOR = new Creator<User>() {
  15. @Override
  16. public User createFromParcel(Parcel in) {
  17. return new User(in);
  18. }
  19. @Override
  20. public User[] newArray(int size) {
  21. return new User[size];
  22. }
  23. };
  24. public int getId() {
  25. return id;
  26. }
  27. public void setId(int id) {
  28. this.id = id;
  29. }
  30. }
  • createFromParcel,User(Parcel in) ,代表从序列化的对象中创建原始对象
  • newArray,代表创建指定长度的原始对象数组
  • writeToParcel,代表将当前对象写入到序列化结构中。
  • describeContents,代表返回当前对象的内容描述。如果还有文件描述符,返回1,否则返回0。

Parcelable的存储是通过Parcel存储到内存的,简单地说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象。

这其中实际又是通过native方法实现的。具体逻辑我就没有去分析了,如果有大神朋友可以在评论区解析下。

当然,Parcelable也是可以持久化的,涉及到Parcel中的unmarshallmarshall方法。 这里简单贴一下代码:

  1. protected void saveParce() {
  2. FileOutputStream fos;
  3. try {
  4. fos = getApplicationContext().openFileOutput(TAG,
  5. Context.MODE_PRIVATE);
  6. BufferedOutputStream bos = new BufferedOutputStream(fos);
  7. Parcel parcel = Parcel.obtain();
  8. parcel.writeParcelable(new ParceData(), 0);
  9. bos.write(parcel.marshall());
  10. bos.flush();
  11. bos.close();
  12. fos.flush();
  13. fos.close();
  14. } catch (Exception e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. protected void loadParce() {
  19. FileInputStream fis;
  20. try {
  21. fis = getApplicationContext().openFileInput(TAG);
  22. byte[] bytes = new byte[fis.available()];
  23. fis.read(bytes);
  24. Parcel parcel = Parcel.obtain();
  25. parcel.unmarshall(bytes, 0, bytes.length);
  26. parcel.setDataPosition(0);
  27. ParceData data = parcel.readParcelable(ParceData.class.getClassLoader());
  28. fis.close();
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. }
  32. }

总结

0)两者的区别,我们该怎么选择?

Serializable是Java提供的序列化接口,使用简单但是开销很大,序列化和反序列化过程都需要大量I/O操作。
Parcelable是Android中提供的,也是Android中推荐的序列化方式。虽然使用麻烦,但是效率很高。

所以,如果是内存序列化层面,那么还是建议Parcelable,因为他效率会比较高。
如果是网络传输和存储磁盘情况,就推荐Serializable,因为序列化方式比较简单,而且Parcelable不能保证,当外部条件发生变化时数据的连续性。

1)对于内存序列化方面建议用Parcelable,为什么呢?

  • 因为Serializable是存储了一个二进制文件,所以会有频繁的IO操作,消耗也比较大,而且用到了大量反射,反射操作也是耗时的。相比之下Parcelable就要效率高很多。

2)对于数据持久化还是建议用Serializable,为什么呢?

  • 首先,Serializable本身就是存储到二进制文件,所以用于持久化比较方便。而Parcelable序列化是在内存中操作,如果进程关闭或者重启的时候,内存中的数据就会消失,那么Parcelable序列化用来持久化就有可能会失败,也就是数据不会连续完整。而且Parcelable还有一个问题是兼容性,每个Android版本可能内部实现都不一样,知识用于内存中也就是传递数据的话是不影响的,但是如果持久化可能就会有问题了,低版本的数据拿到高版本可能会出现兼容性问题。 所以还是建议用Serializable进行持久化。

3)Parcelable一定比Serializable快吗?

  • 有个比较有趣的例子是:当序列化一个超级大的对象图表(表示通过一个对象,拥有通过某路径能访问到其他很多的对象),并且每个对象有10个以上属性时,并且Serializable实现了writeObject()以及readObject(),在平均每台安卓设备上,Serializable序列化速度大于Parcelable 3.6倍,反序列化速度大于1.6倍.

具体原因就是因为Serilazable的实现方式中,是有缓存的概念的,当一个对象被解析过后,将会缓存在HandleTable中,当下一次解析到同一种类型的对象后,便可以向二进制流中,写入对应的缓存索引即可。但是对于Parcel来说,没有这种概念,每一次的序列化都是独立的,每一个对象,都当作一种新的对象以及新的类型的方式来处理。

参考

Parcelable

拜拜

有一起学习的小伙伴可以关注下??我的公众号——码上积木,每天剖析一个知识点,我们一起积累知识。

原文链接:http://www.cnblogs.com/jimuzz/p/13959282.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号