当前位置: 移动技术网 > IT编程>移动开发>Android > Android中Serializable和Parcelable序列化对象详解

Android中Serializable和Parcelable序列化对象详解

2019年07月24日  | 移动技术网IT编程  | 我要评论

身份证灰色利益链,铁树的养殖方法,英国留学迈格森

本文详细对android中serializable和parcelable序列化对象进行学习,具体内容如下

学习内容:

1.序列化的目的

2.android中序列化的两种方式

3.parcelable与serializable的性能比较

4.android中如何使用parcelable进行序列化操作

5.parcelable的工作原理

6.相关实例

 1.序列化的目的

 1).永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中

  2).通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的.因此序列化的目的是将对象数据转换成字节流的形式)

  3).将对象数据在进程之间进行传递(activity之间传递对象数据时,需要在当前的activity中对对象数据进行序列化操作.在另一个activity中需要进行反序列化操作讲数据取出)

  4).java平台允许我们在内存中创建可复用的java对象,但一般情况下,只有当jvm处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比jvm的生命周期更长(即每个对象都在jvm中)但在现实应用中,就可能要停止jvm运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这是java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存)

  5).序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化.

  6).在intent之间,基本的数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进行序列化操作了.

2.android中实现序列化的两种方式
1).implements serializable 接口 (声明一下即可)
serializable 的简单实例:

public class person implements serializable{
 private static final long serialversionuid = -7060210544600464481l;
 private string name;
 private int age;
 
 public string getname(){
  return name;
 }
 
 public void setname(string name){
  this.name = name;
 }
 
 public int getage(){
  return age;
 }
 
 public void setage(int age){
  this.age = age;
 }
}

2).implements parcelable 接口(不仅仅需要声明,还需要实现内部的相应方法)
parcelable的简单实例:

注:写入数据的顺序和读出数据的顺序必须是相同的.

public class book implements parcelable{
 private string bookname;
 private string author;
 private int publishdate;
 
 public book(){
  
 }
 
 public string getbookname(){
  return bookname;
 }
 
 public void setbookname(string bookname){
  this.bookname = bookname;
 }
 
 public string getauthor(){
  return author;
 }
 
 public void setauthor(string author){
  this.author = author;
 }
 
 public int getpublishdate(){
  return publishdate;
 }
 
 public void setpublishdate(int publishdate){
  this.publishdate = publishdate;
 }
 
 @override
 public int describecontents(){
  return 0;
 }
 
 @override
 public void writetoparcel(parcel out, int flags){
  out.writestring(bookname);
  out.writestring(author);
  out.writeint(publishdate);
 }
 
 public static final parcelable.creator<book> creator = new creator<book>(){
  
     @override
  public book[] newarray(int size){
   return new book[size];
  }
  
  @override
  public book createfromparcel(parcel in){
   return new book(in);
  }
 };
 
 public book(parcel in){
  //如果元素数据是list类型的时候需要: lits = new arraylist<?> in.readlist(list); 否则会出现空指针异常.并且读出和写入的数据类型必须相同.如果不想对部分关键字进行序列化,可以使用transient关键字来修饰以及static修饰.
  bookname = in.readstring();
  author = in.readstring();
  publishdate = in.readint();
 }
}

  我们知道在java应用程序当中对类进行序列化操作只需要实现serializable接口就可以,由系统来完成序列化和反序列化操作,但是在android中序列化操作有另外一种方式来完成,那就是实现parcelable接口.也是android中特有的接口来实现类的序列化操作.原因是parcelable的性能要强于serializable.因此在绝大多数的情况下,android还是推荐使用parcelable来完成对类的序列化操作的.

3.parcelable与serializable的性能比较

首先parcelable的性能要强于serializable的原因我需要简单的阐述一下

  1). 在内存的使用中,前者在性能方面要强于后者

  2). 后者在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致gc的频繁调用,因此在性能上会稍微逊色

  3). parcelable是以ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,android推荐使用parcelable,既然是内存方面比价有优势,那么自然就要优先选择.

  4). 在读写数据的时候,parcelable是在内存中直接进行读写,而serializable是通过使用io流的形式将数据读写入在硬盘上.

  但是:虽然parcelable的性能要强于serializable,但是仍然有特殊的情况需要使用serializable,而不去使用parcelable,因为parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的android版本当中,parcelable可能会不同,因此数据的持久化方面仍然是使用serializable)

  速度测试:

  测试方法:

1)、通过将一个对象放到一个bundle里面然后调用bundle#writetoparcel(parcel, int)方法来模拟传递对象给一个activity的过程,然后再把这个对象取出来。

2)、在一个循环里面运行1000 次。

3)、两种方法分别运行10次来减少内存整理,cpu被其他应用占用等情况的干扰。

4)、参与测试的对象就是上面的相关代码

5)、在多种android软硬件环境上进行测试

  • lg nexus 4 – android 4.2.2
  • samsung nexus 10 – android 4.2.2
  • htc desire z – android 2.3.3 

 结果如图:

 

 性能差异:

nexus 10

serializable: 1.0004ms,  parcelable: 0.0850ms – 提升10.16倍。

nexus 4

serializable: 1.8539ms – parcelable: 0.1824ms – 提升11.80倍。

desire z

serializable: 5.1224ms – parcelable: 0.2938ms – 提升17.36倍。

由此可以得出: parcelable 比 serializable快了10多倍。

  从相对的比较我们可以看出,parcelable的性能要比serializable要优秀的多,因此在android中进行序列化操作的时候,我们需要尽可能的选择前者,需要花上大量的时间去实现parcelable接口中的内部方法.

4.android中如何使用parcelable进行序列化操作

说了这么多,我们还是来看看android中如何去使用parcelable实现类的序列化操作吧.
 implements parcelable的时候需要实现内部的方法:

1).writetoparcel 将对象数据序列化成一个parcel对象(序列化之后成为parcel对象.以便parcel容器取出数据)

2).重写describecontents方法,默认值为0

3).public static final parcelable.creator<t>creator (将parcel容器中的数据转换成对象数据) 同时需要实现两个方法:
  3.1 createfromparcel(从parcel容器中取出数据并进行转换.)
  3.2 newarray(int size)返回对象数据的大小

 因此,很明显实现parcelable并不容易。实现parcelable接口需要写大量的模板代码,这使得对象代码变得难以阅读和维护。具体的实例就是上面parcelable的实例代码.就不进行列举了.(有兴趣的可以去看看android中networkinfo的源代码,是关于网络连接额外信息的一个相关类,内部就实现了序列化操作.大家可以去看看)

5.parcelable的工作原理

 无论是对数据的读还是写都需要使用parcel作为中间层将数据进行传递.parcel涉及到的东西就是与c++底层有关了.都是使用jni.在java应用层是先创建parcel(java)对象,然后再调用相关的读写操作的时候.就拿读写32为int数据来说吧:

static jint android_os_parcel_readint(jnienv* env, jobject clazz){
 parcel* parcel = parcelforjavaobject(env, clazz);
 if (parcel != null) {
  return parcel->readint32();
 }
 return 0;
}

调用的方法就是这个过程,首先是将parcel(java)对象转换成parcel(c++)对象,然后被封装在parcel中的相关数据由c++底层来完成数据的序列化操作.

status_t parcel::writeint32(int32_t val){
 return writealigned(val);
} 
template<class t="">
status_t parcel::writealigned(t val) {
 compile_time_assert_function_scope(pad_size(sizeof(t)) == sizeof(t));
 
 if ((mdatapos+sizeof(val)) <= mdatacapacity) {
restart_write:
  *reinterpret_cast<t*>(mdata+mdatapos) = val;
  return finishwrite(sizeof(val));
 }
 
 status_t err = growdata(sizeof(val));
 if (err == no_error) goto restart_write;
 return err;
}
真正的读写过程是由下面的源代码来完成的.
status_t parcel::continuewrite(size_t desired)
{
 // if shrinking, first adjust for any objects that appear
 // after the new data size.
 size_t objectssize = mobjectssize;
 if (desired < mdatasize) {
  if (desired == 0) {
   objectssize = 0;
  } else {
   while (objectssize > 0) {
    if (mobjects[objectssize-1] < desired)
     break;
    objectssize--;
   }
  }
 }
 
 if (mowner) {
  // if the size is going to zero, just release the owner's data.
  if (desired == 0) {
   freedata();
   return no_error;
  }

  // if there is a different owner, we need to take
  // posession.
  uint8_t* data = (uint8_t*)malloc(desired);
  if (!data) {
   merror = no_memory;
   return no_memory;
  }
  size_t* objects = null;
  
  if (objectssize) {
   objects = (size_t*)malloc(objectssize*sizeof(size_t));
   if (!objects) {
    merror = no_memory;
    return no_memory;
   }

   // little hack to only acquire references on objects
   // we will be keeping.
   size_t oldobjectssize = mobjectssize;
   mobjectssize = objectssize;
   acquireobjects();
   mobjectssize = oldobjectssize;
  }
  
  if (mdata) {
   memcpy(data, mdata, mdatasize < desired ? mdatasize : desired);
  }
  if (objects && mobjects) {
   memcpy(objects, mobjects, objectssize*sizeof(size_t));
  }
  //alogi("freeing data ref of %p (pid=%d)\n", this, getpid());
  mowner(this, mdata, mdatasize, mobjects, mobjectssize, mownercookie);
  mowner = null;

  mdata = data;
  mobjects = objects;
  mdatasize = (mdatasize < desired) ? mdatasize : desired;
  alogv("continuewrite setting data size of %p to %d\n", this, mdatasize);
  mdatacapacity = desired;
  mobjectssize = mobjectscapacity = objectssize;
  mnextobjecthint = 0;

 } else if (mdata) {
  if (objectssize < mobjectssize) {
   // need to release refs on any objects we are dropping.
   const sp<processstate> proc(processstate::self());
   for (size_t i=objectssize; i<mobjectssize; i++) {
    const flat_binder_object* flat
     = reinterpret_cast<flat_binder_object*>(mdata+mobjects[i]);
    if (flat->type == binder_type_fd) {
     // will need to rescan because we may have lopped off the only fds
     mfdsknown = false;
    }
    release_object(proc, *flat, this);
   }
   size_t* objects =
    (size_t*)realloc(mobjects, objectssize*sizeof(size_t));
   if (objects) {
    mobjects = objects;
   }
   mobjectssize = objectssize;
   mnextobjecthint = 0;
  }

  // we own the data, so we can just do a realloc().
  if (desired > mdatacapacity) {
   uint8_t* data = (uint8_t*)realloc(mdata, desired);
   if (data) {
    mdata = data;
    mdatacapacity = desired;
   } else if (desired > mdatacapacity) {
    merror = no_memory;
    return no_memory;
   }
  } else {
   if (mdatasize > desired) {
    mdatasize = desired;
    alogv("continuewrite setting data size of %p to %d\n", this, mdatasize);
   }
   if (mdatapos > desired) {
    mdatapos = desired;
    alogv("continuewrite setting data pos of %p to %d\n", this, mdatapos);
   }
  }
  
 } else {
  // this is the first data. easy!
  uint8_t* data = (uint8_t*)malloc(desired);
  if (!data) {
   merror = no_memory;
   return no_memory;
  }
  
  if(!(mdatacapacity == 0 && mobjects == null
    && mobjectscapacity == 0)) {
   aloge("continuewrite: %d/%p/%d/%d", mdatacapacity, mobjects, mobjectscapacity, desired);
  }
  
  mdata = data;
  mdatasize = mdatapos = 0;
  alogv("continuewrite setting data size of %p to %d\n", this, mdatasize);
  alogv("continuewrite setting data pos of %p to %d\n", this, mdatapos);
  mdatacapacity = desired;
 }

 return no_error;
}

1).整个读写全是在内存中进行,主要是通过malloc()、realloc()、memcpy()等内存操作进行,所以效率比java序列化中使用外部存储器会高很多

2).读写时是4字节对齐的,可以看到#define pad_size(s) (((s)+3)&~3)这句宏定义就是在做这件事情

3).如果预分配的空间不够时newsize = ((mdatasize+len)*3)/2;会一次多分配50%

4).对于普通数据,使用的是mdata内存地址,对于ibinder类型的数据以及filedescriptor使用的是mobjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的,目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。
6.相关实例

最后上一个例子..

首先是序列化的类book.class

public class book implements parcelable{
 private string bookname;
 private string author;
 private int publishdate;
 
 public book(){
  
 }
 
 public string getbookname(){
  return bookname;
 }
 
 public void setbookname(string bookname){
  this.bookname = bookname;
 }
 
 public string getauthor(){
  return author;
 }
 
 public void setauthor(string author){
  this.author = author;
 }
 
 public int getpublishdate(){
  return publishdate;
 }
 
 public void setpublishdate(int publishdate){
  this.publishdate = publishdate;
 }
 
 @override
 public int describecontents(){
  return 0;
 }
 
 @override
 public void writetoparcel(parcel out, int flags){
  out.writestring(bookname);
  out.writestring(author);
  out.writeint(publishdate);
 }
 
 public static final parcelable.creator<book> creator = new creator<book>(){
  
     @override
  public book[] newarray(int size){
   return new book[size];
  }
  
  @override
  public book createfromparcel(parcel in){
   return new book(in);
  }
 };
 
 public book(parcel in){
  //如果元素数据是list类型的时候需要: lits = new arraylist<?> in.readlist(list); 否则会出现空指针异常.并且读出和写入的数据类型必须相同.如果不想对部分关键字进行序列化,可以使用transient关键字来修饰以及static修饰.
  bookname = in.readstring();
  author = in.readstring();
  publishdate = in.readint();
 }
}

第一个activity,mainactivity

book book = new book();
book.setbookname("darker");
book.setbookauthor("me");
book.setpublishdate(20);
bundle bundle = new bundle();
bundle.putparcelable("book", book);
intent intent = new intent(mainactivity.this,anotheractivity.class);
intent.putextras(bundle);

第二个activity,anotheractivity

intent intent = getintent();
bundle bun = intent.getextras();
book book = bun.getparcelable("book");
system.out.println(book);

 总结:java应用程序中有serializable来实现序列化操作,android中有parcelable来实现序列化操作,相关的性能也作出了比较,因此在android中除了对数据持久化的时候需要使用到serializable来实现序列化操作,其他的时候我们仍然需要使用parcelable来实现序列化操作,因为在android中效率并不是最重要的,而是内存,通过比较parcelable在效率和内存上都要优秀与serializable,尽管parcelable实现起来比较复杂,但是如果我们想要成为一名优秀的android软件工程师,那么我们就需要勤快一些去实现parcelable,而不是偷懒与实现serializable.当然实现后者也不是不行,关键在于我们头脑中的那一份思想。

以上就是本文的全部内容,希望对大家的学习有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网