我正在参加「掘金·启航计划」
1.前言
最近发现了一个离谱的问题,当我打开某软件并长时间锁屏后重新打开并切换Tab时发现竟然出现了Fragment的重叠问题,回味一会发现其实是Activity销毁重建后Fragment重新加载所导致的问题,本文章就如何解决上述问题提供一点小小的思路。
2.造成重叠的原因
当我们的应用长期处于后台时,系统回收了主页Activity,当我们重新解锁后,应用处于前台,主页Activity被销毁并重新创建了,并且在销毁之前执行了onSaveInstanceState(Bundle outState)这个方法,该方法会保存当前的视图层,之前被实例化过的 Fragment 依然会出现在 Activity 中,此时的 FragmentTransaction 中的相当于又再次 add 了 fragment 进去的,hide()和show()方法对之前保存的fragment已经失效了,这个时候点击底部导航的话会重新去切换fragment,于是发生了重叠的问题。
3.如何复现
复现方法很简单,进入手机设置 -> 开发者选项 -> 应用 -> 开启不保留活动(离开后即销毁每个活动) 即可达成复现条件:
我们现在模拟主页Acitivty,存在AFramgnet和BFragment。
class FragmentActivity : AppCompatActivity() {
//用的是DataBinding
private lateinit var binding: ActivityFragmentBinding
private var aFragment: AFragment? = null
private var bFragment: BFragment? = null
private var currentId = R.id.tv_a //当前id
....
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_fragment)
binding.apply {
tvA.setOnClickListener(onClickListener)
tvB.setOnClickListener(onClickListener)
}
//初始化AFragment
changeFragment(currentId)
}
点击事件:
private var onClickListener = OnClickListener {
changeFragment(it.id)
}
在Activity创建时将通过changeFragment(id: Int)方法将AFragment创建并添加到视图中去。
//切换Fragment
private fun changeFragment(id: Int) {
supportFragmentManager.beginTransaction().apply {
hideFragment(this)
when (id) {
R.id.tv_a -> {
aFragment?.let {
show(it)
} ?: run {
aFragment = AFragment()
add(R.id.fl_container, aFragment!!)
}
binding.tvA.isSelected = true
}
R.id.tv_b -> {
bFragment?.let {
show(it)
} ?: run {
bFragment = BFragment()
add(R.id.fl_container, bFragment!!)
}
binding.tvB.isSelected = true
}
}
}.commitNowAllowingStateLoss()
}
切换之前先通过hideFragment(transaction: FragmentTransaction)隐藏所有的Fragment:
private fun hideFragment(transaction: FragmentTransaction) {
binding.tvA.isSelected = false
binding.tvB.isSelected = false
aFragment?.let {
transaction.hide(it)
}
bFragment?.let {
transaction.hide(it)
}
}
现在我们将项目跑起来,并且在开发者模式中打开不保留活动,进入后切换B fragment后切入后台再切回前台就可以复现重叠的情况了:
4.如何解决
我们在使用FragmentTransaction的add方法时需要一并存入tag,通过FragmentManager的findFragmentByTag来初始化Fragment,重写onSaveInstanceState在Activity销毁时存入Tag,再 onCreate中通过Tag查找存入id再次选中Fragment。
更改后的onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_fragment)
binding.apply {
tvA.setOnClickListener(onClickListener)
tvB.setOnClickListener(onClickListener)
}
aFragment = AFragment()
//通过savedInstanceState来获取销毁时选中的id
savedInstanceState?.let { bundle ->
bundle.getString(TAG)?.let {
currentId = getCurrentId(it)
}
}
changeFragmentSafe(currentId)
}
更改后的切换Fragment方法changeFragmentSafe(id: Int) :
private fun changeFragmentSafe(id: Int) {
currentId = id
//获取Fragment
val fragment = initFragment(id)
supportFragmentManager.beginTransaction().apply {
hideFragment(this)
//add时加入tag
fragment?.let {
if (!it.isAdded) {
add(R.id.fl_container, it, getFragmentTag(id))
}
show(it)
}
findViewById<TextView>(id).isSelected = true
}.commitNowAllowingStateLoss()
}
初始化:
private fun initFragment(id: Int): Fragment? {
//通过id转为tag来找对应的Fragment
var fragment = supportFragmentManager.findFragmentByTag(getFragmentTag(id))
when (id) {
R.id.tv_a -> {
if (fragment == null) {
fragment = AFragment.newInstance()
}
aFragment = fragment as AFragment
}
R.id.tv_b -> {
if (fragment == null) {
fragment = BFragment.newInstance()
}
bFragment = fragment as BFragment
}
}
return fragment
}
这里用了个比较繁琐的方法来转换当前id和存入的tag:
//tag 转 id
private fun getCurrentId(tag: String): Int {
return when (tag) {
"tv_a" -> {
R.id.tv_a
}
"tv_b" -> {
R.id.tv_b
}
else -> {
R.id.tv_a
}
}
}
//id 转 tag
private fun getFragmentTag(id: Int): String {
return when (id) {
R.id.tv_a -> {
"tv_a"
}
R.id.tv_b -> {
"tv_b"
}
else -> {
"tv_a"
}
}
}
通过以上的方法,当Activity销毁重建后,会先去从FragmentManager中使用tag获取对应的Fragment对象,如果没有再去重新创建Fragment实例,这样就解决了Fragment的重叠问题:
5.结尾
以上就是关于Activity销毁重建后Fragment重新加载的分析和解决方法,其实除了更改开发者选项来复现之外还可以通过旋转屏幕、使用AndroidStudio工具 杀掉应用程序,模拟内存不足回收或者转跳到其他Activity、打开多任务窗口、使用Home回到主屏幕再返回都可能复现,且上述的解决方案这只是其中的一种方式,如有不足请在评论区或私信指出,如果你们有更多的方法也欢迎在论区或私信留言捏❤️❤️