前言

10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: FATAL EXCEPTION: main10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: Process: com.baidu.searchbox, PID: 2200010-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: android.support.v4.view.ViewPager$SavedState10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Parcel.readParcelableCreator(Parcel.java:2831)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Parcel.readParcelable(Parcel.java:2757)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Parcel.readValue(Parcel.java:2660)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Parcel.readSparseArrayInternal(Parcel.java:3110)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Parcel.readSparseArray(Parcel.java:2343)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Parcel.readValue(Parcel.java:2717)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Parcel.readArrayMapInternal(Parcel.java:3029)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:288)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.BaseBundle.unparcel(BaseBundle.java:232)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Bundle.getSparseParcelableArray(Bundle.java:1010)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2133)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.Activity.onRestoreInstanceState(Activity.java:1161)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at com.baidu.searchbox.appframework.BaseActivity.onRestoreInstanceState(SourceFile:249)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.Activity.performRestoreInstanceState(Activity.java:1116)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1318)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2986)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.os.Looper.loop(Looper.java:202)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6806)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

(基于 Android P)

上面这段 log 是我们最近调查的一个问题,从 log 中可以看出,这个问题发生在 activity onRestoreInstanceState 的过程中,所做的操作是 restoreHierarchyState,如下所示:
PhoneWindow.java

    /** {@inheritDoc} */    @Override    public void restoreHierarchyState(Bundle savedInstanceState) {        if (mContentParent == null) {            return;        }        SparseArray<Parcelable> savedStates                = savedInstanceState.getSparseParcelableArray(VIEWS_TAG); // line 2133        if (savedStates != null) {            mContentParent.restoreHierarchyState(savedStates);        }        ...    }

可以看到其是在从 savedInstanceState 中取 SparseParcelableArray,因为当时系统中有多个 app 发生这个问题,有系统 app 也有三方 app,调用栈基本类似,因此我们基本可以排除这个 Class 真的不存在的情况,剩下的最可能的原因就是 ClassLoader 出问题了。
因为问题发生在 restore 时,所以我们应该首先考虑一下 Bundle 中存储的数据是什么,存储的时候有没有什么问题,下面来看一下 onSaveInstanceState

一、onSaveInstanceState

首先,看一下 onSaveInstanceState 执行的时机:

  java.lang.Thread.State: RUNNABLE  at android.app.Activity.onSaveInstanceState(Activity.java:1671)  at android.app.Activity.performSaveInstanceState(Activity.java:1587)  at android.app.Instrumentation.callActivityOnSaveInstanceState(Instrumentation.java:1444)  at android.app.ActivityThread.callActivityOnSaveInstanceState(ActivityThread.java:4868)  at android.app.ActivityThread.callActivityOnStop(ActivityThread.java:4192)  at android.app.ActivityThread.performStopActivityInner(ActivityThread.java:4174)  at android.app.ActivityThread.handleStopActivity(ActivityThread.java:4249)  at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:41)  at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:193)  at android.app.ActivityThread.main(ActivityThread.java:6814)  at java.lang.reflect.Method.invoke(Method.java:-1)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

1.1 onSaveInstanceState

Activity.java

    protected void onSaveInstanceState(Bundle outState) {        // WINDOW_HIERARCHY_TAG    "android:viewHierarchyState"        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);        Parcelable p = mFragments.saveAllState();        if (p != null) {            outState.putParcelable(FRAGMENTS_TAG, p);        }        if (mAutoFillResetNeeded) {            outState.putBoolean(AUTOFILL_RESET_NEEDED, true);            getAutofillManager().onSaveInstanceState(outState);        }        getApplication().dispatchActivitySaveInstanceState(this, outState);    }

可以看到 onSaveInstanceState() 会将 1.1.1 中返回的 Bundle 对象以 key “android:viewHierarchyState” 放到 Bundle outState 中,如下图所示:

1.1.1 saveHierarchyState

PhoneWindow.java

    /** {@inheritDoc} */    @Override    public Bundle saveHierarchyState() {        Bundle outState = new Bundle();        if (mContentParent == null) {            return outState;        }        SparseArray<Parcelable> states = new SparseArray<Parcelable>();        mContentParent.saveHierarchyState(states);        // VIEWS_TAG    "android:views"        // 这里对应 log 中的 getSparseParcelableArray        outState.putSparseParcelableArray(VIEWS_TAG, states);        ...        return outState;    }

可以看到 saveHierarchyState 是将 states 存放在一个 Bundle 中,即 Bundle B,我们先看一下 Bundle() 构造方法中做了什么:

    /**     * Constructs a new, empty Bundle.     */    public Bundle() {        super();        mFlags = FLAG_HAS_FDS_KNOWN | FLAG_ALLOW_FDS;    }    /**     * Constructs a new, empty Bundle.     */    BaseBundle() {        this((ClassLoader) null, 0);    }    /**     * Constructs a new, empty Bundle that uses a specific ClassLoader for     * instantiating Parcelable and Serializable objects.     *     * @param loader An explicit ClassLoader to use when instantiating objects     * inside of the Bundle.     * @param capacity Initial size of the ArrayMap.     */    BaseBundle(@Nullable ClassLoader loader, int capacity) {        mMap = capacity > 0 ?                new ArrayMap<String, Object>(capacity) : new ArrayMap<String, Object>();        mClassLoader = loader == null ? getClass().getClassLoader() : loader;    }

可以看到 Bundle() 中主要做了两件事:

  • 初始化 mMap
  • 初始化 mClassLoader,如果参数 loader 不为空则赋值为 loader,否则赋值为 getClass().getClassLoader() ,BaseBundle 类的 ClassLoader 为 BootClassLoader

所以 saveHierarchyState 中返回的 Bundle(Bundle B) 的 ClassLoader 为 BootClassLoader

1.2 onSaveInstanceState() 的 Bundle

onSaveInstanceState() 中的 Bundle 对象是作为参数传进来的,可以看到其初始化的地方是在 callActivityOnSaveInstanceState 中
ActivityThread.java

    private void callActivityOnSaveInstanceState(ActivityClientRecord r) {        r.state = new Bundle();        r.state.setAllowFds(false);        if (r.isPersistable()) {            r.persistentState = new PersistableBundle();            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,                    r.persistentState);        } else {            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);        }    }

可以看到这个 Bundle 对象,即 Bundle A 也是由无参的构造函数 Bundle() 来创建的,所以其状况与 1.1.1 中相同,ClassLoader 为 BootClassLoader

前言 log 中的 Exception 为 ClassNotFoundException when unmarshalling: android.support.v4.view.ViewPager$SavedState,猜测 ClassNotFoundException 是由于 ClassLoader 不对造成的,根据调试发现,这里正常情况下应该为 PathClassLoader,而此时为 BootClassLoader

根据我们前面的分析来看,在 Bundle 对象创建时其 ClassLoader 为 BootClassLoader,那么是什么时候改成 PathClassLoader 的呢?

二、performLaunchActivity

ActivityThread.java

    /**  Core implementation of activity launch. */    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {        ...        ContextImpl appContext = createBaseContextForActivity(r);        Activity activity = null;        try {            java.lang.ClassLoader cl = appContext.getClassLoader();            activity = mInstrumentation.newActivity(                    cl, component.getClassName(), r.intent);            StrictMode.incrementExpectedActivityCount(activity.getClass());            r.intent.setExtrasClassLoader(cl);            r.intent.prepareToEnterProcess();            if (r.state != null) {                r.state.setClassLoader(cl);            }        } catch (Exception e) {            ...        }        try {            Application app = r.packageInfo.makeApplication(false, mInstrumentation);            ...            if (activity != null) {                ...                if (r.isPersistable()) {                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);                } else {                    mInstrumentation.callActivityOnCreate(activity, r.state);                }                if (!activity.mCalled) {                    throw new SuperNotCalledException(                        "Activity " + r.intent.getComponent().toShortString() +                        " did not call through to super.onCreate()");                }                r.activity = activity;            }            r.setState(ON_CREATE);            mActivities.put(r.token, r);        } ...        return activity;    }

可以看到对于 Bundle 对象的 ClassLoader 的修改是在 performLaunchActivity 中,会将其设置为 appContext 的 ClassLoader 即 PathClassLoader,这里设置的 Bundle 对应图中的 Bundle A,那么对于内层的 Bundle B 是什么时候修改的呢?

  java.lang.Thread.State: RUNNABLE  at android.os.Parcel.readBundle(Parcel.java:2159)  at android.os.Parcel.readValue(Parcel.java:2723)  at android.os.Parcel.readArrayMapInternal(Parcel.java:3029)  at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:288)  at android.os.BaseBundle.unparcel(BaseBundle.java:232)  - locked <0x21ba> (a android.os.Bundle)  at android.os.BaseBundle.getBoolean(BaseBundle.java:903)  at android.app.Activity.restoreHasCurrentPermissionRequest(Activity.java:7516)  at android.app.Activity.performCreate(Activity.java:7206)  at android.app.Activity.performCreate(Activity.java:7201)  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2926)  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3081)  at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loop(Looper.java:193)  at android.app.ActivityThread.main(ActivityThread.java:6806)  at java.lang.reflect.Method.invoke(Method.java:-1)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)

可以看到 Bundle B 的 ClassLoader 是在 restoreHasCurrentPermissionRequest() 方法中设置的,方法如下:

    private void restoreHasCurrentPermissionRequest(Bundle bundle) {        if (bundle != null) {            mHasCurrentPermissionsRequest = bundle.getBoolean(                    HAS_CURENT_PERMISSIONS_REQUEST_KEY, false);        }    }

是在对外层的 Bundle A 执行 getBoolean 方法时设置的,下面具体看一下是怎么设置的,
Parcel.java

    /**     * Read and return a new Bundle object from the parcel at the current     * dataPosition(), using the given class loader to initialize the class     * loader of the Bundle for later retrieval of Parcelable objects.     * Returns null if the previously written Bundle object was null.     */    public final Bundle readBundle(ClassLoader loader) {        int length = readInt();        if (length < 0) {            if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);            return null;        }        final Bundle bundle = new Bundle(this, length);        if (loader != null) {            bundle.setClassLoader(loader);        }        return bundle;    }

可以看到是在执行 readBundle 的时候,将 ClassLoader 设置为传入的参数 loader,那么这个参数 loader 是怎么来的呢?

    private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,            boolean parcelledByNative) {        // 1、parcelledData 为空,重置成员后 return        if (isEmptyParcel(parcelledData)) {            if (DEBUG) {                Log.d(TAG, "unparcel "                        + Integer.toHexString(System.identityHashCode(this)) + ": empty");            }            if (mMap == null) {                mMap = new ArrayMap<>(1);            } else {                mMap.erase();            }            mParcelledData = null;            mParcelledByNative = false;            return;        }        final int count = parcelledData.readInt();        if (count < 0) {            return;        }        ArrayMap<String, Object> map = mMap;        if (map == null) {            map = new ArrayMap<>(count);        } else {            map.erase();            map.ensureCapacity(count);        }        // 2、将数据从 parcelledData 中读取出来,放到 map 中        try {            if (parcelledByNative) {                // If it was parcelled by native code, then the array map keys aren't sorted                // by their hash codes, so use the safe (slow) one.                parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);            } else {                // If parcelled by Java, we know the contents are sorted properly,                // so we can use ArrayMap.append().                parcelledData.readArrayMapInternal(map, count, mClassLoader);            }        } catch (BadParcelableException e) {            ...        } finally {            // 3、对成员进行赋值            mMap = map;            if (recycleParcel) {                recycleParcel(parcelledData);            }            mParcelledData = null;            mParcelledByNative = false;        }        ...    } 

上面这个方法可以说是从 Parcel 中创建 Bundle 的主体实现部分,需要关注几个地方:

  • 如果 parcelledData 为空,则重置成员后直接返回
  • 如果不为空,会将 parcelledData 中的数据读取出来,放到 mMap 中,并重置成员 mParcelledData 和 mParcelledByNative

在执行 readArrayMapInternal() 方法时,会将 BaseBundle 的 mClassLoader 传递过去,在这里也就是我们外层 Bundle A 的 PathClassLoader
现在我们知道了,正常情况下,Bundle A 和 Bundle B 对象在 Activity 的启动过程中都会将 ClassLoader 设置为 PathClassLoader,那这里为什么 Bundle B 对象的 ClassLoader 会是 BootClassLoader 呢?

三、原因探究

经过调查,发现是一个 change 导致的:
Looper.java

    public static void loop() {        final Looper me = myLooper();        ...        for (;;) {            Message msg = queue.next(); // might block            ...            // change add:            getMessageString(msg);            ...            try {                msg.target.dispatchMessage(msg);                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;            } finally {                if (traceTag != 0) {                    Trace.traceEnd(traceTag);                }            }            ...            msg.recycleUnchecked();        }    }

如上所示,在 msg dispatch 之前调用了 getMessageString(msg),其实现如下所示:

    public static String getMessageString(Message msg) {        String result = "";        try {            result = msg.toString();        } catch (Exception e) {            Log.e(TAG, "getMessageString failed ! " + e.getMessage());        }        return result;    }

那么是如何影响到 ClassLoader 的呢?看一下调用栈:

android.os.Parcel.readBundle:2159android.os.Parcel.readValue:2723android.os.Parcel.readArrayMapInternal:3029android.os.BaseBundle.initializeFromParcelLocked:288android.os.BaseBundle.unparcel:232android.os.BaseBundle.size:359android.app.servertransaction.LaunchActivityItem.hashCode:193java.util.AbstractList.hashCode:541java.util.Objects.hashCode:98android.app.servertransaction.ClientTransaction.hashCode:236java.lang.Object.toString:273java.lang.String.valueOf:2896java.lang.StringBuilder.append:132android.os.Message.toString:535android.os.Message.toString:506

可以看到这里最终也会调用到 readBundle 方法,再来回顾一下它的实现:

    /**     * Read and return a new Bundle object from the parcel at the current     * dataPosition(), using the given class loader to initialize the class     * loader of the Bundle for later retrieval of Parcelable objects.     * Returns null if the previously written Bundle object was null.     */    public final Bundle readBundle(ClassLoader loader) {        int length = readInt();        if (length < 0) {            if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);            return null;        }        final Bundle bundle = new Bundle(this, length);        if (loader != null) {            bundle.setClassLoader(loader);        }        return bundle;    }

可以看到,其会 setClassLoader,这个 loader 是 Bundle A 的 ClassLoader,因为 getMessageString() 方法是在 msg.target.dispatchMessage(msg) 之前,因此,此时还没有执行 performLaunchActivity,也就是说此时 Bundle A 的 ClassLoader 仍旧为 BootClassLoader;并且这里 initializeFromParcelLocked 之后,会设置

mParcelledData = null;mParcelledByNative = false;

也就是说,后面 restoreHasCurrentPermissionRequest 时,不会再从 parcelledData 中读取数据了,也就不会再执行 readBundle,所以 Bundle B 的 ClassLoader 将会一直为 BootClassLoader

更多相关文章

  1. [Android] Android打开WIFI或者移动网络
  2. Android中重写onBackPressed()方法实现双击退出
  3. Android(安卓)四大组件--service的使用与生命周期
  4. android屏幕保持唤醒
  5. Android(安卓)View的构造方法
  6. Android中intent的使用
  7. 浅谈Java中Collections.sort对List排序的两种方法
  8. 类和 Json对象
  9. Python list sort方法的具体使用

随机推荐

  1. [置顶] Android底层库和程序
  2. android 数据储存——--SQLite存储方式(3)
  3. android services——学习
  4. Android原生(Native)C开发之八:Toolchain
  5. Android官方说明-Activity任务栈
  6. Android 基础:常用布局 介绍 & 使用(附 属
  7. Android设置沉浸式状态栏时改变状态栏的
  8. android 将app添加进入文件的打开方式
  9. Android 监听EditText输入框软键盘显示及
  10. 【Android】源码项目编译ccache配置