探索Android中的Parcel

发表于3年前(2013-01-15 17:26) 阅读( 955)|评论( 06人收藏此文章,我要收藏 1

9月19日成都 OSC 源创会正在报名,送机械键盘和开源无码内裤

一、Android中的Parcel是什么

转自: http://blog.csdn.net/nkmnkm/article/details/6451699 http://blog.csdn.net/luoshengyang/article/details/8498908

Parcel,翻译过来是“打包”的意思。打包干什么呢?是为了序列化。

如果要在进程之间传递一个整数,很简单,直接传就是行了;如果要传一个字符串,就稍微复杂了点:需先分配一块可以容纳字符串的 内存,然后将字符串复制到内存中,再传递(新手可能问:为啥不直接把字符串的引用传过去呢?学过C/C++的地球人都知道:进程有自己的内存地址空间,一 个进程中的1000地址可能在另一个进程中是100000,java对象的引用跟本上还是内存地址);再如果要传递一个类的实例呢?也是先为类分配内存, 然后复制一份再传递可以吗?我认为不可以,我至少可以找到一个理由:类中成员除了属性还有方法,即使属性能完整传过去,但还有方法呢?方法是独立于类对象 存在的,所以到另一个进程中再引用同一个方法就要出错了,还是因为独立地址空间的原因。

Android开发中,很经常在各activity之间传递数据,而跟据Android的设计架构,即使同一个程序中的Activity都不一定运行在同 一个进程中,所以处理数据传递时你不能老假设两个activity都运行于同一进程,那么只能按进程间传递数据来处理,使之具有最广泛的适应性。

   那么到底如何在进程之间传递类对象呢?简单来说可以这样做:在进程A中把类中的非默认值的属性和类的唯一标志打成包(这就叫序列化),把这个包传递到进 程B,进程B接收到包后,跟据类的唯一标志把类创建出来,然后把传来的属性更新到类对象中,这样进程A和进程B中就包含了两个完全一样的类对象。

http://www.cppblog.com/fwxjj/archive/2013/01/14/197252.aspx

二、探索Android中的Parcel机制(上)

转自:http://blog.csdn.net/caowenbin/article/details/6532217(作者:曹文斌)

一.先从Serialize说起

我们都知道JAVA中的Serialize机制,译成串行化、序列化……,其作用是能将数据对象存入字节流当中,在需要时重新生成对象。主要应用是利用外部存储设备保存对象状态,以及通过网络传输对象等。

二.Android中的新的序列化机制

在Android系统中,定位为针对内存受限的设备,因此对性能要求更高,另外系统中采用了新的IPC(进程间通信)机制,必然 要求使用性能更出色的对象传输方式。在这样的环境下,Parcel被设计出来,其定位就是轻量级的高效的对象序列化和反序列化机制。

三.Parcel类的背后

在Framework中有parcel类,源码路径是:

Frameworks/base/core/java/android/os/Parcel.java

典型的源码片断如下:

[html] view plain copy print ?
  1. /**
  2. *WriteanintegervalueintotheparcelatthecurrentdataPosition(),
  3. *growingdataCapacity()ifneeded.
  4. */
  5. publicfinalnativevoidwriteInt(intval);
  6. /**
  7. *WritealongintegervalueintotheparcelatthecurrentdataPosition(),
  8. *growingdataCapacity()ifneeded.
  9. */
  10. publicfinalnativevoidwriteLong(longval);

从中我们看到,从这个源程序文件中我们看不到真正的功能是如何实现的,必须透过JNI往下走了。于是,Frameworks/base/core/jni/android_util_Binder.cpp中找到了线索

[html] view plain copy print ?
  1. staticvoidandroid_os_Parcel_writeInt(JNIEnv*env,jobjectclazz,jintval)
  2. {
  3. Parcel*parcel=parcelForJavaObject(env,clazz);
  4. if(parcel!=NULL){
  5. conststatus_terr=parcel->writeInt32(val);
  6. if(err!=NO_ERROR){
  7. jniThrowException(env,"java/lang/OutOfMemoryError",NULL);
  8. }
  9. }
  10. }
  11. staticvoidandroid_os_Parcel_writeLong(JNIEnv*env,jobjectclazz,jlongval)
  12. {
  13. Parcel*parcel=parcelForJavaObject(env,clazz);
  14. if(parcel!=NULL){
  15. conststatus_terr=parcel->writeInt64(val);
  16. if(err!=NO_ERROR){
  17. jniThrowException(env,"java/lang/OutOfMemoryError",NULL);
  18. }
  19. }
  20. }

从这里我们可以得到的信息是函数的实现依赖于Parcel指针,因此还需要找到Parcel的类定义,注意,这里的类已经是用C++语言实现的了。

找到Frameworks/base/include/binder/parcel.h和Frameworks/base/libs/binder/parcel.cpp。终于找到了最终的实现代码了。

有兴趣的朋友可以自己读一下,不难理解,这里把基本的思路总结一下:

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一个新对象。

三、探索Android中的Parcel机制(下)

上一篇中我们透过源码看到了Parcel背后的机制,本质上把它当成一个Serialize就可以了,只是它是在内存中完成的序列化和反序列化,利用的是连续的内存空间,因此会更加高效。

我们接下来要说的是Parcel类如何应用。就应用程序而言,最常见使用Parcel类的场景就是在Activity间传递数据。没错,在Activity间使用Intent传递数据的时候,可以通过Parcelable机制传递复杂的对象。

在下面的程序中,MyColor用于保存一个颜色值,MainActivity在用户点击屏幕时将MyColor对象设成红色, 传递到SubActivity中,此时SubActivity的TextView显示为红色的背景;当点击SubActivity时,将颜色值改为绿色, 返回MainActivity,期望的是MainActivity的TextView显示绿色背景。

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

[html] view plain copy print ?
  1. packagecom.wenbin.test;
  2. importandroid.graphics.Color;
  3. importandroid.os.Parcel;
  4. importandroid.os.Parcelable;
  5. /**
  6. *@author曹文斌
  7. *http://blog.csdn.net/caowenbin
  8. *
  9. */
  10. publicclassMyColorimplementsParcelable{
  11. privateintcolor=Color.BLACK;
  12. MyColor(){
  13. color=Color.BLACK;
  14. }
  15. MyColor(Parcelin){
  16. color=in.readInt();
  17. }
  18. publicintgetColor(){
  19. returncolor;
  20. }
  21. publicvoidsetColor(intcolor){
  22. this.color=color;
  23. }
  24. @Override
  25. publicintdescribeContents(){
  26. return0;
  27. }
  28. @Override
  29. publicvoidwriteToParcel(Parceldest,intflags){
  30. dest.writeInt(color);
  31. }
  32. publicstaticfinalParcelable.Creator<MyColor>CREATOR
  33. =newParcelable.Creator<MyColor>(){
  34. publicMyColorcreateFromParcel(Parcelin){
  35. returnnewMyColor(in);
  36. }
  37. publicMyColor[]newArray(intsize){
  38. returnnewMyColor[size];
  39. }
  40. };
  41. }


该类实现了Parcelable接口,提供了默认的构造函数,同时也提供了可从Parcel对象开始的构造函数,另外还实现了一个static的构造器用于构造对象和数组。代码很简单,不一一解释了。

再看MainActivity的代码:

[html] view plain copy print ?
  1. packagecom.wenbin.test;
  2. importandroid.app.Activity;
  3. importandroid.content.Intent;
  4. importandroid.graphics.Color;
  5. importandroid.os.Bundle;
  6. importandroid.view.MotionEvent;
  7. /**
  8. *@author曹文斌
  9. *http://blog.csdn.net/caowenbin
  10. *
  11. */
  12. publicclassMainActivityextendsActivity{
  13. privatefinalintSUB_ACTIVITY=0;
  14. privateMyColorcolor=newMyColor();
  15. @Override
  16. publicvoidonCreate(BundlesavedInstanceState){
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.main);
  19. }
  20. @Override
  21. protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){
  22. super.onActivityResult(requestCode,resultCode,data);
  23. if(requestCode==SUB_ACTIVITY){
  24. if(resultCode==RESULT_OK){
  25. if(data.hasExtra("MyColor")){
  26. color=data.getParcelableExtra("MyColor");//Notice
  27. findViewById(R.id.text).setBackgroundColor(color.getColor());
  28. }
  29. }
  30. }
  31. }
  32. @Override
  33. publicbooleanonTouchEvent(MotionEventevent){
  34. if(event.getAction()==MotionEvent.ACTION_UP){
  35. Intentintent=newIntent();
  36. intent.setClass(this,SubActivity.class);
  37. color.setColor(Color.RED);
  38. intent.putExtra("MyColor",color);
  39. startActivityForResult(intent,SUB_ACTIVITY);
  40. }
  41. returnsuper.onTouchEvent(event);
  42. }
  43. }

下面是SubActivity的代码:

[html] view plain copy print ?
  1. packagecom.wenbin.test;
  2. importandroid.app.Activity;
  3. importandroid.content.Intent;
  4. importandroid.graphics.Color;
  5. importandroid.os.Bundle;
  6. importandroid.view.MotionEvent;
  7. importandroid.widget.TextView;
  8. /**
  9. *@author曹文斌
  10. *http://blog.csdn.net/caowenbin
  11. *
  12. */
  13. publicclassSubActivityextendsActivity{
  14. privateMyColorcolor;
  15. @Override
  16. publicvoidonCreate(BundlesavedInstanceState){
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.main);
  19. ((TextView)findViewById(R.id.text)).setText("SubActivity");
  20. Intentintent=getIntent();
  21. if(intent!=null){
  22. if(intent.hasExtra("MyColor")){
  23. color=intent.getParcelableExtra("MyColor");
  24. findViewById(R.id.text).setBackgroundColor(color.getColor());
  25. }
  26. }
  27. }
  28. @Override
  29. publicbooleanonTouchEvent(MotionEventevent){
  30. if(event.getAction()==MotionEvent.ACTION_UP){
  31. Intentintent=newIntent();
  32. if(color!=null){
  33. color.setColor(Color.GREEN);
  34. intent.putExtra("MyColor",color);
  35. }
  36. setResult(RESULT_OK,intent);
  37. finish();
  38. }
  39. returnsuper.onTouchEvent(event);
  40. }
  41. }

下面是main.xml的代码:

[html] view plain copy print ?
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <TextView
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:text="@string/hello"
  11. android:id="@+id/text"
  12. />
  13. </LinearLayout>

注意的是在MainActivity的onActivityResult()中,有一句 color=data.getParcelableExtra("MyColor"),这说明的是反序列化后是一个新的MyColor对象,因此要想使用 这个对象,我们做了这个赋值语句。

记得在上一篇《探索Android中的Parcel机制(上)》 中提到,如果数据本身是IBinder类型,那么反序列化的结果就是原对象,而不是新建的对象,很显然,如果是这样的话,在反序列化后在 MainActivity中就不再需要color=data.getParcelableExtra("MyColor")这句了。因此,换一种 MyColor的实现方法,令其中的int color成员变量使用IBinder类型的成员变量来表示。

新建一个BinderData类继承自Binder,代码如下:

[html] view plain copy print ?
  1. packagecom.wenbin.test;
  2. importandroid.os.Binder;
  3. /**
  4. *@author曹文斌
  5. *http://blog.csdn.net/caowenbin
  6. *
  7. */
  8. publicclassBinderDataextendsBinder{
  9. publicintcolor;
  10. }

修改MyColor的代码如下:

[html] view plain copy print ?
  1. packagecom.wenbin.test;
  2. importandroid.graphics.Color;
  3. importandroid.os.Parcel;
  4. importandroid.os.Parcelable;
  5. /**
  6. *@author曹文斌
  7. *http://blog.csdn.net/caowenbin
  8. *
  9. */
  10. publicclassMyColorimplementsParcelable{
  11. privateBinderDatadata=newBinderData();
  12. MyColor(){
  13. data.color=Color.BLACK;
  14. }
  15. MyColor(Parcelin){
  16. data=(BinderData)in.readValue(BinderData.class.getClassLoader());
  17. }
  18. publicintgetColor(){
  19. returndata.color;
  20. }
  21. publicvoidsetColor(intcolor){
  22. data.color=color;
  23. }
  24. @Override
  25. publicintdescribeContents(){
  26. return0;
  27. }
  28. @Override
  29. publicvoidwriteToParcel(Parceldest,intflags){
  30. dest.writeValue(data);
  31. }
  32. publicstaticfinalParcelable.Creator<MyColor>CREATOR
  33. =newParcelable.Creator<MyColor>(){
  34. publicMyColorcreateFromParcel(Parcelin){
  35. returnnewMyColor(in);
  36. }
  37. publicMyColor[]newArray(intsize){
  38. returnnewMyColor[size];
  39. }
  40. };
  41. }

去掉MainActivity的onActivityResult()中的color=data.getParcelableExtra("MyColor")一句,变成:

[html] view plain copy print ?
  1. @Override
  2. protectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){
  3. super.onActivityResult(requestCode,resultCode,data);
  4. if(requestCode==SUB_ACTIVITY){
  5. if(resultCode==RESULT_OK){
  6. if(data.hasExtra("MyColor")){
  7. findViewById(R.id.text).setBackgroundColor(color.getColor());
  8. }
  9. }
  10. }
  11. }

再次运行程序,结果符合预期。

以上就是Parcel在应用程序中的使用方法,与Serialize还是挺相似的,详细的资料当然还是要参考Android SDK的开发文档了。



三、Android Parcel理解

android 中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++;
}
这样就ok 了
他的createXXX()方法现在没用,用了在说吧!
总结一句话,java 中 基本数据类型的取值范围,引用类型的数据,相当于c中的指针,以及各进制之间的相互转换和灵活的引用,以及定制自己想要的任意进制数据类型。


四、Android开发:什么是Parcel(2)

转自:http://blog.csdn.net/nkmnkm/article/details/6453391

上回书解释了IBinder,这回详细解释一下Parcel,以下是对android sdk 文档的翻议:
Parcel是一个容器,它主要用于存储序列化数据,然后可以通过Binder在进程间传递这些数据(要了解为什么要序列化,请参考:http://blog.csdn.net/nkmnkm/archive/2011/05/28/6451699.aspx)
。Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。

注:Parcel不是一般目的的序列化机制。这个类被设计用于高性能的IPC传输。因此不适合把Parcel写入永久化存储中,因为Parcel中的数据类型的实现的改变会导致旧版的数据不可读。

Parcel的一坨一坨的API用于解决不同类型数据的读写。这些函数们主要有六种类型。

1原始类

这类方法们主要读写原始数据类型。它们是:writeByte(byte), readByte(), writeDouble(double), readDouble(), writeFloat(float), readFloat(), writeInt(int), readInt(), writeLong(long), readLong(), writeString(String), readString().大多数其它数据的操作都是基于这些方法。

2原始数组类

这类方法用于读写原始数据组成的数组。在向数组写数据时先写入数组的长度再写入数据。读数组的方法可以将数据读到已存在的数组中,也可以创建并返回一个新数组。它们是:

  • writeBooleanArray(boolean[]), readBooleanArray(boolean[]), createBooleanArray()
    writeByteArray(byte[]), writeByteArray(byte[], int, int), readByteArray(byte[]), createByteArray()
    writeCharArray(char[]), readCharArray(char[]), createCharArray()
    writeDoubleArray(double[]), readDoubleArray(double[]), createDoubleArray()
    writeFloatArray(float[]), readFloatArray(float[]), createFloatArray()
    writeIntArray(int[]), readIntArray(int[]), createIntArray()
    writeLongArray(long[]), readLongArray(long[]), createLongArray()
    writeStringArray(String[]), readStringArray(String[]), createStringArray().
    writeSparseBooleanArray(SparseBooleanArray), readSparseBooleanArray().

3 Parcelable类
Parcelable为对象从Parcel中读写自己提供了极其高效的协议。你可以使用直接的方法 writeParcelable(Parcelable, int) 和 readParcelable(ClassLoader) 或 writeParcelableArray(T[], int) and readParcelableArray(ClassLoader) 进行读写。这些方法们把类的信息和数据都写入Parcel,以使将来能使用合适的类装载器重新构造类的实例。

还有一些方法提供了更高效的操作Parcelable们的途径,它们是:writeTypedArray(T[], int), writeTypedList(List), readTypedArray(T[], Parcelable.Creator) and readTypedList(List, Parcelable.Creator)。这些方法不会写入类的信息,取而代之的是:读取时必须能知道数据属于哪个类并传入正确的 Parcelable.Creator来创建对象而不是直接构造新对象。(更加高效的读写单个Parcelable对象的方法是:直接调用 Parcelable.writeToParcel()和Parcelable.Creator.createFromParcel())

4 Bundles类

Bundles是一种类型安全的Map型容器,可用于存储任何不同类型的数据。它具有很多对讀写数据的性能优化,并且它的类型安全机制避免了当把它的数据 封送到Parcel中时由于类型错误引起的BUG的调试的麻烦,可以使用的方法为: writeBundle(Bundle), readBundle(), and readBundle(ClassLoader)。

5 活动对象类

Parcel的一个非同寻常的特性是读写活对象的能力。对于活动对象,它们的内容实际上并没有写入,而是仅写入了一个令牌来引用这个对象。当从Parcel中读取这个对象时,你不会获取一个新的对象实例,而是直接得到那个写入的对象。有两种活动对象可操作:

Binder对象。它是 Android跨进程通讯的基础。这种对象可被写入Parcel,并在读取时你将得到原始的对象或一个代理对象(可以想象:在进程内时得到原始的对象,在 进程间时得到代理对象)。可以使用的方法们是: writeStrongBinder(IBinder), writeStrongInterface(IInterface), readStrongBinder(), writeBinderArray(IBinder[]), readBinderArray(IBinder[]), createBinderArray(), writeBinderList(List), readBinderList(List), createBinderArrayList()。

FileDescriptor对象。 它代表了原始的Linux文件描述符,它可以被写入Parcel并在读取时返回一个ParcelFileDescriptor对象用于操作原始的文件描述 符。ParcelFileDescriptor是原始描述符的一个复制:对象和fd不同,但是都操作于同一文件流,使用同一个文件位置指针,等等。可以使 用的方法是:writeFileDescriptor(FileDescriptor), readFileDescriptor()。

6无类型容器类

一类final方法,用于读写标准的java容器类。这些方法们是:writeArray(Object[]), readArray(ClassLoader), writeList(List), readList(List, ClassLoader), readArrayList(ClassLoader), writeMap(Map), readMap(Map, ClassLoader), writeSparseArray(SparseArray), readSparseArray(ClassLoader)。

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. mybatisplus的坑 insert标签insert into select无参数问题的解决
  3. python起点网月票榜字体反爬案例
  4. 类和 Json对象
  5. Python list sort方法的具体使用
  6. python list.sort()根据多个关键字排序的方法实现
  7. android上一些方法的区别和用法的注意事项
  8. 《Android开发从零开始》——25.数据存储(4)
  9. android实现字体闪烁动画的方法

随机推荐

  1. 如何获取和安装Android(安卓)L开发者预览
  2. Android:学习AIDL,这一篇文章就够了(下)
  3. Android无障碍服务三 创建辅助功能服务
  4. android自定义相机拍照
  5. 一起来开发Android的天气软件(一)——功能
  6. Android(安卓)App列表之圆角ListView
  7. 【Android】Handler使用入门 处理耗时较
  8. Android应用程序开发以及背后的设计思想
  9. Android之Handler的用法
  10. Android触摸滑动全解(三)——View坐标体系