Bundle解析

2,431 阅读3分钟

前情提要: 使用掘金有一段时间了,但是一直都只是查找翻阅资料,最近面试被问到了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代码了(不咋熟悉,分析不动了),突然觉得面试时没答上来有点可惜,还是得多看源码呀。如果有什么写的不好的,欢迎大家纠正。