Bienvenido, me llamo Luis y hoy les traigo otro artículo.
Índice
Evite accidentes evitables
Desde sus inicios, Android ha proporcionado una Handler
API. Como dice la documentación, le permite entregar mensajes desde una cola en el hilo del Looper
.
Handler().postDelayed({ doSomething() }, delay)
Esta API es útil y, sin embargo, muy astuta. No se deje engañar al usarlo en una vista. Para comprender dónde reside el peligro, debemos profundizar en la View
clase.
Controladores de vistas
View
en Android aprovecha la Handler
API. Actúa como un paso a través con un interior Handler
y expone ambas funciones post()
y postDelayed()
.
Eche un vistazo más de cerca a su implementación (en el momento en que estoy escribiendo el artículo):
/** * <p>Causes the Runnable to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the user interface thread.</p> * * @param action The Runnable that will be executed. * @param delayMillis The delay (in milliseconds) until the Runnable * will be executed. * * @return true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. Note that a * result of true does not mean the Runnable will be processed -- * if the looper is quit before the delivery time of the message * occurs then the message will be dropped. * * @see #post * @see #removeCallbacks */ public boolean postDelayed(Runnable action, long delayMillis) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.postDelayed(action, delayMillis); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().postDelayed(action, delayMillis); return true; }
Quiero que prestes atención al comentario de Javadoc. La removeCallbacks
línea sugiere algo sobre la eliminación de la devolución de llamada.
Veámoslo en acción:
myView.postDelayed({ myView.animateSomething() }, delay)
En este ejemplo, quiero animar mi vista cuando expira un retraso determinado.
Una vez que expiró el retraso, ¿qué me dice que mi vista todavía existe? ¿Que todavía es visible para el usuario?
Verá, las vistas tienen ciclos de vida. Pueden ser destruidos en cualquier momento, ya sea por el sistema o porque su usuario navega dentro de su aplicación.
Dado que puso en cola un mensaje dentro de a Looper
, el sistema lo entregará si no le indica que lo haga de otra manera. Expone su aplicación a la posibilidad de un bloqueo con un NullPointerException
.
¿Qué sucede si quiero retrasar una acción en una vista?
Aún puede usar esta API y extraer lo Runnable
declarado dentro de su Handler
. Deberá eliminar la devolución de llamada siempre que sea relevante en su código, como se sugiere en el comentario del método.
private val animateRunnable = Runnable { myView.animateSomething() } myView.postDelayed(animateRunnable, delay) // Somewhere in your code myView.removeCallback(animateRunnable)
Abogo por prohibir estos métodos porque se usan incorrectamente o son inadecuados.
Por ejemplo, si está utilizando RxJava
, ya no los necesitará. Hacer una transmisión con un retraso o un temporizador es trivial. Y puede deshacerse de su flujo con facilidad.
val disposable = Observable.timer(delay, TimeUnit.MILLISECONDS) .subscribe { myView.animateSomething() } // Somewhere in your code disposable.dispose()
¿Qué pasa si necesito esperar otro fotograma?
Hasta ahora, solo he mencionado postDelayed()
. A mi, post()
es, con mucho, la API más mal utilizada que he visto cuando se aplica a un View
.
¿Por qué usar post()
cuando no hay demora adjunta? Recuerda Handler
está poniendo en cola mensajes en un Looper
. La publicación pondrá el mensaje en cola y lo entregará en el siguiente marco.
Por lo general, he visto a los desarrolladores usar esto porque la vista no estaba diseñada. Imagina que quieres animar una vista tan pronto como aparece. Necesita su posición y / o su tamaño, dependiendo de la animación que pretenda lograr.
Debe esperar a que se mida la vista. Así que delega la animación al siguiente fotograma y cruza los dedos para que la vista esté lista. Con esta solución destacan dos cosas:
- No tiene ninguna garantía de que su vista se medirá en el siguiente cuadro.
- Este código parece muy irregular.
Como postDelayed()
, hay mecanismos más fiables. Deberías usar ViewTreeObserver
en su lugar, en particular, las dos siguientes devoluciones de llamada:
-
OnPreDrawListener
le notifica cuando una vista está a punto de ser diseñada. En este punto, la vista se ha medido. -
OnGlobalLayoutListener
activa un evento cada vez que cambia el estado de su vista.
Debe tener cuidado al utilizar esos métodos. Es probable que cree pérdidas de memoria si olvida eliminar el oyente una vez que realizó su acción.
myView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { myView.animateSomething() myView.viewTreeObserver.removeOnPreDrawListener(this) return true } })myView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onPreDraw(): Boolean { myView.animateSomething() myView.viewTreeObserver.removeOnGlobalLayoutListener(this) } })
Aún mejor, usa Extensiones KTX, y deje que ellos se encarguen de la calderería por usted.
myView.doOnPreDraw { myView.animateSomething() }myView.doOnLayout { myView.animateSomething() }
¡Eso es todo amigos! Puede que suene duro al defender la prohibición de este mecanismo. Por lo menos, le animo a que los persiga y reflexione sobre si son adecuados para la tarea.
Gracias por leer este artículo.
Añadir comentario