Mengapa postDelayed berbahaya

Seringkali, karena kekhasan sistem android dan sdk, kita perlu menunggu hingga bagian tertentu dari sistem dikonfigurasi atau beberapa peristiwa yang kita perlukan terjadi. Ini sering kali merupakan penopang, tetapi terkadang Anda tidak dapat melakukannya tanpanya, terutama saat menghadapi tenggat waktu. Oleh karena itu, banyak proyek telah menggunakan postDelayed untuk ini. Di bawah pemotongan, kami akan mempertimbangkan mengapa dia sangat berbahaya dan apa yang harus dilakukan.



Masalah



Pertama, mari kita lihat bagaimana postDelayed () biasanya digunakan:



override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.postDelayed({
            Log.d("test", "postDelayed")
            // do action
        }, 100)
}


Ini terlihat bagus, tapi mari kita lihat lebih dekat kode ini:



1) Ini adalah tindakan ditangguhkan yang akan kami tunggu beberapa saat untuk menyelesaikannya. Mengetahui seberapa dinamis pengguna dapat melakukan transisi antar layar, tindakan ini harus dibatalkan saat mengubah fragmen. Namun, ini tidak terjadi di sini, dan tindakan kita akan dijalankan meskipun fragmen saat ini dihancurkan.



Mudah untuk diperiksa. Kami membuat dua fragmen, ketika beralih ke yang kedua, kami menjalankan postDelayed dengan waktu yang lama, misalnya 5000 ms. Kami segera kembali. Dan setelah beberapa saat kami melihat di log bahwa tindakan tersebut belum dibatalkan.



2) Yang kedua "mengikuti" dari yang pertama. Jika dalam runnable ini kita meneruskan referensi ke properti fragmen kita, kebocoran memori akan terjadi, karena referensi ke runnable akan bertahan lebih lama daripada fragmen itu sendiri.



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



?



  1. view — doOnLayout doOnNextLayout
  2. , - (Presenter/ViewModel - ). .
  3. .


, 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, Anda bisa menghubungkan androidx.lifecycle: lifecycle-runtime-ktx: 2.2.0-alpha01 atau lebih tinggi dan kita sudah memiliki cakupan yang sudah jadi:



viewLifecycleOwner.lifecycleScope


fun Fragment.delayActionSafe(delayMillis: Long, action: () -> Unit): Job? {
    view ?: return null
    return viewLifecycleOwner.lifecycleScope.launch {
        delay(delayMillis)
        action()
    }
}


RX



Di RX, kelas Disposable bertanggung jawab untuk membatalkan langganan, tetapi di RX tidak ada konkurensi Terstruktur, tidak seperti coroutine. Karena itu, Anda harus meresepkan semuanya sendiri. Biasanya terlihat seperti ini:



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()
    }
}


Kami juga membatalkan semua tugas di fragmen dasar dengan cara yang sama:



class BaseFragment(@LayoutRes layoutRes: Int) : Fragment(layoutRes),
    DisposableHolder by DisposableHolderImpl() {

    override fun onDestroyView() {
        super.onDestroyView()
        dispose()
    }

    override fun onDestroy() {
        super.onDestroy()
        dispose()
    }
}


Dan ekstensi itu sendiri:



fun BaseFragment.delayActionSafe(delayMillis: Long, block: () -> Unit): Disposable? {
    view ?: return null
    return Completable.timer(delayMillis, TimeUnit.MILLISECONDS).subscribe {
        block()
    }.also {
        addDisposable(it)
    }
}


Dalam pengawasan



Saat menggunakan tindakan yang ditangguhkan, kita tidak boleh lupa bahwa ini sudah merupakan eksekusi asinkron, dan karenanya memerlukan pembatalan, jika tidak, kebocoran memori, kerusakan, dan berbagai hal tak terduga lainnya mulai terjadi.




All Articles