您的位置:首页 > 移动开发 > Android开发

Android进程间通信系列-----------进程间的数据传递载体Parcel

2015-06-02 08:57 537 查看

感谢http://blog.csdn.net/caowenbin

一、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];
}
};
}


去掉MainActivity的onActivityResult()中的color=data.getParcelableExtra("MyColor")一句,再次运行程序,结果符合预期。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: