Fragment 注意事项

本文深入探讨了FragmentManager在Android中的管理机制,特别是在配置更改或应用重建过程中Fragment的状态保存和恢复问题。通过对具体代码案例的分析,揭示了导致Context空指针异常的原因,并提供了有效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近看 ViewModel 实现原理,由于 FragmentManagerViewModel 中 Map 的 key 是 Fragment 名称,所以有点懵,那 Activity 中加入两个同名实例的 Fragment 岂不是乱了,后来发现系统不允许向 FragmentManager 中添加同名的实例(和正文无关)

最近在使用 Fragment 时遇到了一个 Context 空指针崩溃,研究发现自己对于 FragmentManager 的保存恢复机制了解不够,特别记录一下

// 问题代码如下
class MainActivity : AppCompatActivity() {

    lateinit var mAdapter: FragmentAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mAdapter = FragmentAdapter(supportFragmentManager)
        viewPager.adapter = mAdapter
        btn.setOnClickListener {
            mAdapter.fragmentOne.showToast()
        }
    }
}

class FragmentAdapter(fragmentManager: FragmentManager): FragmentPagerAdapter(fragmentManager) {

    val fragmentOne = FragmentOne()
    val fragmentTwo = FragmentTwo()

    override fun getCount(): Int = 2

    override fun getItem(position: Int): Fragment {
        when (position) {
            0 -> fragmentOne
            else -> fragmentTwo
        }
    }
}

class FragmentOne: Fragment() {
    lateinit var mRootView: View
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, saveInstanceState: Bundle?): View {
        mRootView = inflater.inflate(R.layout.ebb, null)
        return mRootView
    }

    fun showToast() {
        Toast(context).show() // 配置改变或者应用销毁重建会存在Context空指针情况
    }
}

崩溃复现路径:在开发者模式里将”不保留活动“设置为开状态。打开 App 后,按 Home 键,再次打开 App,这时点击 Button 会产生空指针异常

原因 :按 Home 键后,再次启动 App 时,Fragment 重建,此时显示的 Fragment 和 MyFragmentAdapter 里持有的 Fragment 已经不是同一个了,当前显示的 FragmentA 是 restoreSaveState 重新恢复创建的,而 MyFragmentAdapter 是直接实例化构造函数创建的,所以对应的 mHost 也是空(mHost 是在生命周期的 init 阶段赋值的)

源代码分析: 按 Home 键后,MainActivity 的 onSaveInstanceState 方法被调用,最终 Fragment 的 onSaveInstanceState 也会被调用,FragmentActivity 方法如下

我们再看下 FragmentAdapter 父类 FragmentPagerAdapter 的一段源码,其中 Fragment 是通过 FragmentManager#findFragmentByTag 获取的:

mFragmentManager.findFragmentByTag 是通过 restore 数据也是重新实例化了 Fragment,下面是 FragmentManagerImpl的restoreSaveState 方法(恢复 retained 和 saveInstance 的Fragment),其在 FragmentActivity 的 onCreate 时被调用,fms.mActive 中保存着需要恢复的Fragment 信息(比如,FragmentManager 中维护两个 Fragment,进程被回收或者配置改变时,fms.mActive 都会保存两个 Fragment 信息),但是只有在配置改变且 Fragment 设置了 retainState 时,可以从 mNonConfig(FragmentManagerViewModel 中获取到之前保存的Fragment 实例)

截止到现在,已经存在两个 Fragment 的实例了,一个因 MyFragmentAdapter 实例化时创建的,一个是 FragmentManager 通过 findFragmentByTag 创建的

总结:Fragment 的获取一定要通过 FragmentManager,可以用 findFragmentWithTag 或者getFragments 来得到特定或者所有 Fragment。如果没有获取到 Fragment 实例再去创建。如果是通过 ViewPager 来间接控制 Fragment 更要注意,传给 FragmentPagerAdapter 的 Fragment 实例一定要和 FragmentManager 中的一致,如果 FragmentManager 中没有再去创建

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

little-sparrow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值