Aufgrund der Besonderheiten des Android-Systems und des SDK müssen wir häufig warten, bis ein bestimmter Teil des Systems konfiguriert ist oder ein Ereignis eintritt, das wir benötigen. Dies ist oft eine Krücke, aber manchmal kann man nicht darauf verzichten, besonders angesichts der Fristen. Daher haben viele Projekte postDelayed dafür verwendet. Unter dem Schnitt werden wir überlegen, warum er so gefährlich ist und was man dagegen tun kann.
Problem
Schauen wir uns zunächst an, wie häufig postDelayed () verwendet wird:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.postDelayed({
Log.d("test", "postDelayed")
// do action
}, 100)
}
Es sieht gut aus, aber schauen wir uns diesen Code genauer an:
1) Dies ist eine verzögerte Aktion, auf deren Abschluss wir einige Zeit warten werden. Wenn Sie wissen, wie dynamisch der Benutzer Übergänge zwischen Bildschirmen ausführen kann, sollte diese Aktion beim Ändern eines Fragments abgebrochen werden. Dies geschieht hier jedoch nicht und unsere Aktion wird ausgeführt, selbst wenn das aktuelle Fragment zerstört wird.
Es ist leicht zu überprüfen. Wir erstellen zwei Fragmente, wenn wir zum zweiten wechseln, führen wir postDelayed mit einer langen Zeit aus, zum Beispiel 5000 ms. Wir gehen sofort zurück. Und nach einer Weile sehen wir in den Protokollen, dass die Aktion nicht abgebrochen wurde.
2) Das zweite "folgt" aus dem ersten. Wenn wir in dieser ausführbaren Datei einen Verweis auf die Eigenschaft unseres Fragments übergeben, tritt ein Speicherverlust auf, da der Verweis auf die ausführbare Datei länger als das Fragment selbst lebt.
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_Reeveskönnen Sie androidx.lifecycle: lifecycle-runtime-ktx: 2.2.0-alpha01 oder höher verbinden und wir haben bereits einen vorgefertigten Bereich:
viewLifecycleOwner.lifecycleScope
fun Fragment.delayActionSafe(delayMillis: Long, action: () -> Unit): Job? {
view ?: return null
return viewLifecycleOwner.lifecycleScope.launch {
delay(delayMillis)
action()
}
}
RX
In RX ist die Disposable-Klasse für das Kündigen von Abonnements verantwortlich, in RX gibt es im Gegensatz zu Coroutine keine strukturierte Parallelität. Aus diesem Grund müssen Sie alles selbst verschreiben. Es sieht normalerweise so aus:
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()
}
}
Wir brechen auch alle Aufgaben im Basisfragment auf die gleiche Weise ab:
class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes),
DisposableHolder by DisposableHolderImpl() {
override fun onDestroyView() {
super.onDestroyView()
dispose()
}
override fun onDestroy() {
super.onDestroy()
dispose()
}
}
Und die Erweiterung selbst:
fun BaseFragment.delayActionSafe(delayMillis: Long, block: () -> Unit): Disposable? {
view ?: return null
return Completable.timer(delayMillis, TimeUnit.MILLISECONDS).subscribe {
block()
}.also {
addDisposable(it)
}
}
In Gewahrsam
Wenn Sie verzögerte Aktionen verwenden, dürfen wir nicht vergessen, dass dies bereits eine asynchrone Ausführung ist und dementsprechend abgebrochen werden muss, da sonst Speicherlecks, Abstürze und verschiedene andere unerwartete Ereignisse auftreten.