php
javastacktrace:
java.lang.RuntimeException:UnabletostartactivityComponentInfo{com.haier.uhome.uplus/com.haier.uhome.uplus.linkage.device_find.ui.LoginDeviceActivity}:android.os.BadParcelableException:ClassNotFoundExceptionwhenunmarshalling:com.haier.uhome.uplus.linkage.device_find.domain.DeviceStatus
atandroid.app.ActivityThread.performLaunchActivity(ActivityThread.java:3816)
atandroid.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3962)
atandroid.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:101)
atandroid.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
atandroid.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
atandroid.app.ActivityThread$H.handleMessage(ActivityThread.java:2380)
atandroid.os.Handler.dispatchMessage(Handler.java:106)
atandroid.os.Looper.loopOnce(Looper.java:210)
atandroid.os.Looper.loop(Looper.java:299)
atandroid.app.ActivityThread.main(ActivityThread.java:8240)
atjava.lang.reflect.Method.invoke(NativeMethod)
atcom.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:559)
atcom.android.internal.os.ZygoteInit.main(ZygoteInit.java:954)
Causedby:android.os.BadParcelableException:ClassNotFoundExceptionwhenunmarshalling:com.haier.uhome.uplus.linkage.device_find.domain.DeviceStatus
atandroid.os.Parcel.readParcelableCreatorInternal(Parcel.java:4903)
atandroid.os.Parcel.readParcelableInternal(Parcel.java:4766)
atandroid.os.Parcel.readValue(Parcel.java:4532)
atandroid.os.Parcel.readValue(Parcel.java:4312)
atandroid.os.Parcel.-$$Nest$mreadValue(UnknownSource:0)
atandroid.os.Parcel$LazyValue.apply(Parcel.java:4410)
atandroid.os.Parcel$LazyValue.apply(Parcel.java:4369)
atandroid.os.BaseBundle.getValueAt(BaseBundle.java:394)
atandroid.os.BaseBundle.getValue(BaseBundle.java:374)
atandroid.os.BaseBundle.getValue(BaseBundle.java:357)
atandroid.os.BaseBundle.getValue(BaseBundle.java:350)
源码执行实例化对象过程分析
1. Bundle 的 getParcelable
源码路径:/frameworks/base/core/java/android/os/Bundle.java
2. 重点在 unparcel()是父类里的方法。
路径在/frameworks/base/core/java/android/os/BaseBundle.java
3. 继续往下跟踪,initializeFromParcelLocked 方法
arduino
private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,409 boolean parcelledByNative) {
410 //。。。。。省略无关代码
433 ArrayMap<String, Object> map = mMap;
434 if (map == null) {
435 map = new ArrayMap<>(count);
436 } else {
437 map.erase();
438 map.ensureCapacity(count);
439 }
440 try {
//重点在这,留意mClassLoader
441 recycleParcel &= parcelledData.readArrayMap(map, count, !parcelledByNative,
442 /* lazy */ true, mClassLoader);
443 } catch (BadParcelableException e) {
444 if (sShouldDefuse) {
445 Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
446 map.erase();
447 } else {
448 throw e;
449 }
450 } finally {
451 .......
460 }
461 if (DEBUG) {
462 Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
463 + " final map: " + mMap);
464 }
465 }
4. 继续跟踪到 parcelledData.readArrayMap
属于/frameworks/base/core/java/android/os/Parcel.java文件
yaml
boolean readArrayMap(ArrayMap<? super String, Object> map, int size, boolean sorted,
5204 boolean lazy, @Nullable ClassLoader loader) {
5205 boolean recycle = true;
5206 while (size > 0) {
5207 String key = readString();
//重点在这 lazy为true
5208 Object value = (lazy) ? readLazyValue(loader) : readValue(loader);
5209 if (value instanceof LazyValue) {
5210 recycle = false;
5211 }
5212 if (sorted) {
5213 map.append(key, value);
5214 } else {
5215 map.put(key, value);
5216 }
5217 size--;
5218 }
5219 if (sorted) {
5220 map.validate();
5221 }
5222 return recycle;
5223 }
5. 跟踪到当前文件的 readLazyValue 最终调用的还是 readValue 方法。直接看 readValue
less
private <T> T readValue(int type, @Nullable ClassLoader loader, @Nullable Class<T> clazz,
@Nullable Class<?>... itemTypes) {
final Object object;
switch (type) {
......省略无关代码,只关注Parcelable
case VAL_PARCELABLE:
object = readParcelableInternal(loader, clazz);
break;
.......
}
}
6. readParcelableInternal 最终调用的是 readParcelableCreatorInternal ,直接看 readParcelableCreatorInternal
java
private <T> Parcelable.Creator<T> readParcelableCreatorInternal(
@Nullable ClassLoader loader, @Nullable Class<T> clazz) {
//。。。。。。
try {
// 这两行是重点,前边传递过来的loader正常情况下不为null且为PathClassLoader实例
ClassLoader parcelableClassLoader =
(loader == null ? getClass().getClassLoader() : loader);
// Avoid initializing the Parcelable class until we know it implements
// Parcelable and has the necessary CREATOR field. http://b/1171613.
Class<?> parcelableClass = Class.forName(name, false /* initialize */,
parcelableClassLoader);
if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
throw new BadParcelableException("Parcelable protocol requires subclassing "
+ "from Parcelable on class " + name);
}
//。。。
} catch (IllegalAccessException e) {
Log.e(TAG, "Illegal access when unmarshalling: " + name, e);
throw new BadParcelableException(
"IllegalAccessException when unmarshalling: " + name, e);
} catch (ClassNotFoundException e) {
Log.e(TAG, "Class not found when unmarshalling: " + name, e);
throw new BadParcelableException(
"ClassNotFoundException when unmarshalling: " + name, e);
} catch (NoSuchFieldException e) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "Parcelable.Creator object called "
+ "CREATOR on class " + name, e);
}
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "non-null Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
synchronized (mCreators) {
map.put(name, creator);
}
return (Parcelable.Creator<T>) creator;
}
7. 问题分析
当前问题是偶现问题,说明类文件路径是正确,且类是一定被打包进入了 apk 内部的。那么为什么找不到会出现这个异常呢?
ini
Class<?> parcelableClass = Class.forName(name, false /* initialize */,
parcelableClassLoader);
异常就是这行代码报的。找不到类。
Class.forName 报异常的两种可能
- name 参数不对,类因为打包,或者由于分包找不到了。
不符合当前问题原因有:
- 当前问题不是毕现。
- 当前问题都是在后台出现的,在前台时不复现
- parcelableClassLoader 不对
早在 Android6 之前系统就存在类似的 bug
issuetracker.google.com/issues/3707...
google 工程师回复,在 6 以后就不再复现了。
This issue could not be reproduced in Marshmallow builds. At this point, our eng teams are not prioritizing changes on earlier releases. Please do let us know if you encounter this issue on 6.0+.
当前问题就是 parcelableClassLoader 导致
为什么不对?
Android 有两种不同的 classloaders:PathClassLoader 和 BootClassLoader,其中 BootClassLoader 知道怎么加载 android 系统类,PathClassLoader 知道怎么加载自定义类,PathClassLoader 继承自 BootClassLoader,所以也知道怎么加载 android classes。
start Activity 时,实例化的 Bundle 使用的默认的 classLoader,即 BootClassLoader。而序列化 对象时不需要 loader
而在页面 start 的时候 fmk 主动给 bundle 设置了 PathClassLoader。这样我们就可以反实例化自定义类了
可以在 Activity 反实例化对象时确认下
所以在 Activity 反实例化时,如果 Bundle 的 loader 为 null,就会使用 Parcel.class.getClassLoader(),这个 classLoader 是什么呢?
结论
进程在后台时,资源被回收导致 Bundle 的 classLoader 等资源被回收。再恢复页面时 Bundle 的 classLoader 变成了默认的 classLoader >>>BootClassLoader,或者 null,都会导致当前 bug 出现。
解决方案
ini
Intent intent = getIntent();
Bundle extras = intent.getExtras();
//异常时Bundle的classLoader为null或BootClassLoader。所以这里主动设置为PathClassLoader
//extras.setClassLoader(DeviceStatus.class.getClassLoader());
deviceStatus = extras.getParcelable(UdpSocketUtils.INTENT_DATA);
8. 团队介绍
「三翼鸟数字化技术平台-场景设计交互平台」 主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。