日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Bundle/Intent传递序列化参数暗藏杀机

發(fā)布時間:2024/4/15 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Bundle/Intent传递序列化参数暗藏杀机 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
前幾天一個朋友跟我說了一個詭異且恐怖的事情,有個人用了幾行代碼就讓他們的app歇菜了。 這勾起了我極大的興趣,于是我親自嘗試了一下。代碼非常簡單,如下: Intent intent = new Intent(); intent.setComponent(new ComponentName("com.test.test", "com.test.test.MainActivity")); intent.putExtra("anykey", new Boom()); startActivity(intent);

其中Boom是一個序列化類(serializable),而且extra的key可以是任何值。

而com.test.test.MainActivity則是另外一個app中允許外部調(diào)起的activity,即MainActivity有一個為android.intent.action.MAIN的action,否則代碼會報錯。 還需要滿足一個條件,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,任何類型的都會出問題,而且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īng)用中沒有Boom這個類,反序列化時找不到,那么既然應(yīng)用中沒有用到anykey,為什么會去做反序列化的操作呢? 查看Intent及Bundle源碼可以發(fā)現(xiàn),那些get函數(shù)最終都會調(diào)用BaseBundle的相應(yīng)的get函數(shù)。 在BaseBundle中不論get函數(shù)還是put函數(shù)中都會先調(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ù)我們后面再說,先看看在這兩種函數(shù)中存取數(shù)據(jù)實際上都是在mMap中做的,這是BaseBundle中一個重要的參數(shù),它存儲著Bundle的數(shù)據(jù)。

那么這個mMap中的數(shù)據(jù)又是哪里來的呢? 下面我們就來看看這個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中的兩個重要參數(shù)mMap和mParcelledData,mMap我們上面說過,另外一個mParcelledData則是一個Parcel對象。它是怎么來的呢?

這要從activity的啟動過程來說,參見探索startActivity流程及在Activity間是如何傳遞Intent的。在這篇文章的最后,我們看到在ActivityManagerNative中onTransact函數(shù)中處理binder接收的消息,其中就有這么一行: Bundle options = data.readInt() != 0? Bundle.CREATOR.createFromParcel(data) : null;

這行的作用就是從binder的消息中解析出傳送過來的Bundle數(shù)據(jù),繼續(xù)看來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ù)其實就是調(diào)用來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; }

通過Bundle的構(gòu)造函數(shù)來新建了一個對象,這個構(gòu)造函數(shù)則調(diào)用了父類BaseBundle對應(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的來源,它實際上就是傳送過來的Bundle序列化后的數(shù)據(jù)。

那么就有了另外一個疑問,既然傳送過來的只有mParcelledData,那么mMap中其實是空的,那么get函數(shù)怎么取到值的? 這就是為什么每個get和put函數(shù)都先調(diào)用unparcel函數(shù)的原因。繼續(xù)觀察上面的unparcel函數(shù),我們發(fā)現(xiàn)“mParcelledData.readArrayMapInternal(map, N, mClassLoader);”這句代碼,調(diào)用了Parcel的readArrayMapInternal函數(shù),并且傳入了map,這個map后面會賦值給mMap,所以實際上兩者是一致的。函數(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(); }

在這個函數(shù)里就可以比較明顯的看出來,從Parcel中分別讀取出key和value,然后put進(jìn)map中。這樣就解決了之前的疑惑,unparcel函數(shù)的作用實際上是預(yù)處理,提前將序列化的數(shù)據(jù)反序列化并放入mMap中,然后Bundle再從mMap中存取數(shù)據(jù)。

我們越來越接近真相了!讀取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ù)不同的類型調(diào)用不同的函數(shù)來獲得value,這里我們只關(guān)注Serializable這個類型,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);} }

我們終于找到了最開始的崩潰錯誤的源頭,在這里反序列化時需要根據(jù)類名去找到Class對象,這時就出問題了,因為通過上面我們知道,unparcel函數(shù)預(yù)處理時會將mParcelledData中所有的數(shù)據(jù)都解析出來,這時當(dāng)解析到最開始的Boom類時,由于在本App中并不存在這個類,所以無法找到這個類,這樣就出問題了。這樣也解釋了為什么任意key都會出問題。


上面我們只說到了序列化的一種:serializable,我們知道在Android中還有另外一種推薦的序列化:Parcelable。 至于這兩種序列化的差別,請參考:Android中Intent/Bundle的通信原理及大小限制(Parcelable原理及與Serializable的區(qū)別) 那么Parcelable會出現(xiàn)這種crash么,經(jīng)測試也會出現(xiàn)這樣的問題,但是報出的錯誤是不同的: 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) ? ... 情況其實與serializable差不多,差別在readValue函數(shù)這一步調(diào)用了另外一個函數(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ù)來解析數(shù)據(jù),在這個函數(shù)中就可以看到同樣需要查找class來反序列化,而不同的是對Expection沒有直接拋出,而是包裝成BadParcelableException拋出的,這也是為什么crash信息有區(qū)別。

你以為這樣就結(jié)束了?還沒有! 讓我們回到之前的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是在一個try-catch中的,而是catch部分又catch了BadParcelableException,這里就有了一個小彩蛋:當(dāng)sShouldDefuse為true時,這個錯誤就被吞掉了,而為false時繼續(xù)拋出。

那么這個sShouldDefuse的值怎么來的? 在BaseBundle中sShouldDefuse默認(rèn)是false,但是有一個函數(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; }

這個函數(shù)是static的,但是是隱藏的,所以我們不能直接使用。通過這個函數(shù)的注釋我們可以知道,當(dāng)設(shè)為true的時候,會吞掉所有BadParcelableException錯誤,這時會返回一個空的Bundle代替crash。

根據(jù)網(wǎng)上相關(guān)android framwork層源碼來看,高版本的android系統(tǒng)中默認(rèn)將其設(shè)置為true,應(yīng)該是google做的一步優(yōu)化。具體那個版本開始的還有待調(diào)查。
經(jīng)過測試發(fā)現(xiàn),不論serializable還是Parcelable在部分華為手機(jī)上并不會crash,估計是華為系統(tǒng)對此進(jìn)行了優(yōu)化,將問題直接吞掉了。 serializable的情況,android各個版本(8.0未測試)都還存在這個問題。 Parcelable則像上面說的,高版本已經(jīng)處理了,具體那個版本還需要調(diào)查一下。 目前想到的解決方法是,在對外的Activity中如果獲取bundle數(shù)據(jù),try-catch一下。

?

總結(jié)

以上是生活随笔為你收集整理的Bundle/Intent传递序列化参数暗藏杀机的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。