FragmentFactory源码浅析

本文主要分析FragmentFactory编写前后对比,以及简单的FragmentFactory使用

简述

Fragment constructor injection is now -and has been for a while- supported in Android thanks to FragmentFactory. While it isn’t an API developers have to use, it can be regarded as a better design approach in certain situations and can help when testing Fragments with external dependencies.[1]

FragmentFactory主要用于Fragment的初始化,但是FragmentFactory并非强制使用的.默认无参的Fragment构造函数可以不使用FragmentFactory,否则还是建议使用FragmentFactory来进行实例化.[2]

源码分析

FragmentFactory最重要的方法 FragmentFactory#instantiate(ClassLoader, String) ,源码方法很短.

    /**
     * Create a new instance of a Fragment with the given class name. This uses
     * {@link #loadFragmentClass(ClassLoader, String)} and the empty
     * constructor of the resulting Class by default.
     *
     * @param classLoader The default classloader to use for instantiation
     * @param className The class name of the fragment to instantiate.
     * @return Returns a new fragment instance.
     * @throws Fragment.InstantiationException If there is a failure in instantiating
     * the given fragment class.  This is a runtime exception; it is not
     * normally expected to happen.
     */
    @NonNull
    public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
        try {
            Class cls = loadFragmentClass(classLoader, className);
            return cls.getConstructor().newInstance();
        } catch (java.lang.InstantiationException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (NoSuchMethodException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": could not find Fragment constructor", e);
        } catch (InvocationTargetException e) {
            throw new Fragment.InstantiationException("Unable to instantiate fragment " + className
                    + ": calling Fragment constructor caused an exception", e);
        }
    }

使用方法就是继承FragmentFactory之后重写 FragmentFactory#instantiate(ClassLoader, String)即可.[1]

//demo code![截屏2020-11-14 17.35.32.png](https://upload-images.jianshu.io/upload_images/1598412-b05f46325d44b35c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

class CustomFragmentFactory(private val dependency: Dependency) : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        if (className == CustomFragment::class.java.name) {
            return CustomFragment(dependency)
        }
        return super.instantiate(classLoader, className)
    }
}

为什么会有FragmentFactory?

没有FragmentFactory之前,是简单直接粗暴调用Fragment#instantiate(@NonNull Context context, @NonNull String fname)就算. 不过很多时候开发者写的Fragment都会写一个staticnewInstance(params:object)方法去实例化,当状态恢复的时候,直接调用Fragment#instantiate(@NonNull Context context, @NonNull String fname)就会出错 -- InstantiationException,所以才有了FragmentFactory+FragmentManager#getFragmentFactory()的搭配使用.

    /**
     * Like {@link #instantiate(Context, String, Bundle)} but with a null
     * argument Bundle.
     * @deprecated Use {@link FragmentManager#getFragmentFactory()} and
     * {@link FragmentFactory#instantiate(ClassLoader, String)}
     */
    @SuppressWarnings("deprecation")
    @Deprecated
    @NonNull
    public static Fragment instantiate(@NonNull Context context, @NonNull String fname) {
        return instantiate(context, fname, null);
    }

用法

如下图所示,可以通过给FragmentManager 传入一个FragmentFactory的对象(这里有个坑,下面会说到),之后对接下来的Fragment做带参实例化处理,如果Fragment可以无参实例化的话,可以不使用FragmentFactory.
然后在需要的地方调起fm.getFragmentFactory().instantiate(context.getClassLoader(), name),
e.g. FragmentManager#restoreSaveState(state),FragmentContainerView(context,attrs,fm),FragmentTabHost#doTabChanged( tag, FragmentTransaction)

FragmentFactory+FragmentManager#getFragmentFactory() 的搭配使用

问题

上面提到,其实每个 FragmentManager 只有一个 FragmentFactory 的对象,而 activity 也是只有一个 FragmentManager. 所以如果是那种[一个Activity多个业务Fragment]的模式,而且业务Fragment都在不同module以及不同framework的话,就不能够每个framework设置一个 FragmentFactory 了,否则就能够给其中一个framework使用了.如果打算每个framework设置一个 FragmentFactory ,之后Activity的 FragmentFactory 遍历各个framework的 FragmentFactory 也是不行的, 因为 FragmentFactory 的源码决定了, 如果找不到Fragment就会报错 , 当然也可以在 else 的时候,不调用 super.instantiate(classLoader, className) ,不过这样在团队开发的时候容易出错,造成APP crash.
为了解决这个问题,只能够把 FragmentFactory 放到Activity做扩展,在各个framework实现不同的delegate.

参考

  1. Android Fragments: FragmentFactory
  2. 带参构造函数为什么需要使用FragmentFactory
  3. [译][Google工程师] 详解 FragmentFactory 如何优雅使用 Koin 以及部分源码分析

你可能感兴趣的:(FragmentFactory源码浅析)