天天看點

ClassNotFoundException 案例前言一、onSaveInstanceState二、performLaunchActivity三、原因探究

前言

10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: FATAL EXCEPTION: main
10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: Process: com.baidu.searchbox, PID: 22000
10-21 09:54:53.620 10150 22000 22000 E AndroidRuntime: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: android.support.v4.view.ViewPager$SavedState
10-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 中,如下圖所示:

ClassNotFoundException 案例前言一、onSaveInstanceState二、performLaunchActivity三、原因探究

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