通常,由于android系统和sdk的特殊性,我们需要等到系统的特定部分配置完毕或发生我们需要的某些事件。这通常是一个拐杖,但有时您不能没有它,尤其是在截止日期前。因此,许多项目为此使用了postDelayed。在削减计划下,我们将考虑他为何如此危险以及如何处理。
问题
首先,让我们看看通常如何使用postDelayed():
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.postDelayed({
Log.d("test", "postDelayed")
// do action
}, 100)
}
看起来不错,但是让我们仔细看一下这段代码:
1)这是一个延迟的操作,我们将等待一段时间才能完成。知道用户可以在屏幕之间进行切换的动态程度,因此在更改片段时应取消此操作。但是,这不会在这里发生,即使当前片段被破坏,我们的动作也将执行。
很容易检查。我们创建两个片段,当切换到第二个片段时,我们运行postDelayed的时间很长,例如5000 ms。我们马上回去。一段时间后,我们在日志中看到该操作尚未取消。
2)从第一个开始的第二个“跟随”。如果在此可运行对象中传递对片段属性的引用,则会发生内存泄漏,因为对可运行对象的引用将比片段本身寿命更长。
3) :
, view onDestroyView
synthitec - java.lang.NullPointerException
, _$_clearFindViewByIdCache
, findViewById
null
viewBinding - java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null
?
- view — doOnLayout doOnNextLayout
- , - (Presenter/ViewModel - ). .
- .
, view window.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Runnable {
// do action
}.let { runnable ->
view.postDelayed(runnable, 100)
view.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View) {}
override fun onViewDetachedFromWindow(view: View) {
view.removeOnAttachStateChangeListener(this)
view.removeCallbacks(runnable)
}
})
}
}
doOnDetach , view window, onViewCreated. .
View.kt:
inline fun View.doOnDetach(crossinline action: (view: View) -> Unit) {
if (!ViewCompat.isAttachedToWindow(this)) { //
action(this) //
} else {
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View) {}
override fun onViewDetachedFromWindow(view: View) {
removeOnAttachStateChangeListener(this)
action(view)
}
})
}
}
extension:
fun View.postDelayedSafe(delayMillis: Long, block: () -> Unit) {
val runnable = Runnable { block() }
postDelayed(runnable, delayMillis)
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(view: View) {}
override fun onViewDetachedFromWindow(view: View) {
removeOnAttachStateChangeListener(this)
view.removeCallbacks(runnable)
}
})
}
. . , . Native Android 2 — Rx Coroutines.
.
, 100% . //.
Coroutines
, di . :
class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes), CoroutineScope by MainScope() {
override fun onDestroyView() {
super.onDestroyView()
coroutineContext[Job]?.cancelChildren()
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
onDestroyView, scope, View Fragment. Fragment .
onDestroy scope, .
.
postDelayed:
fun BaseFragment.delayActionSafe(delayMillis: Long, action: () -> Unit): Job? {
view ?: return null
return launch {
delay(delayMillis)
action()
}
}
, , view , null. . view, .
Keanu_Reeves,您可以连接androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01或更高版本,我们已经有一个现成的范围:
viewLifecycleOwner.lifecycleScope
fun Fragment.delayActionSafe(delayMillis: Long, action: () -> Unit): Job? {
view ?: return null
return viewLifecycleOwner.lifecycleScope.launch {
delay(delayMillis)
action()
}
}
接收
在RX中,Disposable类负责取消订阅,但是在RX中,与协程不同,没有结构化并发。因此,您必须自己开处方。通常看起来像这样:
interface DisposableHolder {
fun dispose()
fun addDisposable(disposable: Disposable)
}
class DisposableHolderImpl : DisposableHolder {
private val compositeDisposable = CompositeDisposable()
override fun addDisposable(disposable: Disposable) {
compositeDisposable.add(disposable)
}
override fun dispose() {
compositeDisposable.clear()
}
}
我们还以相同的方式取消基本片段中的所有任务:
class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes),
DisposableHolder by DisposableHolderImpl() {
override fun onDestroyView() {
super.onDestroyView()
dispose()
}
override fun onDestroy() {
super.onDestroy()
dispose()
}
}
扩展本身:
fun BaseFragment.delayActionSafe(delayMillis: Long, block: () -> Unit): Disposable? {
view ?: return null
return Completable.timer(delayMillis, TimeUnit.MILLISECONDS).subscribe {
block()
}.also {
addDisposable(it)
}
}
在押
当使用任何延迟的动作时,我们一定不要忘记这已经是异步执行,因此它需要取消,否则会发生内存泄漏,崩溃和各种其他意外情况。