FragmentFactory :功能详解&使用场景

简介: FragmentFactory :功能详解&使用场景

1. FragmentFactory的意义?

关于Fragment的使用约定

有Fragment使用经验的人都知道,Fragment必须有有一个空参的构造函数,否则编译时会出现一下错误:

cmd

复制代码

This fragment should provide a default constructor (a public constructor with no arguments)

但即使添加了空参的构造器,如果定义了任何带参数构造器,仍然会被亲切的提醒:

cmd

复制代码

Avoid non-default constructors in fragments: use a default constructor plus Fragment#setArguments(Bundle) instead [ValidFragment]

可见 Android 对于 Framgent携带构造参数唯恐避之不及。

当系统发生 Configuration Change 时(例如横竖屏旋转等)Fragment 会恢复重建,此时系统不知道该选择哪个构造函数,所以系统与开发者约定,统一使用默认的空参构造函数构建,然后通过setArgments设置初始化值。

以往的处理方式:静态工厂

为此,一个常见做法是通过静态方法,避免直接使用带参数的构造函数。

如下,静态方法 getInstance(String str) 中,先空参构造 Fragment,然后通过 setArgments 初始化。

public class MainFragment extends BaseFragment {
   private static final String MY_ARG = "my_arg";
   private String arg = "";
   public static MainFragment getInstance(String str) {
       MainFragment fragment = new MainFragment();
       Bundle bundle = new Bundle();
       bundle.putString(MY_ARG, str);
       fragment.setArguments(bundle);
       return fragment;
   }
   @Override
   public void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       if (getArguments() != null) {
           arg = getArguments().getString(MY_ARG);
       }
   }
}

后续便可以使用此静态方法构建 Fragment 了

MainFragment fragment = MainFragment.getInstance("Hello world!!");

Fragment恢复重建过程中,系统会调用静态方法 Fragment.instantiate(在 onCreateonActivityCreated 之间)

@NonNull
public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
       @Nullable Bundle args) {
   try {
       Class extends Fragment> clazz = FragmentFactory.loadFragmentClass(
               context.getClassLoader(), fname);
       Fragment f = clazz.getConstructor().newInstance();
       if (args != null) {
           args.setClassLoader(f.getClass().getClassLoader());
           f.setArguments(args);
       }
       return f;
   } catch (java.lang.InstantiationException e) {

我们先前通过 setArguments 传递的 bundle(随着 onSaveInstanceState 保存),会被系统传递给 instantiate ,以协助 fragment 的恢复重建。

新的处理方案:FragmentFactory

以上关于 Fragment 空参构造函数的约定,随着 androidx.fragment:fragment-1.1.0-alpha01 的发布成为了历史。

新版本中 Fragment.instantiate已经被@Deprecated,推荐使用FragmentManager.getFragmentFactoryFragmentFactory.instantiate (ClassLoader, String)替代。FragmentFactory 允许开发者按照需要自由定义其构造函数,摆脱了空参构造的束缚。


2. FragmentFactory如何使用?

假设我们的 MainFragment 需要两个参数,那么使用 FragmentFactory 如何构造呢?

定义FragmentFactory

首先,需要定义自己的 FragmentFactory 。主要是重写 instantiate 方法,注意跟以前比,已经不支持传入 Bundle args 作为参数了。即使你想使用 bundle 传参,也推荐在这里手动 setArgument ,而非借助系统的设置。

class MyFragmentFactory extends FragmentFactory {
   private final AnyArg anyArg1;
   private final AnyArg anyArg2;
   public MyFragmentFactory(AnyArg arg1, AnyArg arg2) {
       this.anyArg1 = arg1;
       this.anyArg2 = arg2;
   }
   @NonNull
   @Override
   public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
       Class extends Fragment> clazz = loadFragmentClass(classLoader, className);
       if (clazz == MainFragment.class) {
          return new MainFragment(anyArg1, anyArg2);
       } else {
           return super.instantiate(classLoader, className);
       }
   }
}

有了 FragmentFactory 的加持 Framgent 直接使用构造函数传参即可:

protected MainFragment(AnyArg arg1, AnyArg arg2) {
    this.arg1 = arg1;
    this.arg2 = arg2;
}

设置Factory

接下来需要在 Activity 的onCreate中为 FragmentManager 设置此 Factory

MyFragmentFactory fragmentFactory = new MyFragmentFactory( someObject1,  someObject2);
@Override
public void onCreate(Bundle savedInstanceState) {
    getSupportFragmentManager().setFragmentFactory(fragmentFactory);
    super.onCreate(savedInstanceState);
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
        .replace(
            R.id.fragment_container,
            MainFragment.class);
    if (addToBackStack) {
      fragmentTransaction.addToBackStack(tag);
    }
    fragmentTransaction.commit();
}

后续 FragmentManager 在创建/恢复 fragment 时,会使用此 factory 创建实例。

需要特别注意的是,setFragmentFactory一定要在super.onCreate之前调用,因为在super.onCreate中会进行fragment的重建是需要被使用到。


3. 应用场景:设置 LayoutId

androidx.annotation:annotation-1.1.0-alpha01 起引入了@ContentView 注解用来为Fragment 设置默认布局文件,但时隔不久,androidx.fragment:fragment-1.1.0-alpha05 起,@ContentView 从class注解变为构造函数注解,fragment多了一个带参数的构造函数:支持使用构造函设置LayoutId:

 /**
     * Alternate constructor that can be used to provide a default layout
     * that will be inflated by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
     *
     * @see #Fragment()
     * @see #onCreateView(LayoutInflater, ViewGroup, Bundle)
     */
    @ContentView //以前是用在Class上的注解
    public Fragment(@LayoutRes int contentLayoutId) {
        this();
        mContentLayoutId = contentLayoutId;
    }

Note:Activity自 androidx.activity:activity-1.0.0-alpha06 起也支持通过构造函数设置LayoutId

构造函数中将传入的LayoutId 存于mContentLayoutIdonCreateView 中根据 mConentLayoutId 自动创建 ContentView :

    @Nullable
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
            @Nullable Bundle savedInstanceState) {
        if (mContentLayoutId != 0) {
            return inflater.inflate(mContentLayoutId, container, false);
        }
        return null;
    }

也就是说使用构造函数设置LayoutId 就无需重写 onCreateView 了。

Fragment(@LayoutRes int contentLayoutId) + FragmentFactory

你也许会问这跟 FragmentFactory 有什么关系呢?

因为这里使用了构造函数传递参数,当 ConfigurationChanged 发生时,默认调用无参构造函数进行 fragment 的恢复重建,mContentLayoutId 信息会丢失,onCreateView无法正常创建视图。

因此当使用构造函数设置 LayoutId 时,如果要考虑恢复重建的场景,必须配套设置一个 FragmentFactory。可能是踩坑的人太多了,在 1.1.0 之后的 javadoc 中特别强调了这一点:

You must set a custom FragmentFactory if you want to use a non-default constructor to ensure that your constructor is called when the fragment is re-instantiated.

所以,综合来看,你觉得通过构造函数设置 LayoutId 到底方不方便呢?


4. 应用场景: 依赖注入

FragmentFactory 允许自定义构造参数创建 Fragment, 这在 dagger、koin 等 DI 框架的使用场景中也能发挥更大作用。

以 Koin 中 FragmentFactory 的使用为例 (对 Koin 的基本知识不做介绍):

//定义fragmentModules
private val fragmentModules = module {
    fragment { HomeFragment() }
    fragment { DetailsFragment(get()) } //通过get()获取依赖的参数
}
private val viewModelsModule = module {
    viewModel { DetailsViewModel(get()) }
}
//启动Koin
override fun onCreate() {
    super.onCreate()
    startKoin {
        androidContext(this@App)
        fragmentFactory() // 添加 KoinFragmentFactory
        loadKoinModules(listOf(viewModdules, fragmentModules, ...)) //装在fragmentModules
    }
}

如上,DetailsFrament 参数依赖 DetailsViewModel

KoinFragmentFactory

Koin 使用 KoinFragmentFactory 为其注入这个参数依赖, 其本质就是一个 FragmentFactory

class KoinFragmentFactory : FragmentFactory() {
    override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
        val clazz = Class.forName(className).kotlin
        val instance = getKoin().getOrNull<Fragment>(clazz) //通过 Koin 创建 Fragment
        return instance ?: super.instantiate(classLoader, className)
    }
}

Koin 通过 KoinFragmentFactory 创建 Fragment,构造函数中允许有参数,可以通过 koin 的依赖注入获取

FragmentFactory 需要被设置到 FragmentManager 中使用。 KoinFragmentFactory 也同样。需要在 Activity#onCreateFragment#onCreate 中调用 setupKoinFragmentFactory(), 将其添加到当前 FragmentManager 中

override fun onCreate(savedInstanceState: Bundle?) {
    setupKoinFragmentFactory() // 设置到 FragmentManager
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}
fun FragmentActivity.setupKoinFragmentFactory(scope: Scope? = null) {
    if (scope == null) {
        supportFragmentManager.fragmentFactory = get()
    } else {
        supportFragmentManager.fragmentFactory = KoinFragmentFactory(scope)
    }
}

需要注意,这个调用必须在 super.onCreate 之前完成,因为 super.onCreate 中会进行 fragment 的重建, 此时就需要用到 FragmentFactory 了

Koin 配置完成后,就可以想往常一样将 DetailFagment 添加到 Activity 中了

kotlin

复制代码

supportFragmentManager.beginTransaction()    .replace(R.id.container, DetailsFragment::class.java, null)    .commit()

FragmentTransaction 会自动调用 KoinFragmentFactory#instantiate() 创建DetailsFragment::class.java 对应的 Fragment, 很方便吧?

目录
相关文章
|
安全 Java Kotlin
面试必备:Kotlin 线程同步的 N 种方法
面试必备:Kotlin 线程同步的 N 种方法
418 0
|
Android开发
android基础:Intents 和 intent-filter 的匹配规则
这篇文章详细解释了Android开发中Intent和<intent-filter>的匹配规则,包括Action、Category和Data的解析规则以及如何通过这些规则匹配隐式Intent。
405 1
|
4月前
|
缓存 Android开发 iOS开发
Kotlin跨平台Compose Multiplatform实战指南
Kotlin Multiplatform (KMP) 结合 Compose Multiplatform,助力开发者用一套代码构建跨平台应用(Android、iOS、桌面和 Web)。本文提供实战指南,涵盖环境搭建、项目结构、共享 UI 编写、平台适配、状态管理及资源处理等内容。通过 expect/actual 处理差异,借助官方文档与示例项目学习,减少重复代码,优化多平台开发体验。
897 18
|
10月前
|
数据采集 存储 搜索推荐
爬取网易云音乐热歌榜:从入门到实战
本文介绍如何使用Python爬取网易云音乐热歌榜,包括环境准备、代码解析和实际操作步骤。通过定义榜单ID与名称映射、用户输入、文件夹创建、发起网络请求、正则表达式提取、音乐下载和文件保存等环节,手把手教你实现音乐下载功能。强调遵守网站协议,尊重版权和用户隐私,确保合法合规使用代码。
715 2
|
Android开发
DialogFragment 使用指南:几个小问题的解法
DialogFragment是Android中用于创建弹窗的特殊Fragment,继承自Fragment。使用步骤包括:1. 创建子类,2. 在onCreateView加载布局,3. onViewCreated初始化控件,4. 通过show方法显示。示例代码展示了一个基本的DialogFragment及其布局。此外,文中还解答了三个常见问题:如何设置弹窗宽度为match_parent,如何使弹窗位于屏幕底部,以及如何去除弹窗四周的默认padding。每个问题都提供了相应的解决方案,涉及在onStart中调整窗口参数和设置自定义样式。
1409 2
DialogFragment 使用指南:几个小问题的解法
|
安全 定位技术 API
探讨如何在Flutter中集成支付、地图等第三方服务,以及集成过程中需要注意的问题和最佳实践
【6月更文挑战第11天】本文介绍了在Flutter中集成第三方服务,如支付和地图,以增强应用功能和用户体验。开发者可通过官方或社区插件集成服务,注意服务选择、API调用、错误处理和用户体验。支付集成涉及选择服务、获取API密钥、引入插件、调用API及处理结果。地图集成则包括选择地图服务、获取API密钥、初始化地图组件和添加交互功能。集成时要选择稳定插件、仔细阅读文档,处理错误,优化性能并遵循安全规范。随着Flutter生态发展,更多优质服务将可供选择。
266 2
|
XML Android开发 数据格式
11. 【Android教程】帧布局 FrameLayout
11. 【Android教程】帧布局 FrameLayout
300 1
RecyclerView GridView模式同一行,使其高度平齐,自动适应高度最大item
RecyclerView GridView模式同一行,使其高度平齐,自动适应高度最大item
564 0
|
Android开发
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
Android Gradle开发—脚本实现自动打包后复制一份APK文件,并修改APK名称,到指定目录作备份
704 0
|
Java 开发工具 Android开发
Android 11 SystemUI(状态/导航栏)-图标按键的深浅色
Android 11 SystemUI(状态/导航栏)-图标按键的深浅色
1074 0
Android 11 SystemUI(状态/导航栏)-图标按键的深浅色