لماذا يعد postDelayed خطيرًا

غالبًا ، نظرًا لخصائص نظام 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 مللي ثانية. نعود على الفور. وبعد فترة نرى في السجلات أن الإجراء لم يتم إلغاؤه.



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



?



  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، يمكنك توصيل 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



في RX ، تكون فئة Disposable مسؤولة عن إلغاء الاشتراكات ، ولكن في RX لا يوجد التزامن منظم ، على عكس coroutine. لهذا السبب ، عليك أن تصفها بنفسك. عادة ما يبدو كالتالي:



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


قيد التوقيف



عند استخدام أي إجراءات مؤجلة ، يجب ألا ننسى أن هذا يعد بالفعل تنفيذًا غير متزامن ، وبالتالي فإنه يتطلب الإلغاء ، وإلا فإن حدوث تسرب للذاكرة ، وتعطل ، والعديد من الأشياء الأخرى غير المتوقعة.




All Articles