Android Bundle总结

Bundle 介绍

官方文档对Bundle的说明如下:

A mapping from String values to various Parcelable types.

官方意为Bundle封装了String值到各种Parcelable类型数据的映射,可见跟我们上述理解是吻合的。

Bundle源码分析

知道了Bundle的主要作用,再来看源码就容易理解了。

Bundle位于android.os包中,是一个final类,这就注定了Bundle不能被继承。Bundle继承自BaseBundle并实现了Cloneable和Parcelable两个接口,因此对Bundle源码的分析会结合着对BaseBundle源码进行分析。由于实现了Cloneable和Parcelable接口,因此以下几个重载是必不可少的:

public Object clone()
public int describeContents()
public void writeToParcel(Parcel parcel, int flags)
public void readFromParcel(Parcel parcel)
public static final Parcelable.Creator CREATOR = new Parcelable.Creator()

以上代码无需过多解释。

Bundle的几个公有构造方法

公有构造方法

public Bundle()

Constructs a new, empty Bundle

public Bundle(ClassLoader loader)   

Constructs a new, empty Bundle that uses a specific ClassLoader for instantiating Parcelable and Serializable objects.

public Bundle(int capacity) 

Constructs a new, empty Bundle sized to hold the given number of elements.

public Bundle(Bundle b)

Constructs a Bundle containing a copy of the mappings from the given Bundle.

public Bundle(PersistableBundle b)

Constructs a Bundle containing a copy of the mappings from the given PersistableBundle.

第5个构造函数中的PersistableBundle也是继承自BaseBundle的,Bundle还提供了一个静态方法,用来返回只包含一个键值对的Bundle对象,具体源码如下:

public static Bundle forPair(String key, String value) {
    Bundle b = new Bundle(1);
    b.putString(key, value);
    return b;
}

Bundle的put与get方法族

Bundle的功能是用来保存数据,那么必然提供了一系列存取数据的方法,这些方法太多了,几乎能够存取任何类型的数据,例如:

相关保存方法 相关读取方法
public void putBoolean(String key, boolean value) public boolean getBoolean(String key)
public void putByte(String key, byte value) public byte getByte(String key)
public void putChar(String key, char value) public char getChar(String key) public char getChar(String key)

更多的存取方法可以具体参考API文档说明。
除了上述存取数据涉及到的方法外,Bundle还提供了一个clear方法:public void clear(),该方法可用于移除Bundle中的所有数据。

Bundle之所以能以键值对的方式存储数据,实质上是因为它内部维护了一个ArrayMap,具体定义是在其父类BaseBundle中:

// Invariant - exactly one of mMap / mParcelledData will be null
    // (except inside a call to unparcel)

    ArrayMap<String, Object> mMap = null;

ArrayMap

Bundle存取数据的具体实现,以putBoolean方法为例:

布尔数据的存储源码如下:

/**
 * Inserts a Boolean value into the mapping of this Bundle, replacing
 * any existing value for the given key.  Either key or value may be null.
 *
 * @param key a String, or null
 * @param value a Boolean, or null
 */
void putBoolean(String key, boolean value) {
    unparcel();
    mMap.put(key, value);
}

这里的mMap就是ArrayMap了,存储数据就是把键值对保存到ArrayMap里。

布尔类型数据的读取源码如下:

/**
 * Returns the value associated with the given key, or false if
 * no mapping of the desired type exists for the given key.
 *
 * @param key a String
 * @return a boolean value
 */
boolean getBoolean(String key) {
    unparcel();
    if (DEBUG) Log.d(TAG, "Getting boolean in "+ Integer.toHexString(System.identityHashCode(this)));
    return getBoolean(key, false);
}

getBoolean(String key, boolean defaultValue)的具体实现如下:

/**
 * Returns the value associated with the given key, or defaultValue if
 * no mapping of the desired type exists for the given key.
 *
 * @param key a String
 * @param defaultValue Value to return if key does not exist
 * @return a boolean value
 */
boolean getBoolean(String key, boolean defaultValue) {
    unparcel();
    Object o = mMap.get(key);
    if (o == null) {
        return defaultValue;
    }
    try {
        return (Boolean) o;
    } catch (ClassCastException e) {
        typeWarning(key, o, "Boolean", defaultValue, e);
        return defaultValue;
    }
}

数据读取的逻辑也很简单,就是通过key从ArrayMap里读出保存的数据,并转换成对应的类型返回,当没找到数据或发生类型转换异常时返回缺省值。

注意到这里出现了一个方法:unparcel(),它的具体源码如下:

/**
 * If the underlying data are stored as a Parcel, unparcel them
 * using the currently assigned class loader.
 */

/* package */ synchronized void unparcel() {
    if (mParcelledData == null) {
        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                + ": no parcelled data");
        return;
    }

    if (mParcelledData == EMPTY_PARCEL) {
        if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                + ": empty");
        if (mMap == null) {
            mMap = new ArrayMap(1);
        } else {
            mMap.erase();
        }
        mParcelledData = null;
        return;
    }

    int N = mParcelledData.readInt();
    if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
            + ": reading " + N + " maps");
    if (N < 0) {
        return;
    }
    if (mMap == null) {
        mMap = new ArrayMap(N);
    } else {
        mMap.erase();
        mMap.ensureCapacity(N);
    }
    mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
    mParcelledData.recycle();
    mParcelledData = null;
    if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + " final map: " + mMap);
}

先来看下BaseBundle中mParcelledData的定义:

/*
 * If mParcelledData is non-null, then mMap will be null and the
 * data are stored as a Parcel containing a Bundle.  When the data
 * are unparcelled, mParcelledData willbe set to null.
 */
Parcel mParcelledData = null;

在大部分情况下mParcelledData都是null,因此unparcel()直接返回。当使用构造函数public Bundle(Bundle b)创建Bundle时,会给mParcelledData赋值,具体实现如下:

/**
 * Constructs a Bundle containing a copy of the mappings from the given
 * Bundle.
 *
 * @param b a Bundle to be copied.
 */
BaseBundle(BaseBundle b) {
    if (b.mParcelledData != null) {
        if (b.mParcelledData == EMPTY_PARCEL) {
            mParcelledData = EMPTY_PARCEL;
        } else {
            mParcelledData = Parcel.obtain();
            mParcelledData.appendFrom(b.mParcelledData, 0, b.mParcelledData.dataSize());
            mParcelledData.setDataPosition(0);
        }
    } else {
        mParcelledData = null;
    }

    if (b.mMap != null) {
        mMap = new ArrayMap(b.mMap);
    } else {
        mMap = null;
    }

    mClassLoader = b.mClassLoader;
}

从上述代码片段可以知道mParcelledData的取值有3种情况:

mParcelledData = EMPTY_PARCEL
mParcelledData = Parcel.obtain()
mParcelledData = null

在unparcel()方法中就对上述几种情况做了不同的处理,当mParcelledData为null时,直接返回;当mParcelledData为EMPTY_PARCEL时,会创建一个容量为1的ArrayMap对象;当mParcelledData为Parcel.obtain()时,则会将里面的数据读出,并创建一个ArrayMap,并将数据存储到ArrayMap对象里面,同时将mParcelledData回收并置为null,具体是由以下代码片段实现的:

    int N = mParcelledData.readInt();
    if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
            + ": reading " + N + " maps");
    if (N < 0) {
        return;
    }
    if (mMap == null) {
        mMap = new ArrayMap<String, Object>(N);
    } else {
        mMap.erase();
        mMap.ensureCapacity(N);
    }
    mParcelledData.readArrayMapInternal(mMap, N, mClassLoader);
    mParcelledData.recycle();
    mParcelledData = null;

上面只是以布尔类型的数据为例分析了Bundle的存取过程,其他数据类型的存取原理相同,就不再赘述。

为什么是Bundle而不是HashMap

1.Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。

2.另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。

小结

到此,Bundle的源码分析基本就结束了,其实Bundle比较简单,只是一个数据容器,不像Activity等有复杂的生命周期。对于开发者来说,只需要了解Bundle的功能、使用场景并掌握常用的数据存取方法即可。

你可能感兴趣的:(Android)