Android中Parcel的分析以及使用

摘要:
简单地说:Parcel是存储读取数据的容器。Android系统中的绑定进程间通信(IPC)使用Parcel类在客户端和服务器之间交互数据,AIDL数据也通过Parcel进行交互。Parcel在Java空间和C++中实现。因为它直接使用内存读取C/C++中的数据,所以效率更高。分析Binder机制中客户端和服务器之间的实际操作

简单点来说: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的分析以及使用第1张

 

相信通过前面的介绍,你一定很了解了了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.    

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

免责声明:文章转载自《Android中Parcel的分析以及使用》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇MyBatis Plus 批量数据插入功能,yyds!hihoCoder #1151 : 骨牌覆盖问题·二 (矩阵快速幂,DP)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Flutter实战视频-移动电商-39.路由_Fluro的路由配置和静态化

39.路由_Fluro的路由配置和静态化 handler只是单个路由的配置,这节课我们要学习路由的整体配置 整体配置 新建routers.dart文件来做整体配置 detailsHandler就是我们在router_handler里面定义的detailsHandler 当路由不存在的时候,给用户一个反馈。router.notFoundHandler 这...

jdk版本比较

JDK各个版本的新特性   对于很多刚接触java语言的初学者来说,要了解一门语言,最好的方式就是要能从基础的版本进行了解,升级的过程,以及升级的新特性,这样才能循序渐进的学好一门语言。今天先为大家介绍一下JDK1.5版本到JDK1.7版本的特性。希望能给予帮助。 JDK1.5新特性:   1.自动装箱与拆箱: 自动装箱的过程:每当需要一种类型的对...

关于将桌面扩展到监视器的问题 extended my windows desktop onto this monitor

说下思路吧 下面是网上找的Use the EnumDisplayDevices() API call to enumerate the display devices on the system and look for those that don't have the DISPLAY_DEVICE_ATTACHED_TO_DESKTOP flag se...

结对项目:一寸时光APP(日程管理)二

建立数据库 package com.example.myapplication3.db; import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.database.sqlite.SQLi...

XmlDocument的应用创建Xml模板

 protected void btnCreate_Click(object sender, EventArgs e)        {            //定义XMLDocument            XmlDocument xmlDocument = new XmlDocument();            //定义XML文档头文件   ...

Redis系统学习之自定义RedisTemplate

自定义RedisTemplate 序列化源码分析 在JAVA程序中看到中文是没有问题的,但是在Redis客户端工具,也就是命令行中看见是编码的 继续分析源码 查看RedisTemplate.class 在RedisAutoConfiguration.class中点击 在上面可以看到序列化支持的 往下稍微滑动一些可以看到,默认采用的是JDK的序列化,...