作者:Elye
原文链接:medium.com/mobile-app-…
关于Android开发者来说,深入了解Fragment的原理是重要的。可是,Fragment是一个杂乱的组件,大部分人运用它都会犯一些错误。
在Fragment上呈现的bug有时分非常难debug,由于Fragment有非常杂乱的生命周期,不是总能复现场景。
不过,一些问题能够在代码编写阶段简略地防止。下面是7个问题:
1. 在创立Fragment时,没有查看savedStateInstance
一般咱们运用如下代码,在Activity(或许Fragment)的onCreate中显现Fragment界面
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.replace(R.id.container, NewFragment())
.commit()
}
为什么上面的代码欠好
上面的代码有一个问题。当你的activity是被体系杀死并康复时,一个重复的新Fragment将被创立,即康复的Fragment和新创立的。
正确的办法
咱们应该运用savedInstanceState == null来判别。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, NewFragment())
.commit()
}
}
如果之前的Fragment被康复,它将防止创立和履行新的Fragment。如果你想防止康复Fragment,Manually override Fragments Auto Restoration有一些技巧(虽然不主张用于专业应用程序)。
2. 在onCreateView创立Fragment具有的目标
有时分,咱们需要保证数据目标在Fragment的生命周期中存在。咱们以为咱们能够在onCreateView中创立它,由于这个办法只在Fragment创立时或许从体系杀死状况康复时履行一次。
private var presenter: MyPresenter? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
presenter = MyPresenter()
return inflater.inflate(R.layout.frag_layout, container, false)
}
为什么上面代码欠好
但,上面的说法是有问题的。当Fragment是被另一个Fragment运用replace替换时,这Fragment没有被杀死。一同数据目标仍然在Fragment里面。当Fragment是被康复(例如另一个Fragment被pop out),这onCreateView将再履行一次。因而数据目标(这里是presenter)将被再次创立。一切你的数据将被重置。
上图中的onCreateView能够在同一个Fragment实例中被反复调用。
不算好的办法
咱们能够在创立之前加一个非null判别。
private var presenter: MyPresenter? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
if (presenter != null) presenter = MyPresenter()
return inflater.inflate(R.layout.frag_layout, container, false)
}
这个虽然能解决上面的问题,但不算一个好的办法。
更好的办法
咱们应该在onCreate中创立Fragment具有的数据目标。
private var presenter: MyPresenter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
presenter = MyPresenter()
}
经过这种办法,数据目标将只在每次创立Fragment时被创立一次。当咱们弹出顶层Fragment并从头显现Fragment视图时(即调用onCreateView),它将不会被从头创立。
3. 在 onCreateView 中履行状况康复
我知道在onCreateView中供给了savedInstanceState。因而咱们以为咱们能够在这里存储数据。
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
if (savedInstanceState != null) {
// Restore your stuff here
}
// ... some codes creating view ...
}
为什么这个欠好
上面的办法或许形成一个古怪的问题,你存储在一些Fragment(非顶部可见Fragment)的数据会丢失。呈现场景有:
●你在堆中有超过一个Fragment(运用replace替代add)
●你把你的应用放到后台并康复两次或屡次
● 你的Fragment被destroy(例如被体系杀死)并康复
更多关于这个问题的细节能够看Bug that will only surface when you background your App twice
更好的办法
就像上面的示例2相同,咱们应该在onCreate办法中履行状况康复。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
// Restore your stuff here
}
}
这种办法能够保证你的Fragment状况总是被康复,不管你的视图是否被创立(即使是仓库中不行见的Fragment,也将康复其数据)。
4. 在Activity中保存了Fragment的引证
有时分由于一些原因,咱们想要在Activity(或许父Fragment)中获取Fragment的目标。经过下面的办法,咱们能够很简略地获取Fragment的引证。
private var myFragment: MyFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
myFragment = NewFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.container, myFragment)
.commit()
}
}
private fun anotherFunction() {
myFragemnt?.doSomething()
}
为什么上述办法不对
Fragment有自己的生命周期。它被体系杀死并康复。这个意味着引证的原始的Fragment不再存在(虽然myFragemnt不会为null,可是咱们履行doSomething时或许由于Fragment被杀死而出错)。
如果咱们在Activity中坚持Fragment的引证,咱们需要保证能不断更新对正确Fragment的引证,如果丢失了,会很棘手。
更好的办法
经过Tag获取你的Fragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, NewFragment(), FragmentTag)
.commit()
}
}
private fun anotherFunction() {
(supportFragmentManager.findFragmentByTag(FragmentTag) as?
NewFragment)?.doSomething()
}
当你需要访问它时,你总是能经过Fragment Transaction找到它。尽管这是一种可行的办法,咱们还是应该尽量减少Fragment和 Activity(或父Fragment)之间的这种沟通。
5. 在Fragment的onSavedStateInstance办法中访问View
有时咱们想要在Fragment被体系杀死时保存一些view的信息。
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
binding?.myView?.let {
// doSomething with the data, maybe to save it?
}
}
为什么上面的办法不对
考虑这个场景
●如果Fragment没有被杀死,而是被另一个Fragment给replace了,这个Fragment的onViewDestroy()办法将被调用 (一般状况下,咱们在这里设置binding = null)
●由于Fragment仍然存在,onSaveInstanceState不会被调用。可是Fragment的view现已不存在了
●然后,这个时分Fragment被体系杀死,onSaveInstanceState被调用。可是,由于binding是null,该代码不会被履行。
正确的办法
不管你想从view中访问什么,都应该在onSavedStateInstance之前完结,并存储在其他当地。最好是一切这些都在presenter或View Model中完结。
6. 更喜爱运用add而不是replace
咱们有replace和add去操作Fragment。有时咱们只是想知道咱们应该运用哪一个办法。或许咱们应该运用add,由于它听上去更合乎逻辑。
supportFragmentManager.beginTransaction()
.add(R.id.container, myFragment)
.commit()
运用add的优点是,保证底部Fragment的view不会被毁掉,并且当顶部的Fragment弹出时不需要从头创立view。在下面一些应用场景中,add是有用的。
● 当下一个Fragment是add到一个Fragment的顶部时,它们两个都是可见的,并且相互叠加。如果你在顶部Fragment上有一个半透明的view,你能够看到底部的Fragment
●当你添加的Fragment是花费长期加载的时(例如加载Webview),你想要防止其他Fragment弹出时从头加载它。这时你就运用add替代replace。
为什么上面的办法欠好
上面说到的两种场景并不常见。因而add应该被约束运用,由于它有如下缺陷。
●运用add将坚持底部的Fragment可见,它花费了更多不必要的内存。
●添加了一个以上可见的Fragment,当它们被一同康复时,有时或许会导致状况康复问题。The Crazy Android Fragment Bug I’ve Investigated是2个Fragment一同加载并运用的状况,它会导致杂乱和混乱的问题。
首选办法
运用replace替代add,即使是第一个Fragment提交时。由于关于第一个Fragment,replace和add没有不同,不如直接运用replace,使之成为默许的普遍做法。
7. 运用simpleName作为Fragment的Tag
有时咱们想对 Fragment进行符号,以便以后检索。咱们能够运用当时class的simpleName来符号它,由于它是方便的。
supportFragmentManager.beginTransaction()
.replace(
R.id.container,
fragment,
fragment.javaClass.simpleName)
.commit()
为什么这个欠好
在Android中,咱们运用Proguard或DexGuard来混淆类的称号。而在这个过程中,混淆后的简略称号或许会与其他类的称号发生冲突,如The danger of using getSimpleName() as TAG for Fragment所述。它或许很少见,但一旦发生,它或许会让你惊慌失措。
首选办法
考虑运用一个常数或标准称号作为tag。这将更好地保证它是仅有的。
supportFragmentManager.beginTransaction()
.replace(
R.id.container,
fragment,
fragment.javaClass.canonicalName)
.commit()