Entrevistador: ¿habla sobre el mecanismo de actualización de la interfaz de usuario de Android?

Este artículo resuelve principalmente los siguientes problemas:

  • Todos sabemos que la frecuencia de actualización de Android es de 60 cuadros por segundo. ¿Significa esto que el método onDraw se llamará cada 16 ms?
  • Si no es necesario volver a dibujar la interfaz, 16ms¿se actualizará la pantalla cuando llegue?
  • invalidate()¿Actualizaremos la pantalla inmediatamente después de llamar ?
  • Decimos que la pérdida de trama se debe al funcionamiento lento del subproceso principal. ¿Por qué el subproceso principal causa la pérdida de trama?
  • Si OnDraw()dibujo cuando la pantalla está a punto de actualizar , ¿se eliminarán los cuadros?

Bueno, con las preguntas anteriores, ingresamos el código fuente para encontrar la respuesta.

1. Proceso de dibujo de pantalla

Los principios básicos del mecanismo de dibujo de pantalla se pueden resumir de la siguiente manera:

El proceso básico de dibujar toda la pantalla es:

  • La aplicación solicita un búfer del servicio del sistema.
  • El servicio del sistema devuelve el búfer
  • Después de que se dibuja la aplicación, el búfer se envía al servicio del sistema

Si lo pones en Android, entonces es:

En Android, una Surface corresponde a una pieza de memoria. Cuando la aplicación de memoria es exitosa, hay un lugar para dibujar en el lado de la aplicación. Dado que el dibujo de vista de Android no es el foco de hoy, aquí está el final ~

2. Análisis de actualización de pantalla

El momento de la actualización de la pantalla es cuando llega la señal Vsync, como se muestra en la figura:

En el lado de Android, ¿quién controla Vsyncla generación? ¿Quién nos notificará para actualizar la aplicación? En Android, la Vysncgeneración de la señal es responsabilidad del HWComposer subyacente, y la aplicación de notificación se actualiza, que es la capa de Java. ChoreographerEl núcleo de la actualización completa de la pantalla de Android es esta Choreographer. Echemos un vistazo al código a continuación. Cada vez que queremos volver a dibujar la interfaz de usuario, se llamará a requestLayout (), por lo que comenzamos con este método:

2.1 requestLayout ()
----》类名:ViewRootImpl

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            //重点
            scheduleTraversals();
        }
    }
2.2 ScheduleTraversals ()
----》类名:ViewRootImpl

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ......
        }
    }

Como puede ver, no hay un redibujo inmediato aquí, pero se han hecho dos cosas:

  • Inserte una SyncBarrier (barrera de sincronización) en la cola de mensajes
  • Pasó Cherographer postunocallback

A continuación, hablamos brevemente sobre esta SyncBarrier (barrera de sincronización). El papel de la barrera asincrónica es:

  • Evitar la ejecución de mensajes de sincronización.
  • Priorizar mensajes asincrónicos

¿Por qué diseñar esto SyncBarrier? La razón principal es que, en Android, algunos mensajes son muy urgentes y deben ejecutarse de inmediato. Si hay demasiados mensajes comunes en la cola de mensajes, puede pasar mucho tiempo antes de su ejecución.

En este punto, algunas personas pueden ser como yo, ¿por qué no ponen una prioridad en el Mensaje y las clasifican según la prioridad? Agarre PriorityQueueno está terminado?

Según tengo entendido, en Android, el diseño de la cola de mensajes es una sola lista vinculada, y el orden de toda la lista vinculada se ordena según el tiempo. Si agrega una regla de clasificación de prioridad en este momento, por un lado, será complicado. Por otro lado, también hace que el mensaje sea incontrolable. Debido a que el usuario mismo puede completar la prioridad, ¿no sería un desastre? Si el usuario siempre llena la máxima prioridad cada vez, provocará que los mensajes del sistema se consuman durante mucho tiempo y todo el sistema tenga problemas, lo que finalmente afectará la experiencia del usuario. Por lo tanto, creo que el diseño de la barrera de sincronización de Android sigue siendo muy inteligente. ~

Bueno, para resumir, scheduleTraversals() después de la ejecución , se insertará una barrera para garantizar la ejecución prioritaria de los mensajes asincrónicos.

Inserte una pequeña pregunta de reflexión: si llamamos a requestLayout () varias veces seguidas en un método, luego pregunte: ¿El sistema insertará múltiples barreras o publicará múltiples Callbacks? La respuesta es no, ¿por qué? ¿Ves la variable mTraversalScheduled? Es la respuesta ~

2.3 Choreographer.postCallback ()

Permítanme hablar brevemente sobre esto Choreographer. El Choreographertraductor chino se llama coreógrafo. Su función principal es coordinar el sistema. (Puede conectarse a google the coreógrafo en el trabajo real. El nombre de esta clase es realmente apropiado ~) ¿Cómo se inicializa la aplicación de Coreógrafo? Es a través del getInstance()método:

   public static Choreographer getInstance() {
        return sThreadInstance.get();
    }
    
        // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

Se publica aquí para recordar a todos que en Choreographerlugar de un singleton, cada hilo tiene una copia separada.

Bien, volvamos a nuestro código:

 ----》类名:Choreographer
 //1
    public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
  //2
     public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
       ....
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }
    //3
      private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
                ...
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
                if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                ...
              }
            }

ChoreographerpostEl callbackpondría a CallbackQueueella, esta CallbackQueuees una lista enlazada.

El primero se callbackTypedará una CallbackQueueúnica lista enlazada, a seguir de acuerdo con una secuencia de tiempo, esta devolución de llamada insertado en una sola lista enlazada;

2,4 scheduleFrameLocked()
----》类名:Choreographer
  private void scheduleFrameLocked(long now) {
       ...
       // If running on the Looper thread, then schedule the vsync immediately,
                // otherwise post a message to schedule the vsync from the UI thread
                // as soon as possible.
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtFrontOfQueue(msg);
                }
            } else {
               ...
            }
        }
    }

scheduleFrameLockedEl rol es:

Si el subproceso actual es Cherographerun subproceso de trabajo, ejecute directamentescheduleVysnLocked

De lo contrario, se envía un mensaje asincrónico a la cola de mensajes. El mensaje asincrónico no se ve afectado por la barrera de sincronización y el mensaje también se inserta en el encabezado de la cola de mensajes. Esto muestra que el mensaje es muy urgente

Al rastrear el código fuente, encontramos que MSG_DO_SCHEDULE_VSYNCeste mensaje realmente ejecutó scheduleFrameLockedeste método al final, por lo que rastreamos scheduleVsyncLocked()este método directamente .

2.5 scheduleVsyncLocked ()
 ----》类名:Choreographer
 
    private void scheduleVsyncLocked() {
        mDisplayEventReceiver.scheduleVsync();
    }
    
 ----》类名:DisplayEventReceiver
 
        public void scheduleVsync() {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else {
        //mReceiverPtr是Native层一个类的指针地址
        //这里这个类指的是底层NativeDisplayEventReceiver这个类
        //nativeScheduleVsync底层会调用到requestNextVsync()去请求下一个Vsync,
        //具体不跟踪了,native层代码更长,还涉及到各种描述符监听以及跨进程数据传输
            nativeScheduleVsync(mReceiverPtr);
        }
    }

Aquí podemos ver una nueva clase: DisplayEventReceiverla función de esta clase es registrar el monitoreo de la señal Vsync, que se notificará cuando llegue la próxima señal Vsync DisplayEventReceiver.

¿Dónde notificar? Los comentarios en el código fuente son muy claros:

 ----》类名:DisplayEventReceiver
 
    // Called from native code.  <---注释还是很良心的
    private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
        onVsync(timestampNanos, builtInDisplayId, frame);
    }

Cuando llegue la próxima señal de Vysnc, el onVsyncmétodo finalmente se llamará :

    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {    }

Haga clic dentro para ver, es una implementación vacía, volviendo a la definición de clase, resultó ser una clase abstracta, su clase de implementación es :, FrameDisplayEventReceiverdefinida en Cherographer:

 ----》类名:Choreographer
 
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
            ....
            }
2.6 FrameDisplayEventReceiver.onVysnc ()
----》类名:Choreographer
 
 private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {

        @Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
             ....
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            ....
            doFrame(mTimestampNanos, mFrame);
        }
    }

onVsyncEl método envía un mensaje a la cola de mensajes del subproceso donde se encuentra Cherographer. Este mensaje es en sí mismo (implementa Runnable), por lo que finalmente se llamará al doFrame()método.

2.7 doFrame (mTimestampNanos, mFrame)

doFrame()El procesamiento se divide en dos etapas:

   void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
           //1、阶段一
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            final long jitterNanos = startNanos - frameTimeNanos;
            if (jitterNanos >= mFrameIntervalNanos) {
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                            + "The application may be doing too much work on its main thread.");
                }
                ...
            }
            ...
        }

frameTimeNanosEs la marca de tiempo actual. Reste la hora actual de la hora de inicio para obtener cuánto tiempo tarda en procesar este marco. Si es mayor mFrameIntervalNano, significa que el procesamiento tomó tiempo y luego imprima el comúnThe application may be doing too much work on its main thread。

Fase dos:

 void doFrame(long frameTimeNanos, int frame) {
 ...
try {
//阶段2
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        }
        ...
        }

doFrame()La segunda etapa del proceso es tratar con todo tipo callback, desde el CallbackQueueinterior hasta el tiempo de ejecución del callbackproceso, callback¿qué pasa con esto?

Aquí para recordar la postCallback()operación anterior :

Este Callback es en realidad solo uno mTraversalRunnable. Es un Runnable, que eventualmente llamará a un run()método para lograr una actualización real de la interfaz:

 ----》类名:ViewRootImpl

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
          ...
            performTraversals();
         ...
        }
    }
    
    private void performTraversals() {
      ...
      //开始真正的界面绘制
       performDraw();
      ...
    }

3. Resumen

Después de un largo seguimiento de código, se considera que se ha completado todo el proceso de actualización de la interfaz. Resumamos a continuación:

4. Pregunta respuesta

Q: Todos sabemos que la frecuencia de actualización de Android es de 60 fotogramas por segundo. onDraw¿ Significa esto que el método se llamará cada 16 ms ?

A: Aquí 60 cuadros / segundo es la frecuencia de actualización de la pantalla, pero si se llamará al onDraw()método depende de si se llama a la aplicación requestLayout()para el monitoreo del registro.

Q: Si no es necesario volver a dibujar la interfaz, ¿se actualizará la pantalla después de 16 ms?

A: Si no necesita volver a dibujar, la aplicación no recibirá la señal Vsync, pero aún así se actualizará, pero los datos extraídos no cambian;

Q: ¿Actualizaremos la pantalla inmediatamente después de llamar a invalidate ()?

A: No, hasta que llegue la próxima señal Vsync

Q: Decimos que la pérdida de trama se debe a que el subproceso principal realizó una operación que consume mucho tiempo, por lo que el subproceso principal realizó una operación que consume mucho tiempo causará una pérdida de trama

A: La razón es que si se realiza una operación que consume mucho tiempo en el subproceso principal, afectará el dibujo del siguiente fotograma, lo que provocará que la interfaz no pueda Vsyncactualizarse en este momento, lo que provocará la pérdida del fotograma.

Q: Si OnDraw()dibujo cuando la pantalla está a punto de actualizar , ¿se eliminarán los cuadros?

Esto no importa mucho, porque la señal Vsync es periódica y cuando iniciamos onDraw () no afectará la actualización de la interfaz;

Publicado 488 artículos originales · elogiado 85 · 230,000 vistas +

Supongo que te gusta

Origin blog.csdn.net/Coo123_/article/details/104992566
Recomendado
Clasificación