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

相关推荐
alexhilton6 小时前
在Android应用中实战Repository模式
android·kotlin·android jetpack
雨白12 小时前
高阶函数与内联优化
kotlin
&岁月不待人&15 小时前
实现弹窗随键盘上移居中
java·kotlin
移动开发者1号21 小时前
Android Activity状态保存方法
android·kotlin
移动开发者1号21 小时前
Volley源码深度分析与设计亮点
android·kotlin
移动开发者1号2 天前
App主界面点击与跳转启动方式区别
android·kotlin
移动开发者1号2 天前
我用Intent传大图片时竟然崩了,怎么回事啊
android·kotlin
androidwork2 天前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
androidwork2 天前
OkHttp 3.0源码解析:从设计理念到核心实现
android·java·okhttp·kotlin
莉樱Yurin2 天前
Kotlin/CLR 让Kotlin走进.NET世界
kotlin