官方文档对Bundle的说明如下:
A mapping from String values to various Parcelable types.
官方意为Bundle封装了String值到各种Parcelable类型数据的映射,可见跟我们上述理解是吻合的。
知道了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的功能是用来保存数据,那么必然提供了一系列存取数据的方法,这些方法太多了,几乎能够存取任何类型的数据,例如:
相关保存方法 | 相关读取方法 |
---|---|
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;
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的存取过程,其他数据类型的存取原理相同,就不再赘述。
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的功能、使用场景并掌握常用的数据存取方法即可。