Intent 获取 parcel 数据 ClassNotFoundException 崩溃

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 报异常的两种可能

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

不符合当前问题原因有:

  1. 当前问题不是毕现。
  2. 当前问题都是在后台出现的,在前台时不复现
  1. 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端全流程系统。

相关推荐
一点媛艺7 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风7 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
萌面小侠Plus16 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
wk灬丨20 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
晨曦_子画21 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
大福是小强21 小时前
005-Kotlin界面开发之程序猿初试Composable
kotlin·界面开发·桌面应用·compose·jetpack·可组合
&岁月不待人&1 天前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
小白学大数据1 天前
正则表达式在Kotlin中的应用:提取图片链接
开发语言·python·selenium·正则表达式·kotlin
bytebeats3 天前
Kotlin 注解全面指北
android·java·kotlin
jzlhll1233 天前
kotlin android Handler removeCallbacks runnable不生效的一种可能
android·开发语言·kotlin