Android中Parcel的分析以及使用

智慧并不产生于学历,而是来自对于知识的终生不懈的追求。这篇文章主要讲述Android中Parcel的分析以及使用相关的知识,希望能为你提供帮助。
简单点来说:Parcel就是一个存放读取数据的容器,  Android系统中的binder进程间通信(IPC)就使用了Parcel类来进行客户端与服务端数据的交互,而且AIDL的数据也是通过Parcel来交互的。在java空间和C++都实现了Parcel,由于它在C/C++中,直接使用了内存来读取数据,因此,它更有效率。
分析Binder机制中的客户端与服务器端进行实际操作ontransact()函数 :
[java]

  1. //参数说明:     
  2. //  code  :是请求的ID号       
  3. //  data  :客户端请求发送的参数     
  4. //  reply:服务器端返回的结果     
  5. //  flags:一些额外的标识,如FLAG_ONEWAY等,通常为0.     
  6. virtual  status_t        onTransact(  uint32_t  code,   
  7.                                                                 const  Parcel&   data,   
  8.                                                                 Parcel*  reply,   
  9.                                                                 uint32_t  flags  =  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的分析
 
            相信看了前面的值,对Parcel的使用该有了初步印象。那么,Parcel的内部存储机制是怎么样的?偏移量又是
  什么情况?让我们回忆一下基本数据类型的取值范围:
                                    boolean        1bit                  1字节
                                    char                  16bit                2字节
                                    int                        32bit              4字节
                                    long                  64bit              8字节
                                    float                  32bit              4字节
                                  double            64bit                8字节
 
              如果大家对C语言熟悉的话,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对象中只存在某一类型时,我们就可以通过这个方法来快速的读取
所有值。具体方法如下:
 
[html]
  1. /**   
  2.           *  前提条件,Parcel存在多个类型相同的对象,本例子以10个float对象说明:   
  3.           */   
  4.         public  void  readSameType()  {   
  5.                 Parcel  parcel  =Parcel.obtain()  ;    
  6.                 for  (int  i  =  0;   i  <   10;   i++)  {   
  7.                         parcel.writeDouble(i);    
  8.                         Log.i(TAG,  "write  double  ---->   "  +  getParcelInfo());    
  9.                 }   
  10.                 //方法一  ,显示设置偏移量     
  11.                 int  i  =  0;    
  12.                 int  datasize  =  parcel.dataSize();    
  13.                 while  (i  <   datasize)  {   
  14.                         parcel.setDataPosition(i);    
  15.                         double  fvalue  =  parcel.readDouble();    
  16.                         Log.i(TAG,  "  read  double  is="  +  fvalue  +  ",  ---> "  +  getParcelInfo());    
  17.                         i  +=  8;   //  double占用字节为  8byte     
  18.                 }   
  19. //            方法二,由于对象的类型一致,我们可以直接利用readXXX()读取值会产生偏移量   
  20. //            parcel.setDataPosition(0)    ;     //   
  21. //            while(parcel.dataPosition()< parcel.dataSize()){   
  22. //                    double  fvalue  =  parcel.readDouble();    
  23. //                    Log.i(TAG,  "  read  double  is="  +  fvalue  +  ",  ---> "  +  getParcelInfo());    
  24. //            }   
  25.         }   
 
        由于可能存在读取值的偏差,一个默认的取值规范为:
                        1、  读取复杂对象时: 对象匹配时,返回当前偏移位置的该对象;
                                                            对象不匹配时,返回null对象 ;
                        2、  读取简单对象时: 对象匹配时,返回当前偏移位置的该对象 ;
                                                            对象不匹配时,返回0;   
下面,给出一张浅显的Parcel的存放空间图,希望大家在理解的同时,更能体味其中滋味。有点简单,求谅解。
【Android中Parcel的分析以及使用】                             
Android中Parcel的分析以及使用

文章图片

 
相信通过前面的介绍,你一定很了解了了Parcel的存储机制,下面给定一应用程序来实践。
 
        1、布局文件如下:
[html]
  1. < ?xml  version="1.0"  encoding="utf-8"?>    
  2. < LinearLayout  xmlns:Android="http://schemas.android.com/apk/res/android"   
  3.         android:orientation="vertical"  android:layout_width="fill_parent"   
  4.         android:layout_height="fill_parent">    
  5.         < TextView  android:layout_width="fill_parent"   
  6.                 android:layout_height="wrap_content"  android:text="@string/hello"  />    
  7.         < LinearLayout  android:orientation="horizontal"   
  8.                 android:layout_width="fill_parent"  android:layout_height="wrap_content">    
  9.                 < Button  android:id="@+id/btWriteByte"  android:layout_width="wrap_content"   
  10.                         android:layout_height="wrap_content"  android:text="写入一个byte值"> < /Button>    
  11.                 < Button  android:id="@+id/btWriteInt"  android:layout_width="wrap_content"   
  12.                         android:layout_height="wrap_content"  android:text="写入一个int值"> < /Button>    
  13.         < /LinearLayout>    
  14.         < LinearLayout  android:orientation="horizontal"   
  15.                 android:layout_width="fill_parent"  android:layout_height="wrap_content">    
  16.                 < Button  android:id="@+id/btWriteDouble"  android:layout_width="wrap_content"   
  17.                         android:layout_height="wrap_content"  android:text="写入一个double值"> < /Button>    
  18.                 < Button  android:id="@+id/btWriteString"  android:layout_width="wrap_content"   
  19.                         android:layout_height="wrap_content"  android:text="写入一个String值"> < /Button>    
  20.         < /LinearLayout>    
  21.         < View  android:layout_width="fill_parent"  android:layout_height="2dip"   
  22.                 android:background="#FF1493"> < /View>    
  23.         < LinearLayout  android:orientation="horizontal"   
  24.                 android:layout_marginTop="5dip"  android:layout_width="fill_parent"   
  25.                 android:layout_height="wrap_content">    
  26.                 < Button  android:id="@+id/btReadByte"  android:layout_width="wrap_content"   
  27.                         android:layout_height="wrap_content"  android:text="读取一个byte值"> < /Button>    
  28.                 < Button  android:id="@+id/btReadInt"  android:layout_width="wrap_content"   
  29.                         android:layout_height="wrap_content"  android:text="读取一个int值"> < /Button>    
  30.         < /LinearLayout>    
  31.         < LinearLayout  android:orientation="horizontal"   
  32.                 android:layout_width="fill_parent"  android:layout_height="wrap_content">    
  33.                 < Button  android:id="@+id/btReadDouble"  android:layout_width="wrap_content"   
  34.                         android:layout_height="wrap_content"  android:text="读取一个double值"> < /Button>    
  35.                 < Button  android:id="@+id/btReadString"  android:layout_width="wrap_content"   
  36.                         android:layout_height="wrap_content"  android:text="读取一个String值"> < /Button>    
  37.         < /LinearLayout>    
  38.         < View  android:layout_width="fill_parent"  android:layout_height="2dip"   
  39.                 android:background="#FF1493"> < /View>    
  40.         < Button  android:id="@+id/btSameType"  android:layout_width="wrap_content"   
  41.                 android:layout_height="wrap_content"  android:text="利用setDataPosition读取多个值"> < /Button>    
  42. < /LinearLayout>      
  2、配置文件如下:
[html]
  1. < ?xml  version="1.0"  encoding="utf-8"?>    
  2. < manifest  xmlns:android="http://schemas.android.com/apk/res/android"   
  3.             package="com.qinjuning.parcel"   
  4.             android:versionCode="1"   
  5.             android:versionName="1.0">    
  6.         < application  android:icon="@drawable/icon"  android:label="@string/app_name">    
  7.                 < activity  android:name=".MainActivity"    android:label="@string/app_name">    
  8.                         < intent-filter>    
  9.                                 < action  android:name="android.intent.action.MAIN"  />    
  10.                                 < category  android:name="android.intent.category.LAUNCHER"  />    
  11.                         < /intent-filter>    
  12.                 < /activity>    
  13.         < /application>    
  14. < /manifest>      
 
        3、程序主文件如下:
[html]
  1. public  class  MainActivity  extends  Activity  implements  OnClickListener  {   
  2.    
  3.         private  static  String  TAG  =  "PARCELTEST";    
  4.         //  Button  ID   
  5.         private  static  int[]  btIds  =  new  int[]  {  R.id.btWriteByte,  R.id.btWriteInt,   
  6.                         R.id.btReadDouble,  R.id.btWriteString,  R.id.btReadByte,   
  7.                         R.id.btReadInt,  R.id.btReadDouble,  R.id.btReadString,   
  8.                         R.id.btSameType  };    
  9.         //  每种类型的当前值   
  10.         private  byte  cur_byte  =  1;   //  每次总写入  false   
  11.         private  int  cur_int  =  10;   //  写入值  cur_int  ++  ;    
  12.         private  double  cur_float  =  100.0d;   //  写入值  cur_float++  ;    
  13.         private  String  cur_str  =  "QinJun  --> "  +  cur_int;   //  写入值  "QinJun  --> "+cur_int   
  14.    
  15.         private  Parcel  parcel  =  null;    
  16.    
  17.         @Override   
  18.         public  void  onCreate(Bundle  savedInstanceState)  {   
  19.                 super.onCreate(savedInstanceState);    
  20.                 setContentView(R.layout.main);    
  21.                 for  (int  i  =  0;   i  <   btIds.length;   i++)  {   
  22.                         Button  bt  =  (Button)  findViewById(btIds[i]);    
  23.                         bt.setOnClickListener(this);    
  24.                 }   
  25.                 parcel  =  Parcel.obtain();   //  获得一个Parcel对象  ,相当于new一个,初始大小为0   
  26.                 Log.i(TAG,  "The  original  parcel  info"  +  getParcelInfo());    
  27.         }   
  28.    
  29.         @Override   
  30.         public  void  onClick(View  view)  {   
  31.                 //  TODO  Auto-generated  method  stub   
  32.                 int  viewviewId  =  view.getId();    
  33.                 switch  (viewId)  {   
  34.                 case  R.id.btWriteByte:   
  35.                         parcel.setDataPosition(0);    
  36.                         parcel.writeByte(cur_byte);    
  37.                         Log.i(TAG,  "  after  write  byte,  ---> "  +  getParcelInfo());    
  38.                         break;    
  39.                 case  R.id.btWriteInt:   
  40.                         parcel.writeInt(cur_int);    
  41.                         Log.i(TAG,  "  after  write  int,  ---> "  +  getParcelInfo());    
  42.                         break;    
  43.                 case  R.id.btWriteDouble:   
  44.                         parcel.writeDouble(cur_float);    
  45.                         Log.i(TAG,  "  after  write  float,  ---> "  +  getParcelInfo());    
  46.                         break;    
  47.                 case  R.id.btWriteString:   
  48.                         parcel.writeString(cur_str);    
  49.                         Log.i(TAG,  "  after  write  String,  ---> "  +  getParcelInfo());    
  50.                         break;    
  51.                 case  R.id.btReadByte:   
  52.                         byte  b  =  parcel.readByte();    
  53.                         Log.i(TAG,  "  read  byte  is="  +  b  +  ",  ---> "  +  getParcelInfo()   
  54.                                         +  "String");    
  55.                         break;    
  56.                 case  R.id.btReadInt:   
  57.                         int  i  =  parcel.readInt();    
  58.                         Log.i(TAG,  "  read  int  is="  +  i  +  ",  ---> "  +  getParcelInfo());    
  59.                         break;    
  60.                 case  R.id.btReadDouble:   
  61.                         float  f  =  parcel.readFloat();    
  62.                         readSameType();    
  63.                         Log.i(TAG,  "  read  float  is="  +  f  +  ",  ---> "  +  getParcelInfo());    
  64.                         break;    
  65.                 case  R.id.btReadString:   
  66.                         parcel.setDataPosition(0);    
  67.                         String  str  =  parcel.readString();    
  68.                         Log.i(TAG,  "  read  float  is="  +  str  +  ",  ---> "  +  getParcelInfo());    
  69.                         break;    
  70.                 case  R.id.btSameType:   
  71.                         readSameType();    
  72.                         break;    
  73.                 default:   
  74.                         break;    
  75.                 }   
  76.         }   
  77.    
  78.         private  String  getParcelInfo()  {//  得到parcel的信息   
  79.                 return  "dataSize  =  "  +  parcel.dataSize()  +  ",  dataCapacity="   
  80.                                 +  parcel.dataCapacity()  +  ",  dataPositon  =  "   
  81.                                 +  parcel.dataPosition();    
  82.         }   
  83.    
  84.         /**   
  85.           *  前提条件,Parcel存在多个类型相同的对象,本例子以10个float对象说明:   
  86.           */   
  87.         public  void  readSameType()  {   
  88.                    
  89.                 for  (int  i  =  0;   i  <   10;   i++)  {   
  90.                         parcel.writeDouble(i);    
  91.                         Log.i(TAG,  "write  double  ---->   "  +  getParcelInfo());    
  92.                 }   
  93.                 //方法一  ,显示设置偏移量     
  94.                 int  i  =  0;    
  95.                 int  datasize  =  parcel.dataSize();    
  96.                 while  (i  <   datasize)  {   
  97.                         parcel.setDataPosition(i);    
  98.                         double  fvalue  =  parcel.readDouble();    
  99.                         Log.i(TAG,  "  read  double  is="  +  fvalue  +  ",  ---> "  +  getParcelInfo());    
  100.                         i  +=  8;   //  double占用字节为  8byte     
  101.                 }   
  102. //            方法二,由于对象的类型一致,我们可以直接利用readXXX()读取值会产生偏移量   
  103. //            parcel.setDataPosition(0)    ;     //   
  104. //            while(parcel.dataPosition()< parcel.dataSize()){   
  105. //                    double  fvalue  =  parcel.readDouble();    
  106. //                    Log.i(TAG,  "  read  double  is="  +  fvalue  +  ",  ---> "  +  getParcelInfo());    
  107. //            }   
  108.         }   
  109. }   
  110.      
由于取值时,可能存在类型的转换,因此点击按钮时,可能不会产生预期结果。因此,得保证偏移量对应数值的正确性。

    推荐阅读