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

相关推荐
zh_xuan12 小时前
kotlin定义函数和变量
android·开发语言·kotlin
Android-Flutter21 小时前
Compose - Scaffold使用
android·kotlin
zh_xuan1 天前
kotlin的常见空检查
android·开发语言·kotlin
quanyechacsdn2 天前
Android Studio创建库文件用jitpack构建后使用implementation方式引用
android·ide·kotlin·android studio·implementation·android 库文件·使用jitpack
Huang兄2 天前
kotlin协程-官方框架
kotlin
Huang兄2 天前
kotlin协程-基础设施篇-函数的挂起
kotlin
Kapaseker2 天前
你不看会后悔的2025年终总结
android·kotlin
alexhilton2 天前
务实的模块化:连接模块(wiring modules)的妙用
android·kotlin·android jetpack
幽络源小助理3 天前
下载安装AndroidStudio配置Gradle运行第一个kotlin程序
android·开发语言·kotlin