登录
  • 欢迎访问 Sharezer Blog

Android中Parcelable的原理和使用方法

Android sharezer 1861次浏览 已收录 0个评论

本文由 简悦 SimpRead 转码, 原文地址 https://www.jianshu.com/p/df35baa91541

[TOC]

Parcelable 的简单介绍

介绍 Parcelable 不得不先提一下 Serializable 接口, Serializable 是 Java 为我们提供的一个标准化的序列化接口, 那什么是序列化呢?

进行 Android 开发的时候,无法将对象的引用传给 Activities 或者 Fragments,我们需要将这些对象放到一个 Intent 或者 Bundle 里面,然后再传递。简单来说就是将对象转换为可以传输的二进制流 (二进制序列) 的过程, 这样我们就可以通过序列化, 转化为可以在网络传输或者保存到本地的流 (序列), 从而进行传输数据 , 那反序列化就是从二进制流(序列) 转化为对象的过程.

Parcelable 是 Android 为我们提供的序列化的接口, Parcelable 相对于 Serializable 的使用相对复杂一些, 但 Parcelable 的效率相对 Serializable 也高很多, 这一直是 Google 工程师引以为傲的, 有时间的可以看一下 Parcelable 和 Serializable 的效率对比 Parcelable vs Serializable 号称快 10 倍的效率

Android 源码中的 Parcelable

/**
     * Interface for classes whose instances can be written to
   * and restored from a {@link Parcel}.  Classes implementing the Parcelable
 * interface must also have a non-null static field called <code>CREATOR</code>
 * of a type that implements the {@link Parcelable.Creator} interface.
 * 
 * <p>A typical implementation of Parcelable is:</p>
 * 
 * <pre>
 * public class MyParcelable implements Parcelable {
 *     private int mData;
 *
 *       public int describeContents() {
 *         return 0;
 *     }
 *
 *     public void writeToParcel(Parcel out, int flags) {
 *         out.writeInt(mData);
 *     }
 *
 *     public static final Parcelable.Creator<MyParcelable> CREATOR
 *             = new Parcelable.Creator<MyParcelable>() {
 *         public MyParcelable createFromParcel(Parcel in) {
 *             return new MyParcelable(in);
 *         }
 *
 *         public MyParcelable[] newArray(int size) {
 *             return new MyParcelable[size];
 *         }
 *     };
 *     
 *     private MyParcelable(Parcel in) {
 *         mData = in.readInt();
 *     }
 * }</pre>
 */

通过源码中的介绍 可以知道, Parcelable 接口的实现类是可以通过 Parcel 写入和恢复数据的, 并且必须要有一个非空的静态变量 CREATOR, 而且还给了一个例子, 这样我们写起来就比较简单了, 但是简单的使用并不是我们的最终目的, 通过查看 Android 源码中 Parcelable 可以看出, Parcelable 实现过程主要分为序列化, 反序列化, 描述三个过程, 下面分别介绍下这三个过程。

Parcel 的简介

在介绍之前我们需要先了解 Parcel 是什么? Parcel 翻译过来是打包的意思, 其实就是包装了我们需要传输的数据, 然后在 Binder 中传输, 也就是用于跨进程传输数据

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

Android中Parcelable的原理和使用方法

5889165-0bc444ac5c6f505e.png

Parcel 可以包含原始数据类型(用各种对应的方法写入,比如 writeInt(),writeFloat() 等),可以包含 Parcelable 对象,它还包含了一个活动的 IBinder 对象的引用,这个引用导致另一端接收到一个指向这个 IBinder 的代理 IBinder。
Parcelable 通过 Parcel 实现了 read 和 write 的方法, 从而实现序列化和反序列化

Parcelable 中的三大过程介绍 (序列化, 反序列化, 描述)

什么是序列化

序列化,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。

到这里, 基本上关系都理清了, 也明白简单的介绍和原理了, 接下来在实现 Parcelable 之前, 介绍下实现 Parcelable 的三大流程 .
[图片上传中...(image.png-73288d-1522235519935-0)]

我先把代码贴出来

public class Album implements Parcelable {

  /**
     * 负责反序列化
     */
  private static final Creator<Album> CREATOR = new Creator<Album>() {
    /**
         * 从序列化对象中,获取原始的对象
         * @param source
         * @return
         */
    @Override
    public Album createFromParcel(Parcel source) {
      return new Album(source);
    }

    /**
         * 创建指定长度的原始对象数组
         * @param size
         * @return
         */
    @Override
    public Album[] newArray(int size) {
      return new Album[0];
    }
  };

  private final String mId;
  private final String mCoverPath;
  private final String mDisplayName;
  private final long mCount;

  Album(String id, String coverPath, String displayName, long count) {
    mId = id;
    mCoverPath = coverPath;
    mDisplayName = displayName;
    mCount = count;
  }

  Album(Parcel source) {
    mId = source.readString();
    mCoverPath = source.readString();
    mDisplayName = source.readString();
    mCount = source.readLong();
  }

  /**
     * 描述
     * 返回的是内容的描述信息
     * 只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
     *
     * @return
     */
  @Override
  public int describeContents() {
    return 0;
  }

  /**
     * 序列化
     *
     * @param dest
     * @param flags
     */
  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(mId);
    dest.writeString(mCoverPath);
    dest.writeString(mDisplayName);
    dest.writeLong(mCount);
  }
实现 Parcelable 的作用

1)永久性保存对象,保存对象的字节序列到本地文件中;

2)通过序列化对象在网络中传递对象;

3)通过序列化在进程间传递对象。

首先写一个类实现 Parcelable 接口, 会让我们实现两个方法:

  • describeContents 描述
    其中 describeContents 就是负责文件描述. 通过源码的描述可以看出, 只针对一些特殊的需要描述信息的对象, 需要返回 1, 其他情况返回 0 就可以

  • writeToParcel 序列化
    我们通过 writeToParcel 方法实现序列化, writeToParcel 返回了 Parcel, 所以我们可以直接调用 Parcel 中的 write 方法, 基本的 write 方法都有, 对象和集合比较特殊下面单独讲, 基本的数据类型除了 boolean 其他都有, Boolean 可以使用 int 或 byte 存储

我们将上面的 Album 对象实现序列化, Album 对象包含四个字段。

反序列化

反序列化需要定义一个 CREATOR 的变量, 上面也说了具体的做法, 这里可以直接复制 Android 给的例子中的, 也可以自己定义一个 (名字千万不能改), 通过匿名内部类实现 Parcelable 中的 Creator 的接口

Parcelable 的使用和实现

根据上面三个过程的介绍, Parcelable 就写完了, 就可以直接在 Intent 中传输了, 可以自己写两个 Activity 传输一下数据试一下, 其中一个 putExtra 另一个 getParcelableExtra 即可。

Parcelable 中对象和集合的处理

import android.os.Parcel;
import android.os.Parcelable;

import java.util.ArrayList;

/**
 * Created by fengxing on 2018/3/28.
 */

public class ParcelDemo implements Parcelable {

  private int count;
  private String name;
  private ArrayList<String> tags;
  private Book book;
  // ***** 注意: 这里如果是集合 ,一定要初始化 *****
  private ArrayList<Book> books = new ArrayList<>();

  /**
     * 序列化
     *
     * @param in
     */
  protected ParcelDemo(Parcel in) {
    count = in.readInt();
    name = in.readString();
    tags = in.createStringArrayList();

    // 读取对象需要提供一个类加载器去读取,因为写入的时候写入了类的相关信息
    book = in.readParcelable(Book.class.getClassLoader());

    //读取集合也分为两类,对应写入的两类

    //这一类需要用相应的类加载器去获取
    in.readList(books, Book.class.getClassLoader());// 对应writeList

    //这一类需要使用类的CREATOR去获取
    in.readTypedList(books, Book.CREATOR); //对应writeTypeList

    //books = in.createTypedArrayList(Book.CREATOR); //对应writeTypeList

    //这里获取类加载器主要有几种方式
    getClass().getClassLoader();
    Thread.currentThread().getContextClassLoader();
    Book.class.getClassLoader();

  }

  public static final Creator<ParcelDemo> CREATOR = new Creator<ParcelDemo>() {
    @Override
    public ParcelDemo createFromParcel(Parcel in) {
      return new ParcelDemo(in);
    }

    @Override
    public ParcelDemo[] newArray(int size) {
      return new ParcelDemo[size];
    }
  };

  /**
     * 描述
     *
     * @return
     */
  @Override
  public int describeContents() {
    return 0;
  }

  /**
     * 反序列化
     *
     * @param dest
     * @param flags
     */
  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(count);
    dest.writeString(name);
    //序列化一个String的集合
    dest.writeStringList(tags);
    // 序列化对象的时候传入要序列化的对象和一个flag,
    // 这里的flag几乎都是0,除非标识当前对象需要作为返回值返回,不能立即释放资源
    dest.writeParcelable(book, 0);

    // 序列化一个对象的集合有两种方式,以下两种方式都可以

    //这些方法们把类的信息和数据都写入Parcel,以使将来能使用合适的类装载器重新构造类的实例.所以效率不高
    dest.writeList(books);

    //这些方法不会写入类的信息,取而代之的是:读取时必须能知道数据属于哪个类并传入正确的Parcelable.Creator来创建对象
    // 而不是直接构造新对象。(更加高效的读写单个Parcelable对象的方法是:
    // 直接调用Parcelable.writeToParcel()和Parcelable.Creator.createFromParcel())
    dest.writeTypedList(books);

  }
}

Book 类,需要先实现 Parcelable, 实现步骤就不贴出来了, 和普通的对象一样, 实现三个过程

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by fengxing on 2018/3/28.
 */

public class Book implements Parcelable {

  protected Book(Parcel in) {
  }

  public static final Creator<Book> CREATOR = new Creator<Book>() {
    @Override
    public Book createFromParcel(Parcel in) {
      return new Book(in);
    }

    @Override
    public Book[] newArray(int size) {
      return new Book[size];
    }
  };

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
  }
}

写入和读取集合有两种方式,
一种是写入类的相关信息, 然后通过类加载器去读取, –> writeList | readList
二是不用类相关信息, 创建时传入相关类的 CREATOR 来创建 –> writeTypeList | readTypeList | createTypedArrayList
第二种效率高一些
一定要注意如果有集合定义的时候一定要初始化 like this –>
public ArrayList<T> demo = new ArrayList<>();

Parcelable 和 Serializable 的区别和比较

Parcelable 和 Serializable 都是实现序列化并且都可以用于 Intent 间传递数据, Serializable 是 Java 的实现方式, 可能会频繁的 IO 操作, 所以消耗比较大, 但是实现方式简单 Parcelable 是 Android 提供的方式, 效率比较高, 但是实现起来复杂一些 , 二者的选取规则是: 内存序列化上选择 Parcelable, 存储到设备或者网络传输上选择 Serializable(当然 Parcelable 也可以但是稍显复杂)

选择序列化方法的原则

1)在使用内存的时候,Parcelable 比 Serializable 性能高,所以推荐使用 Parcelable。

2)Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的 GC。

3)Parcelable 不能使用在要将数据存储在磁盘上的情况,因为 Parcelable 不能很好的保证数据的持续性在外界有变化的情况下。尽管 Serializable 效率低点,但此时还是建议使用 Serializable 。


Sharezer , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明Android中Parcelable的原理和使用方法
喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址