Android面试题(25)-Bundle机制

Bundle的概念理解

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

  1. Activity状态数据的保存与恢复涉及到的两个回调:void onSaveInstanceState (Bundle outState)void onCreate (Bundle savedInstanceState)
  2. Fragment的setArguments方法:void setArguments (Bundle args)
  3. 消息机制中的Message的setData方法:void setData (Bundle data)
  4. 其他场景不再列举

Bundle从字面上解释为“一捆、一批、一包”,结合上述几个应用场合,可以知道Bundle是用来传递数据的,我们暂将Bundle理解为Android中用来传递数据的一个容器。官方文档对Bundle的说明如下:

Bundle实现了Parcelable接口,所以他可以方便的在不同进程间传输,这里要注意我们传输的数据必须能够被序列化;

Bundle源码分析

首先看它的声明

public final class Bundle extends BaseBundle implements Cloneable, Parcelable

第一,它使用final修饰,所以不可以被继承

第二,它实现了两个接口,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()

再看他的内存结构:

ArrayMap, Object> mMap = null;

使用的是ArrayMap,这个集合类存储的也是键值对,但是与Hashmap不同的是,hashmap采用的是“数组+链表”的方式存储,而Arraymap中使用的是两个数组进行存储,一个数组存储key,一个数组存储value,内部的增删改查都将会使用二分查找来进行,这个和SparseArray差不多,只不过sparseArray的key值只能是int型的,而Arraymap可以是map型,所以在数据量不大的情况下可以使用这两个集合代替hashmap去优化性能;

我们知道Bundle其实就是一个容器,内部使用了Arraymap去存储数据,那么就必然会提供get,put方法,由于Bundle支持的数据类型太多,这里我们就看一个布尔类型的,其他类型的方式都差不多;

getBoolean

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里读出保存的数据,并转换成对应的类型返回,当没找到数据或发生类型转换异常时返回缺省值。

putBoolean

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

这里出现了一个unparcel()方法

/* package */ 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, 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的定义:

Parcel mParcelledData = null;

在大部分情况下mParcelledData都是null,因此unparcel()直接返回。当使用构造函数public Bundle(Bundle b)创建Bundle时,会给mParcelledData赋值;

void copyInternal(BaseBundle from, boolean deep) {
    synchronized (from) {
        if (from.mParcelledData != null) {
            if (from.isEmptyParcel()) {
                mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
            } else {
                mParcelledData = Parcel.obtain();
                mParcelledData.appendFrom(from.mParcelledData, 0,
                        from.mParcelledData.dataSize());
                mParcelledData.setDataPosition(0);
            }
        } else {
            mParcelledData = null;
        }

        if (from.mMap != null) {
            if (!deep) {
                mMap = new ArrayMap<>(from.mMap);
            } else {
                final ArrayMap, Object> fromMap = from.mMap;
                final int N = fromMap.size();
                mMap = new ArrayMap<>(N);
                for (int i = 0; i < N; i++) {
                    mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
                }
            }
        } else {
            mMap = null;
        }

        mClassLoader = from.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;

你可能感兴趣的:(android,android面试题)