ReportFragment cannot be cast to ReportFragment

环境

  • Replugin插件框架

问题原因

问题原因的分析这段文字没有经过润色,纯粹是初稿,不一定易于阅读,这段看不下去的同学可以直接看文章最后的解决方案。
ReportFragmentandroid.arch.lifecycle组件中的类,lifecycle组件中的ProcessLifecycleOwnerInitializer继承自ContentProvider,利用ContentProvider#onCreate()方法初始化。

在初始化时,通过Application#registerActivityLifecycleCallbacks()注册Activity的生命周期监听,在onActivityCreated()回调中为每一个Activity注入一个ReportFragment

public static void injectIfNeededIn(Activity activity) {
        // ProcessLifecycleOwner should always correctly work and some activities may not extend
        // FragmentActivity from support lib, so we use framework fragments for activities
        android.app.FragmentManager manager = activity.getFragmentManager();
        if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
            manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
            // Hopefully, we are the first to make a transaction.
            manager.executePendingTransactions();
        }
    }

注入ReportFragment之后,ProcessLifecycleOwner中也会监听Activity的生命周期,并且在onActivityCreated()回调中通过ReportFragment.get()方法获取ReportFragment

static ReportFragment get(Activity activity) {
        return (ReportFragment) activity.getFragmentManager().findFragmentByTag(
                REPORT_FRAGMENT_TAG);
    }

出错的代码就是get()方法中的转型。

ReportFragment为什么不能被转型成ReportFragment呢?因为它们的ClassLoader不同,但是从ReportFragment的注入方法:injectIfNeededIn()和使用方法:get()来看,这两处代码都是ProcessLifecycleOwnerInitializer在初始化时注册的Activity的生命周期监听中回调的。又因为在类A中通过new关键字创建类B,类B的ClassLoader就是类A的ClassLoader,那么这两处代码中的ReportFragment的Classloader应该是同一个,为什么会出现ClassLoader不一致的情况呢?

这就要从FragmentActivity的状态保存说起,来看代码,FragmentActivity#onSaveInstanceState()中,this.mFragments.saveAllState会保存Fragment

protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        this.markFragmentsCreated();
        Parcelable p = this.mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable("android:support:fragments", p);
        }
        ...
        }
    }

最终通过new FragmentState()Fragment序列化。

 Parcelable saveAllState() {
        ...
        if (this.mActive != null && this.mActive.size() > 0) {
            int N = this.mActive.size();
            FragmentState[] active = new FragmentState[N];
            boolean haveFragments = false;

            for(int i = 0; i < N; ++i) {
                Fragment f = (Fragment)this.mActive.valueAt(i);
                if (f != null) {
                    if (f.mIndex < 0) {
                        this.throwException(new IllegalStateException("Failure saving state: active " + f + " has cleared index: " + f.mIndex));
                    }

                    haveFragments = true;
                    FragmentState fs = new FragmentState(f);
                    active[i] = fs;
                    ...

而序列化时,只保存了Fragment的类名,ClassLoader信息就丢失了。

FragmentState(Fragment frag) {
        this.mClassName = frag.getClass().getName();
        this.mIndex = frag.mIndex;
        this.mFromLayout = frag.mFromLayout;
        this.mFragmentId = frag.mFragmentId;
        this.mContainerId = frag.mContainerId;
        this.mTag = frag.mTag;
        this.mRetainInstance = frag.mRetainInstance;
        this.mDetached = frag.mDetached;
        this.mArguments = frag.mArguments;
        this.mHidden = frag.mHidden;
    }

当后台App由于内存不足被kill后,从最近使用的App列表中返回App,系统会恢复切后台时所处的Activity,在Activity#onCreate()中通过this.mFragments.restoreAllState恢复Fragment

protected void onCreate(@Nullable Bundle savedInstanceState) {
        this.mFragments.attachHost((Fragment)null);
        super.onCreate(savedInstanceState);
        FragmentActivity.NonConfigurationInstances nc = (FragmentActivity.NonConfigurationInstances)this.getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && this.mViewModelStore == null) {
            this.mViewModelStore = nc.viewModelStore;
        }

        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable("android:support:fragments");
            this.mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
            if (savedInstanceState.containsKey("android:support:next_request_index")) {
            ...

之前序列化了Fragment信息的FragmentState类,通过FragmentState#instantiate()方法,将Context, ClassName传递给Fragment#instantiate()方法,最终通过ContextClassLoader加载FragmentClass

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
        try {
            Class<?> clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                if (!Fragment.class.isAssignableFrom(clazz)) {
                    throw new InstantiationException("Trying to instantiate a class " + fname
                            + " that is not a Fragment", new ClassCastException());
                }
                sClassMap.put(fname, clazz);
            }
           ...

这个ContextActivity#mFragments中的HostCallbacks通过onGetHost获取的,就是Activity自身。

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

class HostCallbacks extends FragmentHostCallback<Activity> {
        @Override
        public Activity onGetHost() {
            return Activity.this;
        }
    }

下面了解一下Replugin的ClassLoader机制,Replugin中有宿主和插件之分,通过RePluginClassLoader加载宿主的类,通过PluginDexClassLoader加载插件的类,插件中的Activity就是PluginDexClassLoader加载的。如上所述,在内存不足的情况下,Activity中的ReportFragment是在状态恢复时,由PluginDexClassLoader加载的。

再回到出问题的代码,此时findfragmentByTag得到的ReportFragment就是由PluginDexClassLoader加载的,而转型后的ReportFragment是由系统的ClassLoader加载的,ClassLoader不同,自然就不能转型。

static ReportFragment get(Activity activity) {
        return (ReportFragment) activity.getFragmentManager().findFragmentByTag(
                REPORT_FRAGMENT_TAG);
    }

解决方案

知道了问题的来龙去脉,就好找解决方案了,只要保证LifeCycle使用的ReportFragment都是由LifeCycle组件加载,就不会有转型问题,但是Activity的状态保存步骤又不能去除,综上得到的方案就是:在Activity创建了ReportFragment之后,LifeCycle插入ReportFragment之前,移除Activity创建的ReportFragment,这样LifeCycle就会自行创建ReportFragment,就能保证LifeCycle使用的ReportFragment都是自己加载的。

方案一

在所有Activity的公共父类的onCreate中执行removeReportTag方法移除ReportFragment。

    private static void removeReportTag(Activity activity) {
            try {
                XLLog.d(TAG, "removeReportTag");
                FragmentManager manager = activity.getFragmentManager();
                if (manager != null) {
                    Fragment fragment = manager.findFragmentByTag(REPORT_FRAGMENT_TAG);
                    XLLog.d(TAG, "removeReportTag--fragment=" + fragment);
                    if (fragment != null) {
                        XLLog.d(TAG, "removeReportTag--fragment.classLoader=" + fragment.getClass().getClassLoader());
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                            manager.beginTransaction().remove(fragment).commitNow();
                        }
                    }
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
    }

这种方法的缺点是,第三方的Activity不会继承我们的公共父类,无法执行这段代码,那就看方案二。

方案二

Application#onCreate中执行HookActivityLifecycle#hook方法,通过反射在Activity生命周期的监听器list的第一位中插入一个监听器,在监听器中的onActivityCreated回调中移除ReportFragment。这个方案的缺点是,Activity中的FragmentManager中的commitNow方法在api 24及以上版本才实现,如果不用commitNow方法,在LifeCycle判断是否插入ReportFragment时,ReportFragment还没来得及被移除。

public class HookActivityLifeCycle {
    private static final String TAG = "HookActivityLifeCycle";
    private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
            + ".LifecycleDispatcher.report_fragment_tag";

    public static void hook(Application application) {
        try {
            Field[] fields = Application.class.getDeclaredFields();
            for (Field field : fields) {
                XLLog.d(TAG, "field=" + field.getName());
            }
            Field mActivityLifecycleCallbacksField = Application.class.getDeclaredField("mActivityLifecycleCallbacks");
            mActivityLifecycleCallbacksField.setAccessible(true);
            List list = (List) mActivityLifecycleCallbacksField.get(application);
            if (list != null) {
                list.add(0, new hook());
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    static class hook implements Application.ActivityLifecycleCallbacks {
        @Override
        public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
            removeReportTag(activity);
        }

        @Override
        public void onActivityStarted(@NonNull Activity activity) {

        }

        @Override
        public void onActivityResumed(@NonNull Activity activity) {

        }

        @Override
        public void onActivityPaused(@NonNull Activity activity) {

        }

        @Override
        public void onActivityStopped(@NonNull Activity activity) {

        }

        @Override
        public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

        }

        @Override
        public void onActivityDestroyed(@NonNull Activity activity) {

        }
    }

 
    private static void removeReportTag(Activity activity) {
        if (activity instanceof FragmentActivity) {
            removeReportTagV4((FragmentActivity) activity);
        } else {
            try {
                XLLog.d(TAG, "removeReportTag");
                FragmentManager manager = activity.getFragmentManager();
                if (manager != null) {
                    Fragment fragment = manager.findFragmentByTag(REPORT_FRAGMENT_TAG);
                    XLLog.d(TAG, "removeReportTag--fragment=" + fragment);
                    if (fragment != null) {
                        XLLog.d(TAG, "removeReportTag--fragment.classLoader=" + fragment.getClass().getClassLoader());
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                            manager.beginTransaction().remove(fragment).commitNow();
                        }
                    }
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * FragmentActivity中的FragmentManager的commitNow方法没有版本限制。
     */
    private static void removeReportTagV4(FragmentActivity activity) {
        try {
            XLLog.d(TAG, "removeReportTagV4");
            android.support.v4.app.FragmentManager manager = activity.getSupportFragmentManager();
            if (manager != null) {
                android.support.v4.app.Fragment fragment = manager.findFragmentByTag(REPORT_FRAGMENT_TAG);
                XLLog.d(TAG, "removeReportTagV4--fragment=" + fragment);
                if (fragment != null) {
                    XLLog.d(TAG, "removeReportTagV4--fragment.classLoader=" + fragment.getClass().getClassLoader());
                    manager.beginTransaction().remove(fragment).commitNow();
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

测试验证

这个问题的复现场景就是内存不足,那么如果模拟内存不足的情况呢,参考模拟内存不足

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章