最近看 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 中没有再去创建