1. 程式人生 > >Android中Parcel的分析以及使用

Android中Parcel的分析以及使用

label nbsp default ext ews nal itl activit tag

簡單點來說: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的存放空間圖,希望大家在理解的同時,更能體味其中滋味。有點簡單,求諒解。

技術分享圖片

相信通過前面的介紹,你一定很了解了了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. private static String TAG = "PARCELTEST";
  3. // Button ID
  4. private static int[] btIds = new int[] { R.id.btWriteByte, R.id.btWriteInt,
  5. R.id.btReadDouble, R.id.btWriteString, R.id.btReadByte,
  6. R.id.btReadInt, R.id.btReadDouble, R.id.btReadString,
  7. R.id.btSameType };
  8. // 每種類型的當前值
  9. private byte cur_byte = 1; // 每次總寫入 false
  10. private int cur_int = 10; // 寫入值 cur_int ++ ;
  11. private double cur_float = 100.0d; // 寫入值 cur_float++ ;
  12. private String cur_str = "QinJun -->" + cur_int; // 寫入值 "QinJun -->"+cur_int
  13. private Parcel parcel = null;
  14. @Override
  15. public void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.main);
  18. for (int i = 0; i < btIds.length; i++) {
  19. Button bt = (Button) findViewById(btIds[i]);
  20. bt.setOnClickListener(this);
  21. }
  22. parcel = Parcel.obtain(); // 獲得一個Parcel對象 ,相當於new一個,初始大小為0
  23. Log.i(TAG, "The original parcel info" + getParcelInfo());
  24. }
  25. @Override
  26. public void onClick(View view) {
  27. // TODO Auto-generated method stub
  28. int viewviewId = view.getId();
  29. switch (viewId) {
  30. case R.id.btWriteByte:
  31. parcel.setDataPosition(0);
  32. parcel.writeByte(cur_byte);
  33. Log.i(TAG, " after write byte, --->" + getParcelInfo());
  34. break;
  35. case R.id.btWriteInt:
  36. parcel.writeInt(cur_int);
  37. Log.i(TAG, " after write int, --->" + getParcelInfo());
  38. break;
  39. case R.id.btWriteDouble:
  40. parcel.writeDouble(cur_float);
  41. Log.i(TAG, " after write float, --->" + getParcelInfo());
  42. break;
  43. case R.id.btWriteString:
  44. parcel.writeString(cur_str);
  45. Log.i(TAG, " after write String, --->" + getParcelInfo());
  46. break;
  47. case R.id.btReadByte:
  48. byte b = parcel.readByte();
  49. Log.i(TAG, " read byte is=" + b + ", --->" + getParcelInfo()
  50. + "String");
  51. break;
  52. case R.id.btReadInt:
  53. int i = parcel.readInt();
  54. Log.i(TAG, " read int is=" + i + ", --->" + getParcelInfo());
  55. break;
  56. case R.id.btReadDouble:
  57. float f = parcel.readFloat();
  58. readSameType();
  59. Log.i(TAG, " read float is=" + f + ", --->" + getParcelInfo());
  60. break;
  61. case R.id.btReadString:
  62. parcel.setDataPosition(0);
  63. String str = parcel.readString();
  64. Log.i(TAG, " read float is=" + str + ", --->" + getParcelInfo());
  65. break;
  66. case R.id.btSameType:
  67. readSameType();
  68. break;
  69. default:
  70. break;
  71. }
  72. }
  73. private String getParcelInfo() {// 得到parcel的信息
  74. return "dataSize = " + parcel.dataSize() + ", dataCapacity="
  75. + parcel.dataCapacity() + ", dataPositon = "
  76. + parcel.dataPosition();
  77. }
  78. /**
  79. * 前提條件,Parcel存在多個類型相同的對象,本例子以10個float對象說明:
  80. */
  81. public void readSameType() {
  82. for (int i = 0; i < 10; i++) {
  83. parcel.writeDouble(i);
  84. Log.i(TAG, "write double ----> " + getParcelInfo());
  85. }
  86. //方法一 ,顯示設置偏移量
  87. int i = 0;
  88. int datasize = parcel.dataSize();
  89. while (i < datasize) {
  90. parcel.setDataPosition(i);
  91. double fvalue = parcel.readDouble();
  92. Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());
  93. i += 8; // double占用字節為 8byte
  94. }
  95. // 方法二,由於對象的類型一致,我們可以直接利用readXXX()讀取值會產生偏移量
  96. // parcel.setDataPosition(0) ; //
  97. // while(parcel.dataPosition()<parcel.dataSize()){
  98. // double fvalue = parcel.readDouble();
  99. // Log.i(TAG, " read double is=" + fvalue + ", --->" + getParcelInfo());
  100. // }
  101. }
  102. }

由於取值時,可能存在類型的轉換,因此點擊按鈕時,可能不會產生預期結果。因此,得保證偏移量對應數值的正確性。

Android中Parcel的分析以及使用