Android字体大小变更致Activity重建,Fragment上下文及ViewModel异常问题
问题场景
当Activity被重建(比如修改系统字体大小触发重建)并重新初始化ViewPager2时,调用Fragment的函数会出现requireContext()、requireView()为空,且ViewModel未初始化的情况。
相关代码
Activity代码
var pagerAdapter: NewPagerAdapter? = null fun initView(){ val vpContent = findViewById<ViewPager2>(R.id.vp_content) val fragmentList = ArrayList<ParentFragment>() fragmentList.add(FristFragment()) fragmentList.add(SecondFragment()) fragmentList.add(ThirdFragment()) pagerAdapter = NewPagerAdapter(fragmentList, supportFragmentManager, lifecycle) vpContent.adapter = pagerAdapter val btnRefresh = findViewById<Button>(R.id.btn_scm_refresh) btnRefresh.setOnClickListener { (pagerAdapter)!!.allRefreshPage() } }
Adapter代码
override fun getItemCount(): Int { return fragmentList.size } override fun createFragment(position: Int): Fragment { return fragmentList[position] } fun allRefreshPage(context: Context){ for(i in 0 until fragmentList.size){ fragmentList[i].refreshData() } }
ParentFragment代码
abstract class ParentFragment : Fragment() { abstract fun refreshData() }
ChildFragment代码
private lateinit var viewModel: NewViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this, ViewModelFactory())[NewViewModel::class.java] } override fun refreshData() { if(context == null || view == null) { Log.w(TAG, "refreshData: have not context or view") return } viewModel.initAppList(requireContext(), requireView()) }
问题与解答
1. 该问题产生的原因是什么?
- Fragment实例管理错误:Activity重建时,你手动创建了新的Fragment实例传入Adapter,但系统已经自动恢复了之前的Fragment实例,此时Adapter中保存的旧实例已与Activity、ViewPager2失去关联,处于detached状态,自然没有可用的Context和View。
- 生命周期与初始化时机不匹配:旧Fragment实例的
onCreate不会在重建时再次触发,导致ViewModel未初始化;即使是新实例,调用refreshData时可能还未执行到onCreateView或onViewCreated,View尚未创建,Context也未完成绑定。 - ViewPager2的Fragment回收机制:ViewPager2默认会销毁非当前页的Fragment(根据
offscreenPageLimit配置),重建后如果Fragment还未被初始化或attach到Activity,调用其方法就会出现空值。
2. 如何确保不会出现上述空值情况?
- 让系统自动管理Fragment实例:修改Adapter,去掉外部传入的
fragmentList,改为在createFragment中动态创建Fragment,系统会自动处理重建时的实例恢复:class NewPagerAdapter(fm: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) { override fun getItemCount(): Int = 3 override fun createFragment(position: Int): Fragment { return when(position) { 0 -> FristFragment() 1 -> SecondFragment() else -> ThirdFragment() } } fun allRefreshPage() { // 通过FragmentManager获取已attach的有效Fragment实例 val fragments = fragmentManager.fragments.filterIsInstance<ParentFragment>() fragments.forEach { it.refreshData() } } } - 增加Fragment状态校验:在Fragment的
refreshData方法中,先判断自身是否已attach到Activity、View是否已创建:override fun refreshData() { if (!isAdded || view == null) { Log.w(TAG, "refreshData: fragment not attached or view not created") return } viewModel.initAppList(requireContext(), requireView()) } - 调整ViewModel绑定范围(可选):如果需要ViewModel在Activity重建后保留数据,或确保其提前初始化,可以将ViewModel绑定到Activity的ViewModelStore:
// 在ChildFragment中修改ViewModel初始化代码 viewModel = ViewModelProvider(requireActivity(), ViewModelFactory())[NewViewModel::class.java] - 延迟调用时机:在Activity的
onResume方法中触发刷新,或通过LiveData通知Fragment主动执行刷新,避免在Activity重建未完成时直接调用Fragment方法。
3. 从Activity调用Fragment的函数还有哪些其他实现方式?
- LiveData事件通知:在Activity的ViewModel中定义LiveData,Fragment观察该LiveData,Activity通过发送事件触发Fragment执行方法:
// Activity的ViewModel class ActivityViewModel : ViewModel() { private val refreshTrigger = MutableLiveData<Event<Unit>>() fun triggerRefresh() = refreshTrigger.postValue(Event(Unit)) fun getRefreshTrigger() = refreshTrigger } // ChildFragment中观察事件 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val activityViewModel = ViewModelProvider(requireActivity())[ActivityViewModel::class.java] activityViewModel.getRefreshTrigger().observe(viewLifecycleOwner) { event -> event.contentIfNotHandled?.let { refreshData() } } } // Activity中触发刷新 activityViewModel.triggerRefresh() - FragmentManager查找实例:直接通过FragmentManager获取已attach的Fragment实例并调用方法:
注:ViewPager2的Fragment标签格式通常为// Activity中调用 val targetFragment = supportFragmentManager.findFragmentByTag("f0") as? FristFragment targetFragment?.refreshData()f${position},可通过日志或源码确认具体格式 - 接口回调(弱引用避免泄漏):定义回调接口,Fragment实现接口,Activity持有接口的弱引用并调用方法:
// 定义回调接口 interface RefreshCallback { fun onRefresh() } // ChildFragment中绑定回调 override fun onAttach(context: Context) { super.onAttach(context) val callback = WeakReference<RefreshCallback>(context as? RefreshCallback) } // Activity实现RefreshCallback接口,调用对应Fragment的回调方法
内容的提问来源于stack exchange,提问作者Jun.Grammer
相关产品推荐
相关产品推荐

