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_PARCELmParcelledData = 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的功能、使用场景并掌握常用的数据存取方法即可。
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- 一句话锁定MySQL数据占用元凶
- Android混合开发之------ AndroidStudio集成Cordova项目
- android中SQLite使用查看
- Android(安卓)MediaScanner:(四)MediaScanner之scanSingleFile
- Android(安卓)SugarORM(1)
- Android(安卓)代码中设置EditText只输入数字、字母
- android parcelable 以及android studio插件
- View、Window、WindowManager-vsync信号