Bundle/Intent传递序列化参数暗藏杀机
其中Boom是一個(gè)序列化類(lèi)(serializable),而且extra的key可以是任何值。
而com.test.test.MainActivity則是另外一個(gè)app中允許外部調(diào)起的activity,即MainActivity有一個(gè)為android.intent.action.MAIN的action,否則代碼會(huì)報(bào)錯(cuò)。 還需要滿(mǎn)足一個(gè)條件,MainActivity代碼中有從intent(getIntent或newIntent的參數(shù))取參數(shù)的操作,如 Bundle bundle = intent.getExtras(); if (bundle != null) {int sd = bundle.getInt("key"); }或int sd = intent.getIntExtra("key", -1);注意,不僅僅是getInt,任何類(lèi)型的都會(huì)出問(wèn)題,而且key不必與之前的anykey一樣!
崩潰日志如下: E/AndroidRuntime: FATAL EXCEPTION: main ? Process: xxx, PID: 1688 ? java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = com.example.Boom) ? at android.os.Parcel.readSerializable(Parcel.java:2630) ? at android.os.Parcel.readValue(Parcel.java:2416) ? at android.os.Parcel.readArrayMapInternal(Parcel.java:2732) ? at android.os.BaseBundle.unparcel(BaseBundle.java:271) ? at android.os.BaseBundle.get(BaseBundle.java:364) ? at?com.test.test.MainActivity.onNewIntent(MainActivity.java:128) ? ... ? Caused by: java.lang.ClassNotFoundException: com.example.Boom ? at java.lang.Class.classForName(Native Method) ? at java.lang.Class.forName(Class.java:400) ? at android.os.Parcel$2.resolveClass(Parcel.java:2616) ? at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613) ? at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518) ? at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772) ? at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) ? at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373) ? at android.os.Parcel.readSerializable(Parcel.java:2624) ? at android.os.Parcel.readValue(Parcel.java:2416) ? at android.os.Parcel.readArrayMapInternal(Parcel.java:2732) ? at android.os.BaseBundle.unparcel(BaseBundle.java:271) ? at android.os.BaseBundle.get(BaseBundle.java:364) ? at?com.test.test.MainActivity.onNewIntent(MainActivity.java:128) ? ... 02-27 17:33:33.799 1688-1688/? E/AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "com.example.Boom" on path: DexPathList[...] ? at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56) ? at java.lang.ClassLoader.loadClass(ClassLoader.java:380) ? at java.lang.ClassLoader.loadClass(ClassLoader.java:312) ? ... 27 more 02-27 17:33:33.813 1688-1688/? E/MobclickAgent: onPause called before onResume 可以看到是因?yàn)閼?yīng)用中沒(méi)有Boom這個(gè)類(lèi),反序列化時(shí)找不到,那么既然應(yīng)用中沒(méi)有用到anykey,為什么會(huì)去做反序列化的操作呢? 查看Intent及Bundle源碼可以發(fā)現(xiàn),那些get函數(shù)最終都會(huì)調(diào)用BaseBundle的相應(yīng)的get函數(shù)。 在BaseBundle中不論get函數(shù)還是put函數(shù)中都會(huì)先調(diào)用unparcel函數(shù),如: public int getInt(String key, int defaultValue) {unparcel();Object o = mMap.get(key);if (o == null) {return defaultValue;}try {return (Integer) o;} catch (ClassCastException e) {typeWarning(key, o, "Integer", defaultValue, e);return defaultValue;} }public void putString(@Nullable String key, @Nullable String value) {unparcel();mMap.put(key, value); }unparcel函數(shù)我們后面再說(shuō),先看看在這兩種函數(shù)中存取數(shù)據(jù)實(shí)際上都是在mMap中做的,這是BaseBundle中一個(gè)重要的參數(shù),它存儲(chǔ)著B(niǎo)undle的數(shù)據(jù)。
那么這個(gè)mMap中的數(shù)據(jù)又是哪里來(lái)的呢? 下面我們就來(lái)看看這個(gè)unparcel函數(shù),關(guān)鍵源碼如下: /* package */ synchronized void unparcel() {synchronized (this) {...ArrayMap<String, Object> map = mMap;if (map == null) {map = new ArrayMap<>(N);} else {map.erase();map.ensureCapacity(N);}try {mParcelledData.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;mParcelledData.recycle();mParcelledData = null;}if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))+ " final map: " + mMap);} }這里面涉及到了Bundle中的兩個(gè)重要參數(shù)mMap和mParcelledData,mMap我們上面說(shuō)過(guò),另外一個(gè)mParcelledData則是一個(gè)Parcel對(duì)象。它是怎么來(lái)的呢?
這要從activity的啟動(dòng)過(guò)程來(lái)說(shuō),參見(jiàn)探索startActivity流程及在Activity間是如何傳遞Intent的。在這篇文章的最后,我們看到在ActivityManagerNative中onTransact函數(shù)中處理binder接收的消息,其中就有這么一行: Bundle options = data.readInt() != 0? Bundle.CREATOR.createFromParcel(data) : null;這行的作用就是從binder的消息中解析出傳送過(guò)來(lái)的Bundle數(shù)據(jù),繼續(xù)看來(lái)Bundle.CREATOR:
public static final Parcelable.Creator<Bundle> CREATOR =new Parcelable.Creator<Bundle>() {@Overridepublic Bundle createFromParcel(Parcel in) {return in.readBundle();}@Overridepublic Bundle[] newArray(int size) {return new Bundle[size];} };createFromParcel函數(shù)其實(shí)就是調(diào)用來(lái)Parcel的readBundle函數(shù),代碼如下:
public final Bundle readBundle() {return readBundle(null); }public final Bundle readBundle(ClassLoader loader) {int length = readInt();...final Bundle bundle = new Bundle(this, length);...return bundle; }通過(guò)Bundle的構(gòu)造函數(shù)來(lái)新建了一個(gè)對(duì)象,這個(gè)構(gòu)造函數(shù)則調(diào)用了父類(lèi)BaseBundle對(duì)應(yīng)的構(gòu)造函數(shù)如下:
BaseBundle(Parcel parcelledData, int length) {readFromParcelInner(parcelledData, length); }private void readFromParcelInner(Parcel parcel, int length) {...Parcel p = Parcel.obtain();p.setDataPosition(0);p.appendFrom(parcel, offset, length);if (DEBUG) Log.d(TAG, "Retrieving "? + Integer.toHexString(System.identityHashCode(this))+ ": " + length + " bundle bytes starting at " + offset);p.setDataPosition(0);mParcelledData = p; }這樣我們就在readFromParcelInner函數(shù)中找到了mParcelledData的來(lái)源,它實(shí)際上就是傳送過(guò)來(lái)的Bundle序列化后的數(shù)據(jù)。
那么就有了另外一個(gè)疑問(wèn),既然傳送過(guò)來(lái)的只有mParcelledData,那么mMap中其實(shí)是空的,那么get函數(shù)怎么取到值的? 這就是為什么每個(gè)get和put函數(shù)都先調(diào)用unparcel函數(shù)的原因。繼續(xù)觀察上面的unparcel函數(shù),我們發(fā)現(xiàn)“mParcelledData.readArrayMapInternal(map, N, mClassLoader);”這句代碼,調(diào)用了Parcel的readArrayMapInternal函數(shù),并且傳入了map,這個(gè)map后面會(huì)賦值給mMap,所以實(shí)際上兩者是一致的。函數(shù)的源碼如下: /* package */ 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(); }在這個(gè)函數(shù)里就可以比較明顯的看出來(lái),從Parcel中分別讀取出key和value,然后put進(jìn)map中。這樣就解決了之前的疑惑,unparcel函數(shù)的作用實(shí)際上是預(yù)處理,提前將序列化的數(shù)據(jù)反序列化并放入mMap中,然后Bundle再?gòu)膍Map中存取數(shù)據(jù)。
我們?cè)絹?lái)越接近真相了!讀取value用的是readValue函數(shù),代碼如下: public final Object readValue(ClassLoader loader) {int type = readInt();switch (type) {case VAL_NULL:return null;case VAL_STRING:return readString();case VAL_INTEGER:return readInt();...case VAL_SERIALIZABLE:return readSerializable(loader);...default:int off = dataPosition() - 4;throw new RuntimeException("Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);} }根據(jù)不同的類(lèi)型調(diào)用不同的函數(shù)來(lái)獲得value,這里我們只關(guān)注Serializable這個(gè)類(lèi)型,readSerializable代碼如下:
private final Serializable readSerializable(final ClassLoader loader) {String name = readString();...try {ObjectInputStream ois = new ObjectInputStream(bais) {@Overrideprotected Class<?> resolveClass(ObjectStreamClass osClass)throws IOException, ClassNotFoundException {if (loader != null) {Class<?> c = Class.forName(osClass.getName(), false, loader);if (c != null) {return c;}}return super.resolveClass(osClass);}};return (Serializable) ois.readObject();} catch (IOException ioe) {throw new RuntimeException("Parcelable encountered " +"IOException reading a Serializable object (name = " + name +")", ioe);} catch (ClassNotFoundException cnfe) {throw new RuntimeException("Parcelable encountered " +"ClassNotFoundException reading a Serializable object (name = "+ name + ")", cnfe);} }我們終于找到了最開(kāi)始的崩潰錯(cuò)誤的源頭,在這里反序列化時(shí)需要根據(jù)類(lèi)名去找到Class對(duì)象,這時(shí)就出問(wèn)題了,因?yàn)橥ㄟ^(guò)上面我們知道,unparcel函數(shù)預(yù)處理時(shí)會(huì)將mParcelledData中所有的數(shù)據(jù)都解析出來(lái),這時(shí)當(dāng)解析到最開(kāi)始的Boom類(lèi)時(shí),由于在本App中并不存在這個(gè)類(lèi),所以無(wú)法找到這個(gè)類(lèi),這樣就出問(wèn)題了。這樣也解釋了為什么任意key都會(huì)出問(wèn)題。
上面我們只說(shuō)到了序列化的一種:serializable,我們知道在Android中還有另外一種推薦的序列化:Parcelable。 至于這兩種序列化的差別,請(qǐng)參考:Android中Intent/Bundle的通信原理及大小限制(Parcelable原理及與Serializable的區(qū)別) 那么Parcelable會(huì)出現(xiàn)這種crash么,經(jīng)測(cè)試也會(huì)出現(xiàn)這樣的問(wèn)題,但是報(bào)出的錯(cuò)誤是不同的: E/AndroidRuntime:?FATAL EXCEPTION: main ? ? java.lang.RuntimeException: Unable to start activity ComponentInfo{com.test.test/com.test.test.MainActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.Boom ? at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369) ? at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2431) ? ... ? Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.bennu.testapp.Boom ? at android.os.Parcel.readParcelableCreator(Parcel.java:2295) ? at android.os.Parcel.readParcelable(Parcel.java:2245) ? at android.os.Parcel.readValue(Parcel.java:2152) ? at android.os.Parcel.readArrayMapInternal(Parcel.java:2485) ? at android.os.BaseBundle.unparcel(BaseBundle.java:221) ? at android.os.BaseBundle.get(BaseBundle.java:280) ? at?com.test.test.MainActivity.onCreate(MainActivity.java:142) ? ... 情況其實(shí)與serializable差不多,差別在readValue函數(shù)這一步調(diào)用了另外一個(gè)函數(shù)readParcelable,源碼如下: public final <T extends Parcelable> T readParcelable(ClassLoader loader) {Parcelable.Creator<?> creator = readParcelableCreator(loader);if (creator == null) {return null;}if (creator instanceof Parcelable.ClassLoaderCreator<?>) {Parcelable.ClassLoaderCreator<?> classLoaderCreator =(Parcelable.ClassLoaderCreator<?>) creator;return (T) classLoaderCreator.createFromParcel(this, loader);}return (T) creator.createFromParcel(this); }/** @hide */ public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {String name = readString();if (name == null) {return null;}Parcelable.Creator<?> creator;synchronized (mCreators) {HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);if (map == null) {map = new HashMap<>();mCreators.put(loader, map);}creator = map.get(name);if (creator == null) {try {ClassLoader parcelableClassLoader =(loader == null ? getClass().getClassLoader() : loader);Class<?> parcelableClass = Class.forName(name, false /* initialize */,parcelableClassLoader);if (!Parcelable.class.isAssignableFrom(parcelableClass)) {throw new BadParcelableException("Parcelable protocol requires that the "+ "class implements Parcelable");}Field f = parcelableClass.getField("CREATOR");if ((f.getModifiers() & Modifier.STATIC) == 0) {throw new BadParcelableException("Parcelable protocol requires "+ "the CREATOR object to be static on class " + name);}Class<?> creatorType = f.getType();if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) {throw new BadParcelableException("Parcelable protocol requires a "+ "Parcelable.Creator object called "+ "CREATOR on class " + name);}creator = (Parcelable.Creator<?>) f.get(null);}catch (IllegalAccessException e) {Log.e(TAG, "Illegal access when unmarshalling: " + name, e);throw new BadParcelableException("IllegalAccessException when unmarshalling: " + name);}catch (ClassNotFoundException e) {Log.e(TAG, "Class not found when unmarshalling: " + name, e);throw new BadParcelableException("ClassNotFoundException when unmarshalling: " + name);}catch (NoSuchFieldException e) {throw new BadParcelableException("Parcelable protocol requires a "+ "Parcelable.Creator object called "+ "CREATOR on class " + name);}if (creator == null) {throw new BadParcelableException("Parcelable protocol requires a "+ "non-null Parcelable.Creator object called "+ "CREATOR on class " + name);}map.put(name, creator);}}return creator; }
在readParcelable函數(shù)中調(diào)用readParcelableCreator函數(shù)來(lái)解析數(shù)據(jù),在這個(gè)函數(shù)中就可以看到同樣需要查找class來(lái)反序列化,而不同的是對(duì)Expection沒(méi)有直接拋出,而是包裝成BadParcelableException拋出的,這也是為什么crash信息有區(qū)別。
你以為這樣就結(jié)束了?還沒(méi)有! 讓我們回到之前的unparcel函數(shù),看看最后部分的代碼: /* package */ synchronized void unparcel() {synchronized (this) {...try {mParcelledData.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;mParcelledData.recycle();mParcelledData = null;}if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))+ " final map: " + mMap);} }可以看到mParcelledData.readArrayMapInternal是在一個(gè)try-catch中的,而是catch部分又catch了BadParcelableException,這里就有了一個(gè)小彩蛋:當(dāng)sShouldDefuse為true時(shí),這個(gè)錯(cuò)誤就被吞掉了,而為false時(shí)繼續(xù)拋出。
那么這個(gè)sShouldDefuse的值怎么來(lái)的? 在BaseBundle中sShouldDefuse默認(rèn)是false,但是有一個(gè)函數(shù)可以設(shè)值,如下: /** * Set global variable indicating that any Bundles parsed in this process * should be "defused." That is, any {@link BadParcelableException} * encountered will be suppressed and logged, leaving an empty Bundle * instead of crashing. * * @hide */ public static void setShouldDefuse(boolean shouldDefuse) {sShouldDefuse = shouldDefuse; }這個(gè)函數(shù)是static的,但是是隱藏的,所以我們不能直接使用。通過(guò)這個(gè)函數(shù)的注釋我們可以知道,當(dāng)設(shè)為true的時(shí)候,會(huì)吞掉所有BadParcelableException錯(cuò)誤,這時(shí)會(huì)返回一個(gè)空的Bundle代替crash。
根據(jù)網(wǎng)上相關(guān)android framwork層源碼來(lái)看,高版本的android系統(tǒng)中默認(rèn)將其設(shè)置為true,應(yīng)該是google做的一步優(yōu)化。具體那個(gè)版本開(kāi)始的還有待調(diào)查。經(jīng)過(guò)測(cè)試發(fā)現(xiàn),不論serializable還是Parcelable在部分華為手機(jī)上并不會(huì)crash,估計(jì)是華為系統(tǒng)對(duì)此進(jìn)行了優(yōu)化,將問(wèn)題直接吞掉了。 而serializable的情況,android各個(gè)版本(8.0未測(cè)試)都還存在這個(gè)問(wèn)題。 Parcelable則像上面說(shuō)的,高版本已經(jīng)處理了,具體那個(gè)版本還需要調(diào)查一下。 目前想到的解決方法是,在對(duì)外的Activity中如果獲取bundle數(shù)據(jù),try-catch一下。
?
總結(jié)
以上是生活随笔為你收集整理的Bundle/Intent传递序列化参数暗藏杀机的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 探索startActivity流程及在A
- 下一篇: 非UI线程下页面处理:view的post