Exploración del problema de rotación de imágenes causado por onAttachedToWindow de View

Exploración del problema de rotación de imágenes causado por onAttachedToWindow de View

inserte la descripción de la imagen aquí

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 Bannernecesidades del carrusel, publicó que ocasionalmente el tiempo del intervalo del carrusel estaría desordenado.

Vi su implementación de carrusel: use Handle.postDelayedla duración del carrusel de intervalo para enviarlo en un bucle después de cada ejecución del carrusel;

inserte la descripción de la imagen aquí

El código parece no tener mayores problemas, ¡pero parece que no debería ser removeCallbacksválido~!

Manejar # eliminar devoluciones de llamada

stackoverflowEncontré información relevante en Internet ¿ Por qué usar removeCallbacks() con postDelayed()?, y luego traté de postDelayedcambiarlo 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 Handlersuma personalizada , que resolvió el problema a la perfección.removeCallBackspostDelayed

¡Registremos el pensamiento en el proceso de resolver todo el problema ~!

cuestiones pendientes

  1. View.removeCallbacks¿Es realmente confiable?
  2. View.postEn comparación View.postDelayedcon por qué la frecuencia de recurrencia de errores es menor;

Ver#dispatchAdjuntoalaventana

Handle¿ El removeCallBacksmétodo de eliminación es poco fiable? Si la tarea actual no se está ejecutando, la tarea debe eliminarse.
En otras palabras, Handle#removeCallBackslo 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 postDelayedal reemplazarlo ?post

Durante algún tiempo esta vez, seguí el código fuente y descubrí que View#postDelayedlos 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小结:

postDelayedCuando se llama al método, si el actual Viewno está adjunto Window, primero se Runnablealmacenará en caché en la cola. RunQueueEspere hasta que vuelva a realizar View.dispatchAttachedToWindowla llamada . El mismo solo se utilizará una vez en este proceso.ViewRootHandlerpostDelayedRunnablepostDelay

Cuando imprimimos stopTimery startTimerejecutamos el método , encontramos que la mayoría de ellos son cuando la lista se desliza rápidamente .ViewPager#getHandlerHandlernull

Bueno, ignoré que esto Bannerestaba en el proceso de deslizamiento antes View#dispatchDetachedFromWindow. Las llamadas a este método dan como resultado un as Viewinterno .Handlenull

Si es Viewasí Handle, nullentonces Messagela 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.postDelayedAquí 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;
}

postY postDelayeden el método postDelayed de View, se explica en este artículo, y se almacenará durante la ejecución cuando se Viewejecute dispatchAttachedToWindowel método .RunQueueMessage

RunQueue.executeActionses ViewRootImpl.performTraversalllamado en él;

RunQueue.executeActionshost.dispatchAttachedToWindow(mAttachInfo, 0);se llama después de la ejecución ;

RunQueue.executeActionsSe llama cada vez que se ViewRootImpl.performTraversalejecuta;

RunQueue.executeActionsLos parámetros están mAttachInfoen Handlereso es ViewRootHandler;

A partir de aquí, no hay problema, View#postlos mensajes que utilicemos se ejecutarán cuando se Viewreciban ;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 MainLoopery 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 Viewestá mAttahInfovacío, solo eliminaremos RunQuqueel mensaje en el caché. . .

¡Oh,
entonces es así~!
¡Esa es realmente la única manera~!

En resumen, si View#mAttachInfono 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 RunQueuemensaje almacenado en caché. Si RunQueuelas noticias en Zhong se han sincronizado con MainLooperZhong en este momento, lamento que ninguna View#mAttachInfoconcubina 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 Viewla 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.dispatchDetachedFromWindowMainLooper

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

inserte la descripción de la imagen aquí

Hablemos de post y postDelayed

Si solo miro el código fuente relevante, siento que no puedo encontrar el problema, porque el método posttambié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 Handleragrega 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#mTraversalRunnablePara 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#performTraversalsdesencadenado por .

¿ Por qué decimos barrera de sincronización ? View#dispatchAttachedToWindowLa 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 postDelayedhay 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#mAttachInfocambios.
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.postno 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).

RecycleViewEl niño interno que conocemos Viewes 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 Viewagregue 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);
            }
        }
        /***部分代码省略***/
    }
    /***部分代码省略***/
}

inserte la descripción de la imagen aquí

Si deslizamos la lista de un lado a otro con frecuencia, entonces esta se ejecutará Bannerde 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 .dispatchAttachedToWindowdispatchDetachedToWindow
View#mAttachInfonullMessage

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!

Supongo que te gusta

Origin blog.csdn.net/stven_king/article/details/128676314
Recomendado
Clasificación