前情提要: 使用掘金有一段时间了,但是一直都只是查找翻阅资料,最近面试被问到了Bundle怎么实现的(写这篇文章的主要原因),而且这是第二次被问到有没有写博客的习惯,就比较尴尬,所以呢就把写技术文章提上了日程。况且有的知识点光看的话确实影响不太深刻。这是第一次分享文章,如果有什么写的不对或者是哪里的说明不太好的,大家都可以评论回复(轻喷),谢谢大家。
Bundle解析(api28)
源码的注释Bundle的定义:
从字符串键到各种{@link Parcelable}值的映射。
Bundle我们经常用来启动Activity,Service以及组件之前传递数据。但是呢,我们传递的数据必须要能够被序列化,比如基本类型、实现了Parcelable/Serializable接口的对象以及Android支持的特殊对象。
Bundle基本使用
//写入数据
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putInt("key",1);
bundle.putString("str", "value值"); //key-"str",通过key得到value-"value值"(String)
intent.putExtras(bundle); //通过intent将bundle传到另个Activity
startActivity(intent);
//另一个Activity读取数据
Bundle bundle2 = this.getIntent().getExtras(); //读取intent的数据给bundle对象
String str1 = bundle.getString("str"); //通过key得到value
int int1 = bundle.getInt("key");
简单来说就是添加什么类型的数据就调用什么类型的方法
添加数据
public void putInt(@Nullable String key, int value) {
unparcel();
mMap.put(key, value);
}
put方法都会把数据放进mMap对象里面,那这个map是什么呢?
ArrayMap<String, Object> mMap = null;
mMap = capacity > 0 ?
new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();
从这里我们可以看出来这个mMap是一个ArrayMap,且value是Object类型。
Bundle的数据存储
Parcel解析
void unparcel() {
synchronized (this) {
final Parcel source = mParcelledData;
if (source != null) {
initializeFromParcelLocked(source, /*recycleParcel=*/ true, mParcelledByNative);
} else {
Log.d(TAG, "unparcel "
+ Integer.toHexString(System.identityHashCode(this))
+ ": no parcelled data");
}
}
}
在上面添加数据处的putInt方法里面的这个unparcel就是用来解析Parcel数据的,也就是说put方法里会先解析Parcel,如果没有Parcel数据就直接mMap.put。接下来又调用了initializeFromParcelLocked,方法太长就不全贴上来了,下面是主要的部分
ArrayMap<String, Object> map = mMap;
if (map == null) {
map = new ArrayMap<>(count);
} else {
map.erase();
map.ensureCapacity(count);
}
try {
if (parcelledByNative) {
//如果它是由native代码打包的,则数组映射键不会按其哈希码排序,因此请使用安全(慢速)键。
parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
} else {
// 如果使用Java打包,则知道内容已正确排序,因此可以使用ArrayMap.append()。
parcelledData.readArrayMapInternal(map, count, mClassLoader);
}
} catch (BadParcelableException e) {
省略
} finally {
mMap = map;
if (recycleParcel) {
recycleParcel(parcelledData);
}
mParcelledData = null;
mParcelledByNative = false;
}
其中调用readArrayMapInternal把Parcel中的数据发序列化为对象。然后存放在mMap中。
void readArrayMapInternal(ArrayMap outVal, int N,
ClassLoader loader) {
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
}
int startPos;
while (N > 0) {
if (DEBUG_ARRAY_MAP) startPos = dataPosition();
String key = readString();
Object value = readValue(loader);
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read #" + (N-1) + " "
+ (dataPosition()-startPos) + " bytes: key=0x"
+ Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
outVal.append(key, value);
N--;
}
outVal.validate();
}
public final Object readValue(ClassLoader loader) {
int type = readInt();
switch (type) {
case VAL_NULL:
return null;
省略很多case
case VAL_MAP:
return readHashMap(loader);
case VAL_PARCELABLE:
return readParcelable(loader);
省略很多case
}
接下来就是分不同类型的数据分开来读取,Parcel对Map类型的数据进行反序列化时,获取key\value的值都存放在HashMap中,然后返回HashMap,也就是说所有Map类型数据最后都会转为HashMap类型数据。这也就能解释强转成Treemap会报错的情况。
总结
最终一波分析下来,好像他的实现也就是Arraymap存储数据,中间添加了对于序列化数据的相关解析,最后有的解析方法都到native代码了(不咋熟悉,分析不动了),突然觉得面试时没答上来有点可惜,还是得多看源码呀。如果有什么写的不好的,欢迎大家纠正。