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

本文详细对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.当然实现后者也不是不行,关键在于我们头脑中的那一份思想。

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

(0)

相关推荐

  • Android中使用Intent在Activity之间传递对象(使用Serializable或者Parcelable)的方法

    Android中的不同Activity之间传递对象,我们可以考虑采用Bundle.putSerializable(Key,Object);也可以考虑采用Bundle.putParcelable(Key, Object);其中前面一种方法中的Object要实现Serializable接口,后面一种方法中的Object要实现Parcelable接口.下面我们以一个完整的例子来说明. 1.新建一个Android的工程,其中该工程的目录结构如下图: 2. 修改main.xml布局文件.布局文件的源码如下

  • Android Parcelable接口使用方法详解

     Android Parcelable接口使用方法详解 1. Parcelable接口 Interface for classes whose instances can be written to and restored from a Parcel. Classes implementing the Parcelable interface must also have a static field called CREATOR, which is an object implementin

  • Android中的Parcelable序列化对象

    今天查阅资料,简单了解了一下Parcelable接口,它是android提供的序列化对象的接口,比java中的 Serializable高效些.通过这个接口序列化对象主要有两步: 1.实现public void writeToParcel(Parcel dest, int flags) {}方法: 2.实例化CREATOR public static final Parcelable.Creator<ParcelableImpl> CREATOR = new Parcelable.Creato

  • Android中Intent传递对象的两种方法Serializable,Parcelable

    Android中的传递有两个方法,一个是Serializable,另一个是Parcelable. Serializable是J2SE本身就支持的.而Parcelable是Android所特有的. 二者的使用场景和区别: 1)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable. 2)Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC. 3)Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelab

  • Android中Parcelable的作用实例解析

    在android提供了一种类型:Parcel.被用作封装数据的容器,封装后的数据可以通过Intent或IPC传递. 除了基本类型以外,只有实现了Parcelable接口的类才能被放入Parcel中.   Parcelable实现要点:需要实现三个东西 1)writeToParcel 方法.该方法将类的数据写入外部提供的Parcel中.声明如下: writeToParcel (Parcel dest, int flags) 具体参数含义见javadoc 2)describeContents方法.没

  • 很详细的android序列化过程Parcelable

    直接上代码:注释都写的很清楚了. public class Entry implements Parcelable{ public int userID; public String username; public boolean isMale; public Book book;//序列化对象可以嵌套序列化对象,前提是2个类的对象都被序列号过 //几乎所有情况下都返回0,可以不管 @Override public int describeContents() { return 0; } //

  • Android Intent传递对象的两种方法(Serializable,Parcelable)详细介绍

    Android Intent传递对象的两种方法(Serializable,Parcelable)详细介绍 今天要给大家讲一下Android中Intent中如何传递对象,就我目前所知道的有两种方法,一种是Bundle.putSerializable(Key,Object);另一种是Bundle.putParcelable(Key, Object);当然这些Object是有一定的条件的,前者是实现了Serializable接口,而后者是实现了Parcelable接口,为了让大家更容易理解我还是照常写

  • Android Parcelable与Serializable详解及区别

    Android Parcelable与 Serializable区别 1.作用 Serializable的作用是为了保存对象的属性到本地文件.数据库.网络流.rmi以方便数据传输,当然这种传输可以是程序内的也可以是两个程序间的.而Android的Parcelable的设计初衷是因为Serializable效率过慢,为了在程序内不同组件间以及不同Android程序间(AIDL)高效的传输数据而设计,这些数据仅在内存中存在,Parcelable是通过IBinder通信的消息的载体. 从上面的设计上我

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

    本文详细对Android中Serializable和Parcelable序列化对象进行学习,具体内容如下 学习内容: 1.序列化的目的 2.Android中序列化的两种方式 3.Parcelable与Serializable的性能比较 4.Android中如何使用Parcelable进行序列化操作 5.Parcelable的工作原理 6.相关实例  1.序列化的目的 1).永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中 2).通过序列化操作将对象数据在网络上进行传输(由于网络传输是以

  • Android 中Fragment与Activity通讯的详解

    Android 中Fragment与Activity通讯的详解 与activity通讯 尽管fragment的实现是独立于activity的,可以被用于多个activity,但是每个activity所包含的是同一个fragment的不同的实例. Fragment可以调用getActivity()方法很容易的得到它所在的activity的对象,然后就可以查找activity中的控件们(findViewById()). 例如: ViewlistView =getActivity().findView

  • Android中asset和raw的区别详解

    *res/raw和assets的相同点: 1.两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制. *res/raw和assets的不同点: 1.res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename:assets文件夹下的文件不会被映射到 R.java中,访问的时候需要AssetManager类. 2.res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹 *读取文件资源

  • Android 中Lambda表达式的使用实例详解

     Android 中Lambda表达式的使用实例详解 Java8 中着实引入了一些非常有特色的功能,如Lambda表达式.streamAPI.接口默认实现等等.Lambda表达式在 Android 中最低兼容到 Android2.3 系统,兼容性还是不错的,Lambda表达式本质上是一种匿名方法,它既没有方法名,也没有访问修饰符和返回值类型,使用它编写的代码将更加简洁易读. 1.Lambda表达式的基本写法 如果想要在 Android 项目中使用 Lambda表达式 或者 Java8 的其他新特

  • Android中判断网络是否连接实例详解

    Android中判断网络是否连接实例详解 在android中,如何监测网络的状态呢,这个有的时候也是十分重要的,方法如下: public class ConnectionDetector { private Context _context; public ConnectionDetector(Context context){ this._context = context; } public boolean isConnectingToInternet(){ ConnectivityMana

  • javascript中的糖衣语法Promise对象详解

    目录 一.Promise的诞生 1.回调地狱 二.Promise的行为 1.Promise的语法 2.Promise的方法 (1)Promise.prototype.then() (2)Promise.prototype.catch() (3)Promise.prototype.finally() (4)Promise.resolve() (5)Promise.reject() (6)Promise.all() (7)Promise.race() 三.Promise的场景 1.Ajax请求 2.

  • Android中FileProvider的各种场景应用详解

    目录 前言 一.常规使用与定义 二.能不能自定义接收文件? 三.能不能主动查询对方的沙盒? 总结 前言 有部分同学只要是上传或者下载,只要用到了文件,不管三七二十一写个 FileProvider 再说. 不是每一种情况都需要使用 FileProvider 的,啥?你问行不行?有没有毛病? 这... 写了确实可以,没毛病!但是这没有必要啊. 如果不需要FileProvider就不需要定义啊,如果定义了重复的 FileProvider,还会导致清单文件合并失败,需要处理冲突,从而引出又一个问题,解决

  • Android中关于Notification及NotificationManger的详解

    Android状态栏提醒 在Android中提醒功能也可以用AlertDialog,但是我们要慎重的使用,因为当使用AlertDialog的时候,用户正在进行的操作将会被打断,因为当前焦点被AlertDialog得到.我们可以想像一下,当用户打游戏正爽的时候,这时候来了一条短信.如果这时候短信用AlertDialog提醒,用户必须先去处理这条提醒,从而才能继续游戏.用户可能会活活被气死.而使用Notification就不会带来这些麻烦事,用户完全可以打完游戏再去看这条短信.所以在开发中应根据实际

  • Android中Messenger原理及基本用法详解

    这边博客主要记录一下Android中Messenger的基本原理和用法. 简单来讲,Messenger其实就是Binder通信的包装器,是一种基于消息传递的进程间通信工具. //Messenger实现了Parcelable接口,因此可以跨进程传输 public final class Messenger implements Parcelable { ............... } 通常情况下,我们可以在A进程中创建一个Messenger,然后将该Messenger传递给B进程. 于是,B进

  • Android中View位置和触摸事件详解

    一.简述 View是Android中所有控件的基类,不管是简单的Button和TextView,还是复杂的RelativeLayout和ListView,其基类都是View类:ViewGroup也继承了View类,这意味着View本身就可以代表简单的和复杂的所有控件和布局,通过这种关系,就形成了View树的结构. 本文Demo都是在自定义View中进行的,文末有下载链接 View的位置参数 MotionEvent屏幕触摸事件 GestureDetector手势检测(单击,双击,长摁,滑动) 二.

随机推荐