下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

Android专题:Android序列化问题与思考

作者:jimuzz     来源: https://www.cnblogs.com/jimuzz/p/13959282.html点击数:1624发布时间: 2021-01-11 09:09:30

标签: android系统android autoandroid版

  序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。

  序列化使其他代码可以查看或修改,那些不序列化便无法访问的对象实例数据。确切地说,代码执行序列化需要特殊的权限:即指定了SerializationFormatter标志的SecurityPermission。在默认策略下,通过Internet下载的代码或Internet代码不会授予该权限;只有本地计算机上的代码才被授予该权限。

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

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

  反序列化就是序列化的相反操作,也就是把序列化生成的字节流转为我们内存的对象。

  序列化指的是讲对象变成有序的字节流,变成字节流之后才能进行传输存储等一系列操作。

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

  Parcelable

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

  publicclassUserimplementsParcelable{

  privateintid;

  protectedUser(Parcelin){

  id=in.readInt();

  }

  @Override

  publicvoidwriteToParcel(Parceldest,intflags){

  dest.writeInt(id);

  }

  @Override

  publicintdescribeContents(){

  return0;

  }

  publicstaticfinalCreator<User>CREATOR=newCreator<User>(){

  @Override

  publicUsercreateFromParcel(Parcelin){

  returnnewUser(in);

  }

  @Override

  publicUser[]newArray(intsize){

  returnnewUser[size];

  }

  };

  publicintgetId(){

  returnid;

  }

  publicvoidsetId(intid){

  this.id=id;

  }

  }

  createFromParcel,User(Parcelin),代表从序列化的对象中创建原始对象

  newArray,代表创建指定长度的原始对象数组

  writeToParcel,代表将当前对象写入到序列化结构中。

  describeContents,代表返回当前对象的内容描述。如果还有文件描述符,返回1,否则返回0。

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

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

  Serializable

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

  publicclassUserimplementsSerializable{

  privatestaticfinallongserialVersionUID=519067123721561165l;

  privateintid;

  publicintgetId(){

  returnid;

  }

  publicvoidsetId(intid){

  this.id=id;

  }

  }

  实现Serializable接口,声明一个serialVersionUID。

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

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

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

  protectedvoidsaveParce(){

  FileOutputStreamfos;

  try{

  fos=getApplicationContext().openFileOutput(TAG,

  Context.MODE_PRIVATE);

  BufferedOutputStreambos=newBufferedOutputStream(fos);

  Parcelparcel=Parcel.obtain();

  parcel.writeParcelable(newParceData(),0);

  bos.write(parcel.marshall());

  bos.flush();

  bos.close();

  fos.flush();

  fos.close();

  }catch(Exceptione){

  e.printStackTrace();

  }

  }

  protectedvoidloadParce(){

  FileInputStreamfis;

  try{

  fis=getApplicationContext().openFileInput(TAG);

  byte[]bytes=newbyte[fis.available()];

  fis.read(bytes);

  Parcelparcel=Parcel.obtain();

  parcel.unmarshall(bytes,0,bytes.length);

  parcel.setDataPosition(0);

  ParceDatadata=parcel.readParcelable(ParceData.class.getClassLoader());

  fis.close();

  }catch(Exceptione){

  e.printStackTrace();

  }

  }

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

  privatevoidwriteObject0(Objectobj,booleanunshared)

  throwsIOException

  {

  ...

  try{

  Objectorig=obj;

  Class<?>cl=obj.getClass();

  ObjectStreamClassdesc;

  desc=ObjectStreamClass.lookup(cl,true);

  if(objinstanceofClass){

  writeClass((Class)obj,unshared);

  }elseif(objinstanceofObjectStreamClass){

  writeClassDesc((ObjectStreamClass)obj,unshared);

  //ENDAndroid-changed:MakeClassandObjectStreamClassreplaceable.

  }elseif(objinstanceofString){

  writeString((String)obj,unshared);

  }elseif(cl.isArray()){

  writeArray(obj,desc,unshared);

  }elseif(objinstanceofEnum){

  writeEnum((Enum<?>)obj,desc,unshared);

  }elseif(objinstanceofSerializable){

  writeOrdinaryObject(obj,desc,unshared);

  }else{

  if(extendedDebugInfo){

  thrownewNotSerializableException(

  cl.getName()+"\\n"+debugInfoStack.toString());

  }else{

  thrownewNotSerializableException(cl.getName());

  }

  }

  }

  ...

  }

  privatevoidwriteOrdinaryObject(Objectobj,

  ObjectStreamClassdesc,

  booleanunshared)

  throwsIOException

  {

  ...

  try{

  desc.checkSerialize();

  //写入二进制文件,普通对象开头的魔数0x73

  bout.writeByte(TC_OBJECT);

  //写入对应的类的描述符,见底下源码

  writeClassDesc(desc,false);

  handles.assign(unshared?null:obj);

  if(desc.isExternalizable()&&!desc.isProxy()){

  writeExternalData((Externalizable)obj);

  }else{

  writeSerialData(obj,desc);

  }

  }finally{

  if(extendedDebugInfo){

  debugInfoStack.pop();

  }

  }

  }

  publiclonggetSerialVersionUID(){

  //如果没有定义serialVersionUID,序列化机制就会调用一个函数根据类内部的属性等计算出一个hash值

  if(suid==null){

  suid=AccessController.doPrivileged(

  newPrivilegedAction<Long>(){

  publicLongrun(){

  returncomputeDefaultSUID(cl);

  }

  }

  );

  }

  returnsuid.longValue();

  }

  可以看到是通过反射获取对象以及对象属性的相关信息,然后将数据写到了一个二进制文件,并且写入了序列化协议版本等等。

  而获取·serialVersionUID·的逻辑也体现出来,如果id为空则会生成计算一个hash值。

  总结

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

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

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

  Serializable是Java提供的序列化接口,使用简单但是开销很大,序列化和反序列化过程都需要大量I/O操作。

  Parcelable是Android中提供的,也是Android中推荐的序列化方式。虽然使用麻烦,但是效率很高。

  所以,如果是内存序列化层面,那么还是建议Parcelable,因为他效率会比较高。

  如果是网络传输和存储磁盘情况,就推荐Serializable,因为序列化方式比较简单,而且Parcelable不能保证,当外部条件发生变化时数据的连续性。

  1)Parcelable一定比Serializable快吗?

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

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

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

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

  

赞(0)
踩(0)
分享到:
上一篇:中文编码Python
华为认证网络工程师 HCIE直播课视频教程