前言
Activity之间数据传递最常见不过了,而且对绝开发者来说简直是超级简单的了。用Intent携带数据就传过去了。但是有一次需要对传过来的数据(Bundle)进行修改,出现了预期之外的情况,吾以文章以记之。
出现问题
出现问题的主要代码:以下代码全部都是简化了的,实际上还有很多逻辑
MainActivity.java 传递数据
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString(EXTRA_A, "A");
bundle.putString(EXTRA_B, "B");
intent.putExtra(EXTRA_BUNDLE, bundle);
intent.putExtras(bundle);
startActivity(intent);
}
SecondActivity.java 在 onResume()
接收处理数据
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras.containsKey(EXTRA_A)) {//如果有 EXTRA_A, 执行一些操作后进行移除
//do...
extras.remove(EXTRA_A);
} else {
//do...
}
}
问题出现在 SecondActivity
,在 onRresume()
里,extras.containsKey(EXTRA_A)
一直为 true
发现问题
为什么会这样呢?为什么无法移除指定的数据呢?代码肯定没写错的,折腾了好一会之后点开了 intent.getExtras()
的源码,终于发现了新大陆。
Intent.java#getExtras() 代码如下:
public @Nullable Bundle getExtras() {
return (mExtras != null)
? new Bundle(mExtras)//重点!!
: null;
}
看了源码,问题就基本解决了, getExtras()
返回的是 null
或者 一个新对象,所以上面 onResume()
里 extras.containsKey(EXTRA_A)
每次都是用新的对象去判断的,所以一直是 true 。分析问题完成。
解决问题
可能你看到 出现问题 的时候已经在吐槽 SecondActivity
的那段代码了。下面举三个解决方法(肯定不止三个)。
方法一:
- 接收数据方
Bundle
用成员变量
private Bundle extras;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
extras = getIntent().getExtras();
}
@Override
protected void onResume() {
super.onResume();
if (extras == null) {
extras = getIntent().getExtras();
if (extras.containsKey(EXTRA_A)) {//如果有 EXTRA_A, 执行一些操作后进行移除
//do...
extras.remove(EXTRA_A);
} else {
//do...
}
}
}
方法二:
- 传递数据用
intent.putExtra(EXTRA_BUNDLE, bundle);
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
Bundle bundle = new Bundle();
bundle.putString(EXTRA_A, "A");
bundle.putString(EXTRA_B, "B");
intent.putExtra(EXTRA_BUNDLE, bundle);
startActivity(intent);
}
- 接收数据用
Bundle bundleExtra = intent.getBundleExtra(EXTRA_BUNDLE);
@Override
protected void onResume() {
super.onResume();
Bundle bundleExtra = intent.getBundleExtra(EXTRA_BUNDLE);
bundleExtra.remove(EXTRA_A);
}
方法三:
- 使用
Intent#removeExtra(EXTRA_A)
移除数据
@Override
protected void onResume() {
super.onResume();
Bundle extras = intent.getExtras();
if (extras.containsKey(EXTRA_A)) {//如果有 EXTRA_A, 执行一些操作后进行移除
//do...
intent.removeExtra(EXTRA_A);
} else {
//do...
}
}
引出思考
这次踩坑让我决定去一探 intent.getExtras()
的究竟。其实是看下源码,做下源码解析(把英文注释翻译成中文+自己理解)
1. Intent.java Intente.getExtras()
代码
/**
* 返回带有 putExtra() 和 putExtras() 数据的新 Bundle 或者 null
*/
public @Nullable Bundle getExtras() {
return (mExtras != null)
? new Bundle(mExtras)
: null;
}
2. Bundle.java
构造函数 Bundle(mExtras)
代码
/**
* Bundle 的一个构造函数,对原始数据进行浅复制,
* 参数 b: 被复制的Bundle
*/
public Bundle(Bundle b) {
super(b);
mFlags = b.mFlags;
}
Bundle
的父类是 BaseBundle
, 同时也实现了 java.lang.Cloneable
和 android.os.Parcelable
这两个接口。重写了 clone()
方法,调用 Bundle(Bundle b) 构造函数创建 Bundle 对象
@Override
public Object clone() {
return new Bundle(this);//调用 Bundle(Bundle b) 构造函数创建 Bundle 对象
}
需要留意的方法 deepCopy()
,要想得到一个和原来值一样的 Bundle 可以使用这个方法
/**
* 深复制给定的Bundle,返回复制后的Bundle
* 遍历Bundle、PersistableBundle(和Bundle差不多的)、ArrayList、 基本数据类型数组并复制值,这些值不会和其他Bundle共享
* 其他数据类型比如 Parcelable 或者 Serializable 按原样进行引用,不会进行复制
*/
public Bundle deepCopy() {
Bundle b = new Bundle(false);//不初始化Bundle的特殊构造函数
b.copyInternal(this, true);//父类copyInternal深复制
return b;
}
接下来看 super(b);
3. BaseBundle.java
构造函数 BaseBundle(BaseBundle b)
代码
/**
* 复制一个指定的Bundle
*/
BaseBundle(BaseBundle b) {
copyInternal(b, false);//实现复制的核心代码,
}
方法 copyInternal(BaseBundle from, boolean deep)
主要代码
/**
* 复制 Bundle 的方法,代码块是同步代码块
*
* @param from 被复制的Bundle
* @param deep 是否深复制
*/
void copyInternal(BaseBundle from, boolean deep) {
synchronized (from) {//同步代码,保证线程安全
//省略了部分代码...
if (from.mMap != null) {
if (!deep) {//浅复制:调用ArrayMap(ArrayMap map)创建对象
mMap = new ArrayMap<>(from.mMap);
} else {//深复制:调用 deepCopyValue(Object value) 复制 from.mMap 的值
final ArrayMap fromMap = from.mMap;
final int N = fromMap.size();
mMap = new ArrayMap<>(N);//创建 ArrayMap 对象
//遍历、复制值并添加到新的对象里
for (int i = 0; i < N; i++) {
mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
}
}
} else {
mMap = null;
}
//省略了部分代码...
}
}
方法 deepCopyValue(Object value)
代码
/**
* 深复制 Bundle 值的方法,返回复制后的对象
*
* @param value 被复制的值
*/
Object deepCopyValue(Object value) {
if (value == null) {
return null;
}
if (value instanceof Bundle) {//Bundle:调用 Bundle.deepCopy() 复制,代码在前面 Bundle.java 里介绍了
return ((Bundle)value).deepCopy();
} else if (value instanceof PersistableBundle) {//和Bundle差不多
return ((PersistableBundle)value).deepCopy();
} else if (value instanceof ArrayList) {//ArrayList:deepcopyArrayList(ArrayList value)
return deepcopyArrayList((ArrayList) value);// deepcopyArrayList会调用 deepCopyValue(Object value)方法进行复制
} else if (value.getClass().isArray()) {//基本数据类型数组,直接调用clone()
if (value instanceof int[]) {
return ((int[])value).clone();
} else if (value instanceof long[]) {
return ((long[])value).clone();
} else if (value instanceof float[]) {
return ((float[])value).clone();
} else if (value instanceof double[]) {
return ((double[])value).clone();
} else if (value instanceof Object[]) {
return ((Object[])value).clone();
} else if (value instanceof byte[]) {
return ((byte[])value).clone();
} else if (value instanceof short[]) {
return ((short[])value).clone();
} else if (value instanceof char[]) {
return ((char[]) value).clone();
}
}
//以上之外的其他数据类型直接返回值,比如基本数据类型、Parcelable、Serializable
return value;
}
总结
开始在 Activity 使用 Bundle 传递数据踩坑,后来通过看源码出坑,进而了解了 Bundle 关于复制相关的代码实现,阅读源码是多么重要!别人写的代码使用起来总是那么简单,但是里面的实现却很讲究。分析完 Bundle 的部分源码之后,愈发觉得 Bundle 的设计像是使用了设计模式中的 原型模式,不是吗?