Android中Parcelable的原理和使用方法

1.1 Parcelable的简单介绍

介绍Parcelable不得不先提一下Serializable接口,Serializable是Java为我们提供的一个标准化的序列化接口,那什么是序列化呢? ---- 简单来说就是将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据 ,那反序列化就是从二进制流(序列)转化为对象的过程.

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实现过程主要分为序列化,反序列化,描述三个过程,下面分别介绍下这三个过程

1.2 Parcel的简介

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

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

Parcel模型

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

Parcelable通过Parcel实现了read和write的方法,从而实现序列化和反序列化,我们在源码中看一下

Parcel中的Read方法

Parcel中的write方法

可以看出包含了各种各样的read和write方法,最终都是通过native方法实现

Parcel中的native方法

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

到这里,基本上关系都理清了,也明白简单的介绍和原理了,接下来在实现Parcelable之前,介绍下实现Parcelable的三大流程
首先写一个类实现Parcelable接口,会让我们实现两个方法

要实现的方法

1.3.1 描述

其中describeContents就是负责文件描述,首先看一下源码的解读

描述的源码

通过上面的描述可以看出,只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以

1.3.2 序列化

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

举个例子:我们将上面的User对象实现序列化,User对象包含三个字段 age,name,isMale

 /**
 * 该方法负责序列化
 * @param dest
 * @param flags
 */
@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeInt(age);
    dest.writeString(name);
    // boolean 可以使用int或byte方式进行存储,怎么存就怎么取
    dest.writeInt(isMale ? 1 : 0);
}
1.3.3 反序列化

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

/**
 * 负责反序列化
 */
public static final Creator<User> CREATOR = new Creator<User>() {
    /**
     * 从序列化后的对象中创建原始对象
     */
    @Override
    public User createFromParcel(Parcel source) {
        return new User(source);
    }

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

public User(Parcel parcel) {
    age = parcel.readInt();
    name = parcel.readString();
    isMale = parcel.readInt() == 1;
}

2. Parcelable的实现和使用

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

这里实现Parcelable也很简单

1.写一个类实现Parcelable然后alt+enter 添加Parcelable所需的代码块,AndroidStudio会自动帮我们实现(这里需要注意如果其中包含对象或集合需要把对象也实现Parcelable)

3. Parcelable中对象和集合的处理

如果实现Parcelable接口的对象中包含对象或者集合,那么其中的对象也要实现Parcelable接口
如果包含对象Author和集合:

public class Book implements Parcelable {

  public int id;
  public String name;
  public boolean isCell;
  public ArrayList<String> tags;
  public Author author;
  // ***** 注意: 这里如果是集合 ,一定要初始化 *****
  public ArrayList<Author> authors = new ArrayList<>();

  public Book() {
  }
}

这里包含一个Author对象和Author集合

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

针对上面Book对象的序列化和反序列化的实现:

反序列化:

//反序列化
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];
    }
};

protected Book(Parcel in) {
    id = in.readInt();
    name = in.readString();
    isCell = in.readByte() != 0;

    tags = in.createStringArrayList();

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

    //读取集合也分为两类,对应写入的两类
    //这一类需要用相应的类加载器去获取
    //in.readList(authors, Author.class.getClassLoader()); // 对应writeList

    //这一类需要使用类的CREATOR去获取
    in.readTypedList(authors, Author.CREATOR); //对应writeTypeList
    //authors = in.createTypedArrayList(Author.CREATOR);//对应writeTypeList

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

序列化:

@Override
public void writeToParcel(Parcel dest, int flags) {
    //下面三种是基本类型就不多说
    dest.writeInt(id);
    dest.writeString(name);
    dest.writeByte((byte) (isCell ? 1 : 0));

    //序列化一个String的集合
    dest.writeStringList(tags);

    // 序列化对象的时候传入要序列化的对象和一个flag,
    // 这里的flag几乎都是0,除非标识当前对象需要作为返回值返回,不能立即释放资源
    dest.writeParcelable(author, flags);

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

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

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

写入和读取集合有两种方式,
一种是写入类的相关信息,然后通过类加载器去读取, --> writeList | readList
二是不用类相关信息,创建时传入相关类的CREATOR来创建 --> writeTypeList | readTypeList | createTypedArrayList
第二种效率高一些

一定要注意如果有集合定义的时候一定要初始化 like this -->
public ArrayList<Author> authors = new ArrayList<>();

4. Parcelable和Serializable的区别和比较

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


Android 源码中Parcel使用一例(在ServiceManager中的使用)

在Android源码中的ServiceManagerProxy类中,就使用了Parcel。

例如,getService方法中:

 说明:这是获取服务的代码,其中:

(1)data.writeString(name);将数据name(就是要获取的服务名,由外部传入)写入到Parcel对象data中;
(2)mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);讲Parcel对象data发给服务端的binder,GET_SERVICE_TRANSACTION意味着这是获取Service的请求。

对应的binder服务端的处理:

   
virtual status_t onTransact( uint32_t code,  
                                const Parcel& data,  
                                Parcel* reply,  
                                uint32_t flags = 0);  

 说明:

   
code :是请求的ID号    
data :客户端请求发送的参数   
reply:服务器端返回的结果   
flags:一些额外的标识,如FLAG_ONEWAY等,通常为0.

Parcel中的读写数据的相关方法:

obtain() //获得一个新的parcel ,相当于new一个对象
 
dataSize() //得到当前parcel对象的实际存储空间
 
dataCapacity()  //得到当前parcel对象的已分配的存储空间, >=dataSize()值  (以空间换时间)
 
dataPostion() //获得当前parcel对象的偏移量(类似于文件流指针的偏移量)
 
setDataPosition()//设置偏移量
 
recyle() //清空、回收parcel对象的内存
 
writeInt(int) //写入一个整数
 
writeFloat(float) //写入一个浮点数
 
writeDouble(double) //写入一个双精度数
 
writeString(string) //写入一个字符串
 
 
 
当然,还有更多的writeXXX()方法,与之对应的就是readXXX(),具体方法请参阅SDK。
 
其中几个值得注意的方法为:
 
writeException() //在Parcel队头写入一个异常
 
writeException() //Parcel队头写入“无异常“
 
readException() //在Parcel队头读取,若读取值为异常,则抛出该异常;否则,程序正常运行。

Parcel中数据存储机制:

我们知道,下面的基本数据类型的取值范围:

 boolean     1bit          1字节

 char          16bit         2字节

 int             32bit        4字节

 long          64bit        8字节

float           32bit        4字节

double       64bit         8字节

        除了基本数据类型外,而C语言中,结构体的内存对齐和Parcel采用的内存存放机制一样,即读取最小字节为32bit,也即4个字节。高于4个字节的,以实际数据类型进行存放,但得为4byte的倍数。基本公式如下:

 实际存放字节:

                       判别一:  32bit      (<=32bit)             例如:boolean,char,int

                       判别二:  实际占用字节(>32bit)     例如:long,float,String,数组等

 当我们使用readXXX()方法时,读取方法也如上述:

   实际读取字节:

                        判别一:  32bit      (<=32bit)            例如:boolean,char,int

                        判别二:  实际字节大小(>32bit)     例如:long,float,String,数值等

    由上可以知道,当我们写入/读取一个数据时,偏移量至少为4byte(32bit),于是,偏移量的公式如下:

                 f(x)= 4x  (x=0,1,…n)

事实上,我们可以显示的通过setDataPostion(int postion) 来直接操作我们欲读取数据时的偏移量。毫无疑问,你可以设置任何偏移量,但所读取的值是类型可能有误。因此显示设置偏移量读取值的时候,需要小心。

​​另外一个注意点就是我们在writeXXX()和readXXX()时,导致的偏移量是共用的。

例如,我们在writeInt(23)后,此时的datapostion=4,如果我们想读取5,简单的通过readInt()是不行的,只能得到0。这时我们只能通过setDataPosition(0)设置为起始偏移量,从起始位置读取四个字节,即23。因此,在读取某个值时,可能需要使用setDataPostion(int postion)使偏移量装换到我们的值处。

    巧用setDataPosition()方法,当我们的parcel对象中只存在某一类型时,我们就可以通过这个方法来快速的读取所有值。具体方法如下:

/**  
     * 前提条件,Parcel存在多个类型相同的对象,本例子以10个float对象说明:  
     */  
    public void readSameType() {  
        Parcel parcel =Parcel.obtain() ;  
        for (int i = 0; i < 10; i++) {  
            parcel.writeDouble(i);  
            Log.i(TAG, "write double ----> " + getParcelInfo());  
        }  
        //方法一 ,显示设置偏移量   
        int i = 0;  
        int datasize = parcel.dataSize();  
        while (i < datasize) {  
            parcel.setDataPosition(i);  
            double fvalue = parcel.readDouble();  
            Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());  
            i += 8; // double占用字节为 8byte   
        }  
//      方法二,由于对象的类型一致,我们可以直接利用readXXX()读取值会产生偏移量  
//      parcel.setDataPosition(0)  ;  //  
//      while(parcel.dataPosition()<parcel.dataSize()){  
//          double fvalue = parcel.readDouble();  
//          Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());  
//      }  
    }  

说明:

由于可能存在读取值的偏差,一个默认的取值规范为:

1、  读取复杂对象时:

              对象匹配时,返回当前偏移位置的该对象;

              对象不匹配时,返回null对象 ;

2、  读取简单对象时:

              对象匹配时,返回当前偏移位置的该对象 ;

              对象不匹配时,返回0;

     下面,给出一张浅显的Parcel的存放空间图,希望大家在理解的同时,更能体味其中滋味。有点简单,求谅解。

相信通过前面的介绍,你一定很了解了了Parcel的存储机制,下面给定一应用程序来实践。

 1、布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:text="@string/hello" />
	<LinearLayout android:orientation="horizontal"
		android:layout_width="fill_parent" android:layout_height="wrap_content">
		<Button android:id="@+id/btWriteByte" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="写入一个byte值"></Button>
		<Button android:id="@+id/btWriteInt" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="写入一个int值"></Button>
	</LinearLayout>
	<LinearLayout android:orientation="horizontal"
		android:layout_width="fill_parent" android:layout_height="wrap_content">
		<Button android:id="@+id/btWriteDouble" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="写入一个double值"></Button>
		<Button android:id="@+id/btWriteString" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="写入一个String值"></Button>
	</LinearLayout>
	<View android:layout_width="fill_parent" android:layout_height="2dip"
		android:background="#FF1493"></View>
	<LinearLayout android:orientation="horizontal"
		android:layout_marginTop="5dip" android:layout_width="fill_parent"
		android:layout_height="wrap_content">
		<Button android:id="@+id/btReadByte" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="读取一个byte值"></Button>
		<Button android:id="@+id/btReadInt" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="读取一个int值"></Button>
	</LinearLayout>
	<LinearLayout android:orientation="horizontal"
		android:layout_width="fill_parent" android:layout_height="wrap_content">
		<Button android:id="@+id/btReadDouble" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="读取一个double值"></Button>
		<Button android:id="@+id/btReadString" android:layout_width="wrap_content"
			android:layout_height="wrap_content" android:text="读取一个String值"></Button>
	</LinearLayout>
	<View android:layout_width="fill_parent" android:layout_height="2dip"
		android:background="#FF1493"></View>
	<Button android:id="@+id/btSameType" android:layout_width="wrap_content"
		android:layout_height="wrap_content" android:text="利用setDataPosition读取多个值"></Button>
</LinearLayout>

 2、配置文件如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.qinjuning.parcel"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".MainActivity"  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest> 

    3、程序主文件如下:

public class MainActivity extends Activity implements OnClickListener {
 
	private static String TAG = "PARCELTEST";
	// Button ID
	private static int[] btIds = new int[] { R.id.btWriteByte, R.id.btWriteInt,
			R.id.btReadDouble, R.id.btWriteString, R.id.btReadByte,
			R.id.btReadInt, R.id.btReadDouble, R.id.btReadString,
			R.id.btSameType };
	// 每种类型的当前值
	private byte cur_byte = 1; // 每次总写入 false
	private int cur_int = 10; // 写入值 cur_int ++ ;
	private double cur_float = 100.0d; // 写入值 cur_float++ ;
	private String cur_str = "QinJun -->" + cur_int; // 写入值 "QinJun -->"+cur_int
 
	private Parcel parcel = null;
 
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		for (int i = 0; i < btIds.length; i++) {
			Button bt = (Button) findViewById(btIds[i]);
			bt.setOnClickListener(this);
		}
		parcel = Parcel.obtain(); // 获得一个Parcel对象 ,相当于new一个,初始大小为0
		Log.i(TAG, "The original parcel info" + getParcelInfo());
	}
 
	@Override
	public void onClick(View view) {
		// TODO Auto-generated method stub
		int viewId = view.getId();
		switch (viewId) {
		case R.id.btWriteByte:
			parcel.setDataPosition(0);
			parcel.writeByte(cur_byte);
			Log.i(TAG, " after write byte, --->" + getParcelInfo());
			break;
		case R.id.btWriteInt:
			parcel.writeInt(cur_int);
			Log.i(TAG, " after write int, --->" + getParcelInfo());
			break;
		case R.id.btWriteDouble:
			parcel.writeDouble(cur_float);
			Log.i(TAG, " after write float, --->" + getParcelInfo());
			break;
		case R.id.btWriteString:
			parcel.writeString(cur_str);
			Log.i(TAG, " after write String, --->" + getParcelInfo());
			break;
		case R.id.btReadByte:
			byte b = parcel.readByte();
			Log.i(TAG, " read byte is=" + b + ", --->" + getParcelInfo()
					+ "String");
			break;
		case R.id.btReadInt:
			int i = parcel.readInt();
			Log.i(TAG, " read int is=" + i + ", --->" + getParcelInfo());
			break;
		case R.id.btReadDouble:
			float f = parcel.readFloat();
			readSameType();
			Log.i(TAG, " read float is=" + f + ", --->" + getParcelInfo());
			break;
		case R.id.btReadString:
			parcel.setDataPosition(0);
			String str = parcel.readString();
			Log.i(TAG, " read float is=" + str + ", --->" + getParcelInfo());
			break;
		case R.id.btSameType:
			readSameType();
			break;
		default:
			break;
		}
	}
 
	private String getParcelInfo() {// 得到parcel的信息
		return "dataSize = " + parcel.dataSize() + ", dataCapacity="
				+ parcel.dataCapacity() + ", dataPositon = "
				+ parcel.dataPosition();
	}
 
	/**
	 * 前提条件,Parcel存在多个类型相同的对象,本例子以10个float对象说明:
	 */
	public void readSameType() {
		
		for (int i = 0; i < 10; i++) {
			parcel.writeDouble(i);
			Log.i(TAG, "write double ----> " + getParcelInfo());
		}
		//方法一 ,显示设置偏移量 
		int i = 0;
		int datasize = parcel.dataSize();
		while (i < datasize) {
			parcel.setDataPosition(i);
			double fvalue = parcel.readDouble();
			Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());
			i += 8; // double占用字节为 8byte 
		}
//		方法二,由于对象的类型一致,我们可以直接利用readXXX()读取值会产生偏移量
//		parcel.setDataPosition(0)  ;  //
//		while(parcel.dataPosition()<parcel.dataSize()){
//			double fvalue = parcel.readDouble();
//			Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());
//		}
	}
}

​​​​​​​   由于取值时,可能存在类型的转换,因此点击按钮时,可能不会产生预期结果。因此,得保证偏移量对应数值的正确性。


Android Parcel理解

    Parcel是一个存储基本数据类型和引用数据类型的容器,在andorid 中通过IBinder来绑定数据在进程间传递数据。

        Parcel parcel = Parcel.obtain();// 获取一个Parcel 对象

        下面就可以对其方法进行操作了,createXXX(),wirteXXX(),readXXX(),

        其中 dataPosition(),返回当前Parcel对象存储数据的偏移量,而setDataPosition(),设置当前Parcel 对象的偏移量,方便读取parcel 中的数据,可问题就出在读取出来的数据要么是空(null),要么永远是第一个偏移量处的值,存储和读取数据的。Parcel采用什么机制实现的,是以什么形式存储的,然后才能任意对其操作,读取目标数据。

基本数据类型的取值范围,

boolean 1bit

short 16bit

int 32bit

long 64bit

float 32bit

double 64bit

char 16bit

byte 8bit

        由此可以猜想,Parcel 32bit 作为基本单位存储写入的变量,4byte*8=32bit,在内存中的引用地址变量是采用16进制进行编码,且作为偏移量,即偏移量是4的倍数,0,4,8,12,16,20,24,28,32,36,40,44,48......4*N,f(x) = 4*y{y>=0&y是自然数}绝对不会出现向偏移量是3,6,9这样的数据。由此我们可以推断出,无论存储的是基本数据类型或引用数据类型的变量,都是以32bit基本单位作为偏移量,

parcel.writeInt(1);

parcel.writeInt(2);

parcel.writeInt(3);

parcel.writeInt(4);

parcel.writeInt(5);

parcel.writeInt(6);

parcel.writeInt(7);

parcel.writeInt(81011111);

parcel.writeFloat(1f);

parcel.writeFloat(1000000000000000000000000000000000000f);

parcel.writeXXX(), 每写一次数据,在32bit的空间里能够存储要放入的变量,怎只占一个偏移量,也就之一动4个位置,而当存储的数据如 parcel.writeFloat(1000000000000000000000000000000000000f);他就自动往后移动,      

parcel.writeString("a");

parcel.writeString("b");

parcel.writeString("d");

parcel.writeString("c");

parcel.writeString("abcd"); 的区别。有此可见,他的内存的分配原来是这样的。

那我怎样才能把存进去的数据依次的取出来呢?setDataPosition(),设置parcel 的偏移量,在readXXX(),读取数据

int size = parcel.dataSize();

int i = 0;

while (i <= size ) {

parcel.setDataPosition(i);

int curr_int = parcel.readInt();

i+=4;

int j = 0;

j++;

}

由此可见parcel 写入数据是按照32bit 为基本的容器,依次存储写入的数据,基本和引用(其实引用的也是有多个基本数据类型组合而成OBJECTS-属性|方法),读取的时候我们就可以按照这种规律根据目标数据的偏移量的位置(curr_position),以及偏移量的大小(size),,取出已经存进去的数据了

int i = curr_position;

while (i <= size ) {

parcel.setDataPosition(i);

int curr_int = parcel.readXXXt();

i+=4;

int j = 0;

j++;

}

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

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

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

4、对于普通数据,使用的是mData内存地址,对于IBinder类型的数据以及FileDescriptor使用的是mObjects内存地址。后者是通过flatten_binder()和unflatten_binder()实现的,目的是反序列化时读出的对象就是原对象而不用重新new一个新对象。

示例


         Parcel本质上把它当成一个Serialize就可以了,只是它是在内存中完成的序列化和反序列化,利用的是连续的内存空间,因此会更加高效。
         我们接下来要说的是Parcel类如何应用。就应用程序而言,最常见使用Parcel类的场景就是在Activity间传递数据。没错,在Activity间使用Intent传递数据的时候,可以通过Parcelable机制传递复杂的对象。
         在下面的程序中,MyColor用于保存一个颜色值,MainActivity在用户点击屏幕时将MyColor对象设成红色,传递到SubActivity中,此时SubActivity的TextView显示为红色的背景;当点击SubActivity时,将颜色值改为绿色,返回MainActivity,期望的是MainActivity的TextView显示绿色背景。

来看一下MyColor类的实现代码:

package com.wenbin.test;
 
import android.graphics.Color;
import android.os.Parcel;
import android.os.Parcelable;
 

public class MyColor implements Parcelable {
	private int color=Color.BLACK;
	
	MyColor(){
		color=Color.BLACK;
	}
	
	MyColor(Parcel in){
		color=in.readInt();
	}
	
	public int getColor(){
		return color;
	}
	
	public void setColor(int color){
		this.color=color;
	}
	
	@Override
	public int describeContents() {
		return 0;
	}
 
	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeInt(color);
	}
 
        public static final Parcelable.Creator<MyColor> CREATOR
	    = new Parcelable.Creator<MyColor>() {
		public MyColor createFromParcel(Parcel in) {
		    return new MyColor(in);
		}
		
		public MyColor[] newArray(int size) {
		    return new MyColor[size];
		}
	};
}
package com.wenbin.test;
 
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
 
public class MainActivity extends Activity {
    private final int SUB_ACTIVITY=0;
    private MyColor color=new MyColor();
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
 
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		if (requestCode==SUB_ACTIVITY){
			if (resultCode==RESULT_OK){
	        	if (data.hasExtra("MyColor")){
	        		color=data.getParcelableExtra("MyColor");  //Notice
	        		findViewById(R.id.text).setBackgroundColor(color.getColor());
	        	}
			}
		}
	}
 
	@Override
	public boolean onTouchEvent(MotionEvent event){
		if (event.getAction()==MotionEvent.ACTION_UP){
			Intent intent=new Intent();
			intent.setClass(this, SubActivity.class);
			color.setColor(Color.RED);
			intent.putExtra("MyColor", color);
			startActivityForResult(intent,SUB_ACTIVITY);	
		}
		return super.onTouchEvent(event);
	}
 
}
package com.wenbin.test;
 
import android.app.Activity;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.TextView;
 
public class SubActivity extends Activity {
	private MyColor color;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ((TextView)findViewById(R.id.text)).setText("SubActivity");
        Intent intent=getIntent();
        if (intent!=null){
        	if (intent.hasExtra("MyColor")){
        	        color=intent.getParcelableExtra("MyColor");
        		findViewById(R.id.text).setBackgroundColor(color.getColor());
        	}
        }
    }
    
	@Override
	public boolean onTouchEvent(MotionEvent event){
		if (event.getAction()==MotionEvent.ACTION_UP){
			Intent intent=new Intent();
			if (color!=null){
				color.setColor(Color.GREEN);
				intent.putExtra("MyColor", color);
			}
			setResult(RESULT_OK,intent);
			finish();
		}
		return super.onTouchEvent(event);
	}
}

下面是main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    android:id="@+id/text"
    />
</LinearLayout>

         注意在MainActivity的onActivityResult()中,有一句color=data.getParcelableExtra("MyColor"),这说明的是反序列化后是一个新的MyColor对象,因此要想使用这个对象,我们做了这个赋值语句。
         如果数据本身是IBinder类型,那么反序列化的结果就是原对象,而不是新建的对象,很显然,如果是这样的话,在反序列化后在MainActivity中就不再需要color=data.getParcelableExtra("MyColor")这句了。因此,换一种MyColor的实现方法,令其中的int color成员变量使用IBinder类型的成员变量来表示。
         新建一个BinderData类继承自Binder,代码如下:
 

package com.wenbin.test;
 
import android.os.Binder;
 
public class BinderData extends Binder {
	public int color;
}

修改 MyColor 的代码如下:

package com.wenbin.test;
 
import android.graphics.Color;
import android.os.Parcel;
import android.os.Parcelable;
 
public class MyColor implements Parcelable {
	private BinderData data=new BinderData();
	
	MyColor(){
		data.color=Color.BLACK;
	}
	
	MyColor(Parcel in){
		data=(BinderData) in.readValue(BinderData.class.getClassLoader());
	}
	
	public int getColor(){
		return data.color;
	}
	
	public void setColor(int color){
		data.color=color;
	}
	
	@Override
	public int describeContents() {
		return 0;
	}
 
	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeValue(data);
	}
 
    public static final Parcelable.Creator<MyColor> CREATOR
	    = new Parcelable.Creator<MyColor>() {
		public MyColor createFromParcel(Parcel in) {
		    return new MyColor(in);
		}
		
		public MyColor[] newArray(int size) {
		    return new MyColor[size];
		}
	};
}

去掉 MainActivity 的 onActivityResult() 中的 color=data.getParcelableExtra("MyColor") 一句,再次运行程序,结果符合预期。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值