详解 Android IPC 机制(三)使用 Bundle 实现进程间通信

Author Avatar
wshunli 6月 07, 2018
  • 在其它设备中阅读本文章

在 Android 四大组件中 Activity、Service、Receiver 都支持在 Intent 中附加传递 Bundle 数据。

Bundle 是以键值对的形式存储数据,支持基本数据类型、数组、实现 Serializable 或 Parcelable 接口的对象以及一些 Android 支持的特殊对象。

// 发送数据
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString("userName", "wshunli");
intent.putExtras(bundle);
startActivity(intent);
// 接收数据
Bundle bundle = getIntent().getExtras();
String userName = null;
if (bundle != null) {
    userName = bundle.getString("userName");
}
Log.d(TAG, "userName: " + userName);

使用 Bundle 的 put 与 get 方法族将数据保存至 bundle 对象,调用 Intent 的 putExtras 方法即可。

Bundle 源码分析,Bundle 继承自 BaseBundle 并实现了 Cloneable 和 Parcelable 接口。

以 Boolean 类型数据存取为例:

ArrayMap<String, Object> mMap = null;

public void putBoolean(@Nullable String key, boolean value) {
    unparcel();
    mMap.put(key, value);
}
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;
    }
}

根据源码可知数据存取都是在 ArrayMap 对象中。

其中 unparcel() 方法源码如下:

/*
* 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;

/* package */ void unparcel() {
    synchronized (this) {
        final Parcel source = mParcelledData;
        if (source != null) {
            initializeFromParcelLocked(source, /*recycleParcel=*/ true);
        } else {
            if (DEBUG) {
                Log.d(TAG, "unparcel "
                        + Integer.toHexString(System.identityHashCode(this))
                        + ": no parcelled data");
            }
        }
    }
}
private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel) {
    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(parcelledData)) {
        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;
    }

    final int count = parcelledData.readInt();
    if (DEBUG) {
        Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                + ": reading " + count + " maps");
    }
    if (count < 0) {
        return;
    }
    ArrayMap<String, Object> map = mMap;
    if (map == null) {
        map = new ArrayMap<>(count);
    } else {
        map.erase();
        map.ensureCapacity(count);
    }
    try {
        parcelledData.readArrayMapInternal(map, count, 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;
        if (recycleParcel) {
            recycleParcel(parcelledData);
        }
        mParcelledData = null;
    }
    if (DEBUG) {
        Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                + " final map: " + mMap);
    }
}

只有使用 Bundle 构造函数实例化 Bundle 对象时,才会给 mParcelledData 赋值,其他大部分情况下 mParcelledData 都是 null 。

BaseBundle(Parcel parcelledData) {
    readFromParcelInner(parcelledData);
}
void readFromParcelInner(Parcel parcel) {
    // Keep implementation in sync with readFromParcel() in
    // frameworks/native/libs/binder/PersistableBundle.cpp.
    int length = parcel.readInt();
    readFromParcelInner(parcel, length);
}
private void readFromParcelInner(Parcel parcel, int length) {
    if (length < 0) {
        throw new RuntimeException("Bad length in parcel: " + length);

    } else if (length == 0) {
        // Empty Bundle or end of data.
        mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
        return;
    }

    final int magic = parcel.readInt();
    if (magic != BUNDLE_MAGIC) {
        throw new IllegalStateException("Bad magic number for Bundle: 0x"
                + Integer.toHexString(magic));
    }

    if (parcel.hasReadWriteHelper()) {
        // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
        // unparcel right away.
        synchronized (this) {
            initializeFromParcelLocked(parcel, /*recycleParcel=*/ false);
        }
        return;
    }

    // Advance within this Parcel
    int offset = parcel.dataPosition();
    parcel.setDataPosition(MathUtils.addOrThrow(offset, length));

    Parcel p = Parcel.obtain();
    p.setDataPosition(0);
    p.appendFrom(parcel, offset, length);
    p.adoptClassCookies(parcel);
    if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))
            + ": " + length + " bundle bytes starting at " + offset);
    p.setDataPosition(0);

    mParcelledData = p;
}

从上述代码片段可以知道 mParcelledData 的取值有3种情况:

mParcelledData = null
mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL
mParcelledData = Parcel.obtain()

在 unparcel() 方法中分别对这三种情况进行了处理,如果为 null 不进行任何操作返回;如果为 EMPTY_PARCEL 则使用 mMap = new ArrayMap<>(1) 创建长度为 1 的 ArrayMap 对象;如果为 Parcel.obtain() 则实例化 ArrayMap 对象并存储数据。

参考资料
1、Android Bundle详解 - CSDN博客
https://blog.csdn.net/cswhale/article/details/39053411
2、Android Bundle总结 - CSDN博客
https://blog.csdn.net/ylyg050518/article/details/72638852
3、Android细节问题 —— 有了Intent,为什么还要有Bundle? - 简书
https://www.jianshu.com/p/e9db0797293b

如果本文对您有所帮助,且您手头还很宽裕,欢迎打赏赞助我,以支付网站服务器和域名费用。 https://paypal.me/wshunli 您的鼓励与支持是我更新的最大动力,我会铭记于心,倾于博客。
本文链接:https://www.wshunli.com/posts/f0f2eaec.html