Bundle原理解析

前言

Bundle对于android开发者来说肯定非常熟悉,它经常出现在以下场合:

  • Activity状态数据的保存与恢复涉及到的两个回调:void onSaveInstanceState (Bundle outState)、void onCreate (Bundle savedInstanceState)
  • Fragment的setArguments方法:void setArguments(Bundle args)
  • 消息机制中的Message的setData方法:void setData(Bundle data)

Bundle是用来传递数据的,可以将其理解为android中用来传递数据的一个容器,官方意为Bundle封装了String值到各种Parcelable类型数据的映射。

Bundle源码分析

Bundle位于android.os包中,是一个final类,这就注定了Bundle不能被继承。Bundle继承自BaseBundle并实现了Cloneable和Parcelable两个接口。

由于实现了Cloneable和Parcelable接口,因此以下几个重载是必不可少的:

1
2
3
4
5
public Object clone();
public int describeContents();
public void writeToParcel(Parcel parcel, int flag);
public void readFromParcel(Parcel parcel);
public static final Parcelable.Creator<Bundle> CREATOR = new Parcelable.Creator<Bundle>();

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.

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 void putShort(String key, short value) public short getShort(String key)
public void putFloat(String key, float value) public float getFloat(String key)
public void putCharSequence(String key, CharSequence value) public CharSequence getCharSequence(String key)
public void putParcelable(String key, Parcelable value) public T getParcelable(String key)
public void putSize(String key, Size value) public Size getSize(String key)
public void putSizeF(String key, SizeF value) public SizeF getSizeF(String key)
public void putParcelableArray(String key, Parcelable[] value) public Parcelable[] getParcelableArray(String key)
public void putParcelableArrayList(String key, ArrayList value) public ArrayList getParcelableArrayList(String key)
public void putSparseParcelableArray(String key, SparseArray value) public SparseArray getSparseParcelableArray(String key)
public void putIntegerArrayList(String key, ArrayList value) public ArrayList getIntegerArrayList(String key)
public void putStringArrayList(String key, ArrayList value) public ArrayList getStringArrayList(String key)
public void putCharSequenceArrayList(String key, ArrayList value) public ArrayList getCharSequenceArrayList(String key)
public void putSerializable(String key, Serializable value) public Serializable getSerializable(String key)
public void putBooleanArray(String key, boolean[] value) public boolean[] getBooleanArray(String key)
public void putByteArray(String key, byte[] value) public byte[] getByteArray(String key)
public void putShortArray(String key, short[] value) public short[] getShortArray(String key)
public void putCharArray(String key, char[] value) public char[] getCharArray(String key)
public void putFloatArray(String key, float[] value) public float[] getFloatArray(String key)
public void putCharSequenceArray(String key, CharSequence[] value) public CharSequence[] getCharSequenceArray(String key)
public void putBundle(String key, Bundle value) public Bundle getBundle(String key)
public void putBinder(String key, IBinder value) public IBinder getBinder(String key)

除了上述存取数据涉及的方法外,Bundle还提供了一个clear方法:public void clear(),该方法可用于移除Bundle中的所有数据。

Bundle之所以能以键值对的方式存储数据,实质上是因为它的父类BaseBundle内部维护了一个ArrayMap。

1
ArrayMap<String, Object> mMap = null;

Bundle存取数据的具体实现

下面使用布尔类型的存储源码为例:

1
2
3
4
public void putBoolean(@Nullable String key, boolean value) {
unparcel();
mMap.put(key, value);
}

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

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

1
2
3
4
5
6
public boolean getBoolean(String key) {
unparcel();
if (DEBUG) Log.d(TAG, "Getting boolean in "
+ Integer.toHexString(System.identityHashCode(this)));
return getBoolean(key, false);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public 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(),它的具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/* package */ synchronized void unparcel() {
synchronized (this) {
final Parcel parcelledData = mParcelledData;
if (parcelledData == null) {
if (DEBUG) Log.d(TAG, "unparcel "
+ Integer.toHexString(System.identityHashCode(this))
+ ": no parcelled data");
return;
}
if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
+ "clobber all data inside!", new Throwable());
}
if (isEmptyParcel()) {
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 = parcelledData.readInt();
if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ ": reading " + N + " maps");
if (N < 0) {
return;
}
ArrayMap<String, Object> map = mMap;
if (map == null) {
map = new ArrayMap<>(N);
} else {
map.erase();
map.ensureCapacity(N);
}
try {
parcelledData.readArrayMapInternal(map, N, mClassLoader);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
map.erase();
} else {
throw e;
}
} finally {
mMap = map;
parcelledData.recycle();
mParcelledData = null;
}
if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ " final map: " + mMap);
}
}

先来看BaseBundle中mParcelledData的定义:

1
2
3
4
5
6
/*
* 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赋值,具体实现如下:

1
2
3
4
public Bundle(Bundle b) {
super(b);
mFlags = b.mFlags;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
BaseBundle(BaseBundle b) {
if (b.mParcelledData != null) {
if (b.isEmptyParcel()) {
mParcelledData = NoImagePreloadHolder.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。

上面只是以布尔类型的数据分析了Bundle的存储过程,其他数据类型的存储原理类似,不再赘述。

Bundle容器实现原因

Bundle的容器使用ArrayMap实现而不是HashMap,其原因如下:

  • Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据操作,如果在数据量比较大的情况下,它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。
  • 在android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。在android平台中,更推荐Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速地进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。