1. 程式人生 > >Android程序間通訊系列-----------程序間的資料傳遞載體Parcel

Android程序間通訊系列-----------程序間的資料傳遞載體Parcel

一、Android中的新的序列化機制

        JAVA中的Serialize機制,其作用是能將資料物件存入位元組流當中,在需要時重新生成物件。主要應用是利用外部儲存裝置儲存物件狀態,以及通過網路傳輸物件等。

        在Android系統中,定位為針對記憶體受限的裝置,因此對效能要求更高,另外系統中採用了新的IPC(程序間通訊)機制,必然要求使用效能更出色的物件傳輸方式。在這樣的環境下,Parcel被設計出來,其定位就是輕量級的高效的物件序列化和反序列化機制。

        如果要在程序之間傳遞一個整數,很簡單,直接傳就行了;如果要傳一個字串,就稍微複雜了點:需先分配一塊可以容納字串的記憶體,然後將字串複製到記憶體中,再傳遞(新手可能問:為啥不直接把字串的引用傳過去呢?因為每個程序有自己的記憶體地址空間,一個程序中的1000地址可能在另一個程序中是100000,java物件的引用根本上還是記憶體地址);再如果要傳遞一個類的例項呢?也是先為類分配記憶體,然後複製一份再傳遞可以嗎?我認為不可以,我至少可以找到一個理由:類中成員除了屬性還有方法,即使屬效能完整傳過去,但還有方法呢?方法是獨立於類物件存在的,所以到另一個程序中再引用同一個方法就要出錯了,還是因為獨立地址空間的原因。

        Android開發中,很經常在各activity之間傳遞資料,而跟據Android的設計架構,即使同一個程式中的Activity都不一定執行在同一個程序中,所以處理資料傳遞時不能總是假設兩個activity都運行於同一程序,那麼只能按程序間傳遞資料來處理,使之具有最廣泛的適應性。

        那麼到底如何在程序之間傳遞類物件呢?簡單來說可以這樣做:在程序A中把類中的非預設值的屬性和類的唯一標誌打成包(這就叫序列化),把這個包傳遞到程序B,程序B接收到包後,根據類的唯一標誌把類創建出來,然後把傳來的屬性更新到類物件中,這樣程序A和程序B中就包含了兩個完全一樣的類物件。

        Parcel

是一種資料的載體,用於承載希望通過IBinder傳送的相關資訊(包括資料和物件引用)。也就是說Parcel是一個容器,它主要用於儲存序列化資料,然後可以通過Binder在程序間傳遞這些資料。Parcel可以包含原始資料型別(用各種對應的方法寫入,比如writeInt(),writeFloat()等),可以包含Parcelable物件,它還包含了一個活動的IBinder物件的引用,這個引用導致另一端接收到一個指向這個IBinder的代理IBinder。

        Parcel的API用於解決不同型別資料的讀寫。這些函式們主要有六種型別。

1、原始資料型別類

        這類方法們主要讀寫原始資料型別。它們是:

writeByte(byte), readByte(), writeDouble(double),readDouble(), writeFloat(float), readFloat(), writeInt(int), readInt(),writeLong(long), readLong(), writeString(String), readString().

2、原始資料型別陣列類

        這類方法用於讀寫原始資料組成的陣列。在向陣列寫資料時先寫入陣列的長度(4個位元組)再寫入資料。讀陣列的方法可以將資料讀到已存在的陣列中,也可以建立並返回一個新陣列。它們是:

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) , readParcelableArray(ClassLoader) 進行讀寫。這些方法們把類的資訊和資料都寫入Parcel,以使將來能使用合適的類裝載器重新構造類的例項。

        還有一些方法提供了更高效的操作Parcelable的途徑,它們是:writeTypedArray(T[], int), writeTypedList(List), readTypedArray(T[],Parcelable.Creator) 和readTypedList(List, Parcelable.Creator)。這些方法不會寫入類的資訊,取而代之的是:讀取時必須能知道資料屬於哪個類並傳入正確的Parcelable.Creator來建立物件而不是直接構造新物件。(更加高效的讀寫單個Parcelable物件的方法是:直接呼叫Parcelable.writeToParcel()和Parcelable.Creator.createFromParcel())

4 Bundles類

        Bundles是一種型別安全的Map型容器,可用於儲存任何不同型別的資料。它具有很多對讀寫資料的效能優化,並且它的型別安全機制避免了當把它的資料封送到Parcel中時由於型別錯誤引起的BUG的除錯的麻煩,可以使用的方法為: writeBundle(Bundle), readBundle(),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)。

二、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;

/**
 * @author 曹文斌
 * http://blog.csdn.net/caowenbin
 *
 */
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];
		}
	};
}

去掉MainActivityonActivityResult()中的color=data.getParcelableExtra("MyColor")一句,再次執行程式,結果符合預期。

相關推薦

Android程序通訊系列-----------程序資料傳遞載體Parcel

一、Android中的新的序列化機制         JAVA中的Serialize機制,其作用是能將資料物件存入位元組流當中,在需要時重新生成物件。主要應用是利用外部儲存裝置儲存物件狀態,以及通過網路傳輸物件等。         在Android系統中,定位為針對記憶體

程序資料傳遞載體——Parcel(一)

    前段時間參與一個專案關於程序間通訊,看人家的程式碼中Parcel用的各種6,可是咱看不懂呀!所以只好在查些資料咯!      所謂Parcel英文直譯就是“打包”的意思。如果程序間通訊直接傳遞引用物件,這樣也只是傳遞了物件的記憶體地址,這樣的行為是 行不通的,畢竟兩

c/c++ linux 程序通訊系列7,使用pthread mutex

linux 程序間通訊系列7,使用pthread mutex #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/shm.h> #include <pthr

c/c++ linux 程序通訊系列4,使用共享記憶體

linux 程序間通訊系列4,使用共享記憶體 1,建立共享記憶體,用到的函式shmget, shmat, shmdt 函式名 功能描述 shmget 建立共享記憶體,返回pic key

c/c++ linux 程序通訊系列3,使用socketpair,pipe

linux 程序間通訊系列3,使用socketpair,pipe 1,使用socketpair,實現程序間通訊,是雙向的。 2,使用pipe,實現程序間通訊 使用pipe關鍵點:fd[0]只能用於接收,fd[1]只能用於傳送,是單向的。 3,使用pipe,用標準輸入往裡寫。 疑問:在

c/c++ linux 程序通訊系列2,使用UNIX_SOCKET

linux 程序間通訊系列2,使用UNIX_SOCKET 1,使用stream,實現程序間通訊 2,使用DGRAM,實現程序間通訊 關鍵點:使用一個臨時的檔案,進行資訊的互傳。 s_un.sun_family = AF_UNIX; strcpy(s_un.sun_path, "

c/c++ linux 程序通訊系列1,使用signal,kill

linux 程序間通訊系列1,使用signal,kill 訊號基本概念:  軟中斷訊號(signal,又簡稱為訊號)用來通知程序發生了非同步事件。程序之間可以互相通過系統呼叫kill傳送軟中斷訊號。核心也可以因為內部事件而給程序傳送訊號,通知程序發生了某個事件。注意,訊號只是用來通知某程序發生了什

程序通訊—MFC三種訊息傳遞資料

MFC訊息型別:訊息字首:wm(系統訊息),rm(註冊訊息),um(自定義訊息) //自定義訊息 void CServerDlg::OnBnClickedButtonUserMessage() { // TODO: 在此新增控制元件通知處理程式程式碼 Upda

多工(程序, 程序通訊-Queue ,程序池)

1. 程序 程式:例如xxx.py這是程式,是一個靜態的 程序:一個程式執行起來後,程式碼+用到的資源 稱之為程序,它是作業系統分配資源的基本單元。 不僅可以通過執行緒完成多工,程序也是可以的 2. 程序的狀態 工作中,任務數往往大於cpu的核數,即一定有一些任務正在

共享記憶體多程序通訊程序同步使用訊號量來實現

Linux 環境下C程式設計指南,通過共享記憶體進行程序間通訊的例子,程序間同步使用訊號量來實現。 程式碼 11-5 使用說明:這是一個簡單的伺服器和客戶端程式,如果啟動程式時不帶引數,則執行伺服器程式; 如果帶引數,則執行客戶端程式,所帶引數只有一個,就是伺服器端所顯

程序通訊-Queue-程序池中的Queue

程序間通訊-Queue from multiprocessing import Queue,Process q = Queue() 程序池中-Queue from multiprocessing import Manager,Pool q = Manager().Queue

Vue2.0-元件通訊、元件傳遞資料方法總結(帶例子)

元件間通訊-傳遞資料 父元件給子元件傳遞資料,子元件需要設定props來宣告自己的預期資料,如果傳遞的資料有‘-’,子元件要用小駝峰形式接受: <div id="app">

Android Service的啟動終止,以及資料傳遞的簡單案例

生命週期方法簡單介紹 startService() 作用:啟動Service服務  手動呼叫startService()後,自動呼叫內部方法:onCreate()、onStartCommand() 

Android程序通訊(二)——傳遞複雜資料

上一篇文章中介紹瞭如何在Android不同程序間傳遞資料,但是傳遞的都是基本資料型別或者String型別,這些都是系統資料型別。如何傳遞自定義資料型別呢? 下面我們來舉個栗子。假設我們有兩個自定義的類,一個是Person類,一個是Car類。一個Person

android程序通訊--Binder

深入理解之程序間通訊–Binder 同一個程式中的兩個方法能夠直接呼叫的根本原因是處於相同的記憶體空間中。兩個不同的應用程式因為不在同一個程序中,他們是沒有辦法直接通過記憶體地址來訪問到對方的函式和變數的。同一個程序中物件的傳遞是傳遞的記憶體地址,這個地址並不是真正的實體地址,而是邏

Android BroadcastReceiver使用,可實現程序通訊

1、建立廣播接收器: /** * 作者:created by meixi * 郵箱:[email protected] * 日期:2018/11/1 09 */ public class MyBroadcastReceiver extends BroadcastReceiver

Android IPC程序通訊(七) Binder連線池

Binder管家之Binder連線池 IPC程序間通訊(四)之AIDL中的AIDL由一個Service進行管理,若是建立10個AIDL業務模組是不是也要建立10個Service來進行管理,那100個呢?顯然繁瑣,怎麼辦麼,用Binder連線池呀! 工作機制: 1.每個業務模組建立其AID

Android IPC程序通訊(六)Socket

網路通訊之Socket 特點:功能強大,可通過網路傳輸位元組流,支援一對多併發即時通訊。 不支援RPC。 服務端實現: public class SorviceSocket extends Service { private static final String TAG

Android IPC程序通訊(四)AIDL

AIDL-Android介面定義語言 一· 1.相比於Messenger AIDL可跨程序呼叫方法。 2.支援資料型別: (1) Java 的原生基本型別(int, long, char, boolean, double等) (2)String 和CharSequence (3) Arr

Android IPC程序通訊(三)Binder

程序間通訊的介質Binder Binder實現了IBinder介面,是android中跨程序通訊的一種方式。是服務端和客戶端通訊的媒介。 Binder的建立: 1.建立自定義類Book.java實現Parcelable介面,以實現序列化可反序列化。 public class Book