前言
Activity之間數(shù)據(jù)傳遞最常見不過了扼倘,而且對(duì)絕開發(fā)者來說簡(jiǎn)直是超級(jí)簡(jiǎn)單的了并思。用Intent攜帶數(shù)據(jù)就傳過去了拟枚。但是有一次需要對(duì)傳過來的數(shù)據(jù)(Bundle)進(jìn)行修改谒获,出現(xiàn)了預(yù)期之外的情況,吾以文章以記之业簿。
出現(xiàn)問題
出現(xiàn)問題的主要代碼:以下代碼全部都是簡(jiǎn)化了的瘤礁,實(shí)際上還有很多邏輯
MainActivity.java 傳遞數(shù)據(jù)
@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()
接收處理數(shù)據(jù)
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras.containsKey(EXTRA_A)) {//如果有 EXTRA_A, 執(zhí)行一些操作后進(jìn)行移除
//do...
extras.remove(EXTRA_A);
} else {
//do...
}
}
問題出現(xiàn)在 SecondActivity
,在 onRresume()
里梅尤,extras.containsKey(EXTRA_A)
一直為 true
發(fā)現(xiàn)問題
為什么會(huì)這樣呢柜思?為什么無法移除指定的數(shù)據(jù)呢岩调?代碼肯定沒寫錯(cuò)的,折騰了好一會(huì)之后點(diǎn)開了 intent.getExtras()
的源碼赡盘,終于發(fā)現(xiàn)了新大陸号枕。
Intent.java#getExtras() 代碼如下:
public @Nullable Bundle getExtras() {
return (mExtras != null)
? new Bundle(mExtras)//重點(diǎn)!陨享!
: null;
}
看了源碼葱淳,問題就基本解決了, getExtras()
返回的是 null
或者 一個(gè)新對(duì)象霉咨,所以上面 onResume()
里 extras.containsKey(EXTRA_A)
每次都是用新的對(duì)象去判斷的蛙紫,所以一直是 true 。分析問題完成途戒。
解決問題
可能你看到 出現(xiàn)問題 的時(shí)候已經(jīng)在吐槽 SecondActivity
的那段代碼了坑傅。下面舉三個(gè)解決方法(肯定不止三個(gè))。
方法一:
- 接收數(shù)據(jù)方
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, 執(zhí)行一些操作后進(jìn)行移除
//do...
extras.remove(EXTRA_A);
} else {
//do...
}
}
}
方法二:
- 傳遞數(shù)據(jù)用
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);
}
- 接收數(shù)據(jù)用
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)
移除數(shù)據(jù)
@Override
protected void onResume() {
super.onResume();
Bundle extras = intent.getExtras();
if (extras.containsKey(EXTRA_A)) {//如果有 EXTRA_A, 執(zhí)行一些操作后進(jìn)行移除
//do...
intent.removeExtra(EXTRA_A);
} else {
//do...
}
}
引出思考
這次踩坑讓我決定去一探 intent.getExtras()
的究竟喷斋。其實(shí)是看下源碼唁毒,做下源碼解析(把英文注釋翻譯成中文+自己理解)
1. Intent.java Intente.getExtras()
代碼
/**
* 返回帶有 putExtra() 和 putExtras() 數(shù)據(jù)的新 Bundle 或者 null
*/
public @Nullable Bundle getExtras() {
return (mExtras != null)
? new Bundle(mExtras)
: null;
}
2. Bundle.java
構(gòu)造函數(shù) Bundle(mExtras)
代碼
/**
* Bundle 的一個(gè)構(gòu)造函數(shù),對(duì)原始數(shù)據(jù)進(jìn)行淺復(fù)制星爪,
* 參數(shù) b: 被復(fù)制的Bundle
*/
public Bundle(Bundle b) {
super(b);
mFlags = b.mFlags;
}
Bundle
的父類是 BaseBundle
浆西, 同時(shí)也實(shí)現(xiàn)了 java.lang.Cloneable
和 android.os.Parcelable
這兩個(gè)接口。重寫了 clone()
方法顽腾,調(diào)用 Bundle(Bundle b) 構(gòu)造函數(shù)創(chuàng)建 Bundle 對(duì)象
@Override
public Object clone() {
return new Bundle(this);//調(diào)用 Bundle(Bundle b) 構(gòu)造函數(shù)創(chuàng)建 Bundle 對(duì)象
}
需要留意的方法 deepCopy()
近零,要想得到一個(gè)和原來值一樣的 Bundle 可以使用這個(gè)方法
/**
* 深復(fù)制給定的Bundle,返回復(fù)制后的Bundle
* 遍歷Bundle抄肖、PersistableBundle(和Bundle差不多的)久信、ArrayList、 基本數(shù)據(jù)類型數(shù)組并復(fù)制值漓摩,這些值不會(huì)和其他Bundle共享
* 其他數(shù)據(jù)類型比如 Parcelable 或者 Serializable 按原樣進(jìn)行引用裙士,不會(huì)進(jìn)行復(fù)制
*/
public Bundle deepCopy() {
Bundle b = new Bundle(false);//不初始化Bundle的特殊構(gòu)造函數(shù)
b.copyInternal(this, true);//父類copyInternal深復(fù)制
return b;
}
接下來看 super(b);
3. BaseBundle.java
構(gòu)造函數(shù) BaseBundle(BaseBundle b)
代碼
/**
* 復(fù)制一個(gè)指定的Bundle
*/
BaseBundle(BaseBundle b) {
copyInternal(b, false);//實(shí)現(xiàn)復(fù)制的核心代碼,
}
方法 copyInternal(BaseBundle from, boolean deep)
主要代碼
/**
* 復(fù)制 Bundle 的方法管毙,代碼塊是同步代碼塊
*
* @param from 被復(fù)制的Bundle
* @param deep 是否深復(fù)制
*/
void copyInternal(BaseBundle from, boolean deep) {
synchronized (from) {//同步代碼腿椎,保證線程安全
//省略了部分代碼...
if (from.mMap != null) {
if (!deep) {//淺復(fù)制:調(diào)用ArrayMap(ArrayMap map)創(chuàng)建對(duì)象
mMap = new ArrayMap<>(from.mMap);
} else {//深復(fù)制:調(diào)用 deepCopyValue(Object value) 復(fù)制 from.mMap 的值
final ArrayMap<String, Object> fromMap = from.mMap;
final int N = fromMap.size();
mMap = new ArrayMap<>(N);//創(chuàng)建 ArrayMap 對(duì)象
//遍歷、復(fù)制值并添加到新的對(duì)象里
for (int i = 0; i < N; i++) {
mMap.append(fromMap.keyAt(i), deepCopyValue(fromMap.valueAt(i)));
}
}
} else {
mMap = null;
}
//省略了部分代碼...
}
}
方法 deepCopyValue(Object value)
代碼
/**
* 深復(fù)制 Bundle 值的方法夭咬,返回復(fù)制后的對(duì)象
*
* @param value 被復(fù)制的值
*/
Object deepCopyValue(Object value) {
if (value == null) {
return null;
}
if (value instanceof Bundle) {//Bundle:調(diào)用 Bundle.deepCopy() 復(fù)制啃炸,代碼在前面 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會(huì)調(diào)用 deepCopyValue(Object value)方法進(jìn)行復(fù)制
} else if (value.getClass().isArray()) {//基本數(shù)據(jù)類型數(shù)組,直接調(diào)用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();
}
}
//以上之外的其他數(shù)據(jù)類型直接返回值卓舵,比如基本數(shù)據(jù)類型南用、Parcelable、Serializable
return value;
}
總結(jié)
開始在 Activity 使用 Bundle 傳遞數(shù)據(jù)踩坑,后來通過看源碼出坑训枢,進(jìn)而了解了 Bundle 關(guān)于復(fù)制相關(guān)的代碼實(shí)現(xiàn),閱讀源碼是多么重要忘巧!別人寫的代碼使用起來總是那么簡(jiǎn)單恒界,但是里面的實(shí)現(xiàn)卻很講究。分析完 Bundle 的部分源碼之后砚嘴,愈發(fā)覺得 Bundle 的設(shè)計(jì)像是使用了設(shè)計(jì)模式中的 原型模式十酣,不是嗎?