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") 一句,再次运行程序,结果符合预期。