Exploración del problema de rotación de imágenes causado por onAttachedToWindow de View
Directorio de artículos
prefacio
Este artículo se basa en el pensamiento profundo de todas las teorías básicas de este artículo basado en el método postDelayed de Ver . Se puede decir que es la práctica del pensamiento profundo sobre los puntos de conocimiento de este artículo para el método postDelayed de Vista .
Un día, cuando un colega, Xjin, estaba haciendo una página de lista para agregar las Banner
necesidades del carrusel, publicó que ocasionalmente el tiempo del intervalo del carrusel estaría desordenado.
Vi su implementación de carrusel: use Handle.postDelayed
la duración del carrusel de intervalo para enviarlo en un bucle después de cada ejecución del carrusel;
El código parece no tener mayores problemas, ¡pero parece que no debería ser removeCallbacks
válido~!
Manejar # eliminar devoluciones de llamada
stackoverflow
Encontré información relevante en Internet ¿ Por qué usar removeCallbacks() con postDelayed()?, y luego traté de postDelayed
cambiarlo a Unreliable post
, ¡y descubrí que el problema del tiempo de intervalo de carrusel desordenado se resolvió ~!
Aunque no está claro qué causó que el problema no volviera a aparecer, la investigación de seguimiento no continuó debido a otras interrupciones del trabajo.
Unos días después, apareció nuevamente el problema de los intervalos de rotación desordenados.
Esta vez usamos una
Handler
suma personalizada , que resolvió el problema a la perfección.removeCallBacks
postDelayed
¡Registremos el pensamiento en el proceso de resolver todo el problema ~!
cuestiones pendientes
View.removeCallbacks
¿Es realmente confiable?View.post
En comparaciónView.postDelayed
con por qué la frecuencia de recurrencia de errores es menor;
Ver#dispatchAdjuntoalaventana
Handle
¿ El removeCallBacks
método de eliminación es poco fiable? Si la tarea actual no se está ejecutando, la tarea debe eliminarse.
En otras palabras, Handle#removeCallBacks
lo que se elimina es lo que está en cola para ser ejecutado Message
.
Entonces, ¿cuál es exactamente el problema y por qué se reduce la probabilidad de que vuelva a ocurrir
postDelayed
al reemplazarlo ?post
Durante algún tiempo esta vez, seguí el código fuente y descubrí que View#postDelayed
los mensajes enviados por el usuario pueden no colocarse inmediatamente en la cola de mensajes.
Revise el método postDelayed de la Vista anterior y piense profundamente en la descripción de este artículo View.postDelayed小结
:
postDelayed
Cuando se llama al método, si el actualView
no está adjuntoWindow
, primero seRunnable
almacenará en caché en la cola.RunQueue
Espere hasta que vuelva a realizarView.dispatchAttachedToWindow
la llamada . El mismo solo se utilizará una vez en este proceso.ViewRootHandler
postDelayed
Runnable
postDelay
Cuando imprimimos stopTimer
y startTimer
ejecutamos el método , encontramos que la mayoría de ellos son cuando la lista se desliza rápidamente .ViewPager#getHandler
Handler
null
Bueno, ignoré que esto Banner
estaba en el proceso de deslizamiento antes View#dispatchDetachedFromWindow
. Las llamadas a este método dan como resultado un as View
interno .Handle
null
Si es View
así Handle
, null
entonces Message
la implementación de la puede verse afectada.
También se ha analizado en profundidad el impacto del método postDelayed de ViewmAttachInfo
en este artículo . View.postDelayed
Aquí tomamos el código fuente principal y lo leemos.
//View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
/****部分代码省略*****/
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
/****部分代码省略*****/
}
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;
}
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
public boolean removeCallbacks(Runnable action) {
if (action != null) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mHandler.removeCallbacks(action);
attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
Choreographer.CALLBACK_ANIMATION, action, null);
}
getRunQueue().removeCallbacks(action);
}
return true;
}
post
Y postDelayed
en el método postDelayed de View, se explica en este artículo, y se almacenará durante la ejecución cuando se View
ejecute dispatchAttachedToWindow
el método .RunQueue
Message
RunQueue.executeActions
esViewRootImpl.performTraversal
llamado en él;
RunQueue.executeActions
host.dispatchAttachedToWindow(mAttachInfo, 0);
se llama después de la ejecución ;
RunQueue.executeActions
Se llama cada vez que seViewRootImpl.performTraversal
ejecuta;
RunQueue.executeActions
Los parámetros estánmAttachInfo
enHandler
eso esViewRootHandler
;
A partir de aquí, no hay problema, View#post
los mensajes que utilicemos se ejecutarán cuando se View
reciban ;Attached
Durante el desarrollo de programas generales, si se trata del uso de contenedores, se deben considerar dos situaciones de producción y consumo.
En el código fuente anterior, hemos visto la lógica de la ejecución de mensajes (eventualmente, todos los mensajes se colocarán MainLooper
y consumirán), ¿qué sucede si se eliminan los mensajes involucrados?
public class HandlerActionQueue {
public void removeCallbacks(Runnable action) {
synchronized (this) {
final int count = mCount;
int j = 0;
final HandlerAction[] actions = mActions;
for (int i = 0; i < count; i++) {
if (actions[i].matches(action)) {
// Remove this action by overwriting it within
// this loop or nulling it out later.
continue;
}
if (j != i) {
// At least one previous entry was removed, so
// this one needs to move to the "new" list.
actions[j] = actions[i];
}
j++;
}
// The "new" list only has j entries.
mCount = j;
// Null out any remaining entries.
for (; j < count; j++) {
actions[j] = null;
}
}
}
}
Al eliminar el mensaje, si el actual View
está mAttahInfo
vacío, solo eliminaremos RunQuque
el mensaje en el caché. . .
¡Oh,
entonces es así~!
¡Esa es realmente la única manera~!
En resumen, si View#mAttachInfo
no está vacío, hola, a mí ya todos. De lo contrario View#post
, el mensaje esperará a que se agregue a la cola de caché, pero el mensaje eliminado solo puede eliminar el RunQueue
mensaje almacenado en caché. Si RunQueue
las noticias en Zhong se han sincronizado con MainLooper
Zhong en este momento, lamento que ninguna View#mAttachInfo
concubina no pueda eliminarlas.
De acuerdo con el código comercial anterior, si la operación de eliminación de mensajes se ejecuta más tarde, los mensajes que ya están en
View
la cola no se pueden eliminar y si se continúa agregando el mensaje del carrusel, provocará la ejecución frecuente del bloque de código del carrusel.dispatchDetachedFromWindow
MainLooper
La descripción del texto puede no ser fácil de entender por un tiempo, el siguiente es un diagrama de análisis simple de un carrusel inesperado (por qué hay varios mensajes de carrusel):
Hablemos de post y postDelayed
Si solo miro el código fuente relevante, siento que no puedo encontrar el problema, porque el método post
también se ejecuta al final postDelayed
. Entonces, la comparación entre los dos es solo una diferencia de tiempo. ¿Qué impacto puede causar esta diferencia de tiempo?
Mirando hacia atrás en el artículo que escribí antes y pensando en el mecanismo de mensajes de Android (Handler & Looper) por otro año , hay un término llamado barrera de sincronización .
Barrera síncrona : ignora todos los mensajes síncronos y devuelve mensajes asíncronos. En otras palabras, la barrera de sincronización
Handler
agrega un mecanismo de prioridad simple al mecanismo de mensajes, y la prioridad de los mensajes asíncronos es mayor que la de los mensajes síncronos.
La barrera de sincronización más utilizada es la actualización de página ( ) . ViewRootImpl#mTraversalRunnable
Para artículos relacionados, puede leer Choreographer, el coreógrafo del sistema Android y el monólogo de ViewRootImpl, I am not a View (layout).View#dispatchAttachedToWindow
El método descrito en este artículo es ViewRootImpl#performTraversals
desencadenado por .
¿ Por qué decimos barrera de sincronización ? View#dispatchAttachedToWindow
La llamada al método que se puede ver en el diagrama de flujo del carrusel inesperado anterior es muy importante para todo el proceso. 移除
Y 添加
dos mensajes, dos si postDelayed
hay otros mensajes insertados en el medio, y la barrera de sincronización es el mensaje más probable que se inserte y este mensaje provocará View#mAttachInfo
cambios.
Esto empeora el código con algunos pequeños problemas y el error es más fácil de reproducir.
Hablando de RecycleView
¿Por qué debería mencionar este problema, porque muchas veces View.post
no tenemos ningún problema con la ejecución de tareas (PD: siento que este punto de vista también es la fuente original de este problema).
RecycleView
El niño interno que conocemos View
es solo una precarga más que el tamaño de la pantalla View
, y exceder este rango o ingresar a este rango hará que se View
agregue y elimine.
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
/***部分代码省略***/
private void initChildrenHelper() {
this.mChildHelper = new ChildHelper(new Callback() {
public int getChildCount() {
return RecyclerView.this.getChildCount();
}
public void addView(View child, int index) {
RecyclerView.this.addView(child, index);
RecyclerView.this.dispatchChildAttached(child);
}
public int indexOfChild(View view) {
return RecyclerView.this.indexOfChild(view);
}
public void removeViewAt(int index) {
View child = RecyclerView.this.getChildAt(index);
if (child != null) {
RecyclerView.this.dispatchChildDetached(child);
child.clearAnimation();
}
RecyclerView.this.removeViewAt(index);
}
}
/***部分代码省略***/
}
/***部分代码省略***/
}
Si deslizamos la lista de un lado a otro con frecuencia, entonces esta se ejecutará Banner
de forma continua . Esto resulta en la mayor parte del tiempo , lo que afecta la lógica de ejecución enviada al subproceso principal en el código comercial .dispatchAttachedToWindow
dispatchDetachedToWindow
View#mAttachInfo
null
Message
El artículo casi está aquí. Resolver este problema me ha traído un sentimiento profundo. Antes de aprender el código fuente relevante del sistema Android, era solo que todos estaban aprendiendo y entrevistando.
Todavía hay relativamente pocos puntos de conocimiento que se puedan aplicar al proceso real de investigación y desarrollo, en muchos casos es suficiente para resolver el problema, es decir, saberlo pero no saber por qué.
El problema resuelto esta vez puede hacerme sentir profundamente fuck the source code is beatifully
.
El artículo se cuenta aquí, si tiene otras necesidades para comunicarse, ¡puede dejar un mensaje ~!
En 2023, les deseo un nuevo año con un estado de ánimo siempre cambiante, felicidad como el azúcar como la miel, amigos que valoran el amor y la rectitud, amantes que nunca se irán, éxito en el trabajo y ¡que todo salga bien!