Intent 获取 parcel 数据 ClassNotFoundException 崩溃

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 方法

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 文件

     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

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

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 内部的。那么为什么找不到会出现这个异常呢?

Class<?> parcelableClass = Class.forName(name, false /* initialize */,
                   parcelableClassLoader);

异常就是这行代码报的。找不到类。

Class.forName 报异常的两种可能

  1. name 参数不对,类因为打包,或者由于分包找不到了。

不符合当前问题原因有:

  1. 当前问题不是毕现。

  2. 当前问题都是在后台出现的,在前台时不复现

  1. parcelableClassLoader 不对

早在 Android6 之前系统就存在类似的 bug

https://issuetracker.google.com/issues/37073849

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 出现。

解决方案

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端全流程系统。

  • 29
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值