Entrevistador da Tencent: Fale-me sobre o mecanismo de atualização da IU do Android.

A entrevista de Dachang para Android não é tão difícil quanto todos imaginavam. Muitas perguntas são sobre como trocar a sopa em vez de trocar o remédio.

Autor: 偶尔皮一下的Raina
link: https: //juejin.im/post/5e64390bf265da575f4e7de8

Este artigo resolve principalmente os seguintes problemas:

  • Todos nós sabemos que a taxa de atualização do Android é de 60 quadros por segundo. Isso significa que o método onDraw será chamado a cada 16 ms?
  • Se a interface não precisar ser redesenhada, 16msa tela será atualizada posteriormente?
  • invalidate()A tela será atualizada imediatamente após a chamada ?
  • Dizemos que a perda de quadros ocorre porque o thread principal realiza uma operação demorada. Por que o thread principal realiza uma operação demorada que causa perda de quadros?
  • Se eu OnDraw()desenhar quando a tela estiver para atualizar , os quadros serão perdidos?

Bem, com as perguntas acima, vamos inserir o código-fonte para encontrar a resposta.

1. Processo de desenho da tela

O princípio básico do mecanismo de desenho da tela pode ser resumido da seguinte forma:

O processo básico de desenho da tela inteira é:

  • O aplicativo solicita um buffer do serviço do sistema
  • O serviço do sistema retorna o buffer
  • Envie o buffer para o serviço do sistema depois que o aplicativo for desenhado

Se for colocado no Android, será:

No Android, um pedaço de Surface corresponde a um pedaço de memória. Depois que o aplicativo de memória for bem-sucedido, há um lugar para desenhar no lado do aplicativo. Já que o desenho de visualização do Android não é o foco de hoje, então vamos parar por aqui ~

2. Análise de atualização da tela

O momento da atualização da tela é quando o sinal Vsync chega, conforme mostrado na figura:

Do lado do Android, quem está controlando Vsynca produção? Quem nos notificará para atualizar o aplicativo? No Android, o Vysncsinal é gerado pelo HWComposer subjacente, e o aplicativo de notificação a ser atualizado é a camada Java. ChoreographerO núcleo da atualização de tela inteira do Android reside nisso Choreographer. Vamos dar uma olhada no código juntos. Sempre que quisermos redesenhar a interface do usuário, chamaremos requestLayout (), portanto, começamos com 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 você pode ver, não há um redesenho imediato aqui, mas duas coisas foram feitas:

  • Insira um SyncBarrier (barreira de sincronização) na fila de mensagens
  • Passou Cherographer postporcallback

A seguir, falaremos brevemente sobre esta SyncBarrier (barreira de sincronização). O papel da barreira assíncrona é:

  • Impedir a execução de mensagens de sincronização
  • Dê prioridade a mensagens assíncronas

Por que projetar isso SyncBarrier? O principal motivo é que, no Android, algumas mensagens são muito urgentes e precisam ser executadas imediatamente. Se houver muitas mensagens comuns na fila de mensagens, o tempo pode ter passado no momento em que é executado.

Neste ponto, alguém pode ser como eu, por que não apenas colocar uma prioridade na Mensagem e classificar por prioridade? Se apossar PriorityQueueainda não terminou?

Meu próprio entendimento é que no Android, o design da fila de mensagens é uma lista unicamente vinculada, e toda a lista vinculada é classificada de acordo com o tempo. Se uma regra de classificação de prioridade for adicionada neste momento, as regras de classificação serão complicadas no Por outro lado, também tornará a mensagem incontrolável. Porque a prioridade pode ser preenchida pelo próprio usuário, não é complicado? Se o usuário sempre preencher a prioridade mais alta todas as vezes, isso fará com que as mensagens do sistema sejam consumidas por um longo tempo, e todo o funcionamento do sistema terá problemas, o que acabará afetando a experiência do usuário. Portanto, acho que o design do Android barreira de sincronização é bastante inteligente. ~

Bem, para resumir, scheduleTraversals() após a execução , uma barreira será inserida para garantir a execução prioritária das mensagens assíncronas.

Insira uma pequena pergunta: Se chamarmos requestLayout () várias vezes em um método, eu gostaria de perguntar: O sistema irá inserir várias barreiras ou postar vários Callbacks? A resposta é não, por quê? Você vê a variável mTraversalScheduled? É a resposta ~

2.3 Choreographer.postCallback ()

Vamos falar brevemente sobre isso primeiro Choreographer. A Choreographertradução chinesa é chamado coreógrafo, e sua principal função é coordenar o sistema. (Você pode ir para o coreógrafo do trabalho real no google, este nome de classe é realmente apropriado ~) Como o aplicativo de classe Choreographer é inicializado? É através do 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;
        }
    };

Ele foi postado aqui para lembrar a todos que não Choreographeré um singleton, mas uma cópia separada para cada tópico.

Ok, de volta ao nosso 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 {
                ...
              }
            }

ChoreographerpostO callbackdiria CallbackQueueque esta CallbackQueueé uma lista vinculada individualmente.

Em primeiro lugar, callbackTypeuma CallbackQueuelista ligada de forma simples será obtida e, em seguida, o retorno de chamada será inserido na lista ligada de forma simples de acordo com a seqüência de tempo;

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 {
               ...
            }
        }
    }

scheduleFrameLockedA função é:

Se o thread atual for Cherographerum thread de trabalho, execute-o diretamentescheduleVysnLocked

Caso contrário, envie uma mensagem assíncrona para a fila de mensagens. Esta mensagem assíncrona não é afetada pela barreira de sincronização e a mensagem deve ser inserida na cabeça da fila de mensagens. Pode ser visto que esta mensagem é muito urgente.

Rastreando o código-fonte, descobrimos que, na verdade, MSG_DO_SCHEDULE_VSYNCessa mensagem foi executada por scheduleFrameLockedesse método, portanto, rastreamos diretamente scheduleVsyncLocked()esse método.

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

Aqui podemos ver uma nova classe :, DisplayEventReceivero papel desta classe é registrar o monitoramento do sinal Vsync, e ela será notificada quando o próximo sinal Vsync chegar DisplayEventReceiver.

Onde notificar? Os comentários no código-fonte são muito claros:

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

Quando o próximo sinal Vysnc chegar, o onVsyncmétodo será finalmente chamado :

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

Clique e veja, é uma implementação vazia, de volta à definição da classe, acabou por ser uma classe abstrata, e sua classe de implementação é :, FrameDisplayEventReceiverdefinida Cherographernela:

 ----》类名: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);
        }
    }

onVsyncO método envia uma mensagem para a fila de mensagens da thread onde o Cherographer está localizado.Esta mensagem é ele mesmo (implementa Runnable), então o doFrame()método será eventualmente chamado .

2.7 doFrame (mTimestampNanos, mFrame)

doFrame()O processamento é dividido em duas 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.");
                }
                ...
            }
            ...
        }

frameTimeNanosÉ o carimbo de data / hora atual. Subtraia a hora atual e a hora de início para saber quanto tempo levou o processamento deste quadro. Se for maior do que mFrameIntervalNanoisso, o processamento é demorado e, em seguida, imprima o que vemos todos os diasThe application may be doing too much work on its main thread。

Fase dois:

 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()A segunda etapa é lidar com todos os tipos de processamento callback, desde o CallbackQueueinterior até o tempo de execução callbackdo processamento, então callbackcomo é isso?

Aqui, para relembrar a postCallback()operação anterior :

Este retorno de mTraversalRunnablechamada é na verdade apenas um , é um Runnable, que eventualmente chamará o run()método para obter a atualização real da interface:

 ----》类名:ViewRootImpl

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

Três, resumo

Depois de um longo rastreamento de código, todo o processo de atualização da interface terminou. Vamos resumir a seguir:

Quatro, resposta a perguntas

Q: Todos nós sabemos que a taxa de atualização do Android é de 60 quadros por segundo. onDrawIsso significa que o método será chamado a cada 16 ms ?

A: Aqui, 60 quadros / segundo é a frequência de atualização da tela, mas se o onDraw()método será chamado depende se o aplicativo é chamado requestLayout()para monitoramento de registro.

Q: Se a interface não precisar ser redesenhada, a tela será atualizada após 16 ms?

A: Se não houver necessidade de redesenhar, o aplicativo não receberá o sinal Vsync, mas ainda será atualizado, mas os dados desenhados não serão alterados;

Q: A tela será atualizada imediatamente após chamarmos invalidate ()?

A: Não, até que o próximo sinal Vsync chegue

Q: Dizemos que o quadro é descartado porque o encadeamento principal realiza uma operação demorada, o motivo pelo qual o encadeamento principal realiza uma operação demorada fará com que o quadro seja descartado

A: O motivo é que, se uma operação demorada for realizada no thread principal, ela afetará o desenho do próximo quadro, fazendo com que a interface não Vsyncconsiga atualizar neste momento, resultando em perda de quadros.

Q: Se eu OnDraw()desenhar quando a tela estiver para atualizar , os quadros serão perdidos?

Isso não importa muito, porque o sinal Vsync é periódico, quando iniciamos onDraw () não afetará a atualização da interface;

Cinco, documentos de referência

Seis, finalmente

Coloquei os materiais de direção de aprendizagem mais importantes e populares no Android que compilei durante este período de tempo em nossa pasta compartilhada do círculo de troca de tecnologia Android com milhares de pessoas (clique aqui para ver) . Também há rotas de programação de autoaprendizagem e entrevistas em direções diferentes. Coleção de perguntas / clássicos do rosto e uma série de artigos técnicos, etc.

Os recursos são atualizados continuamente e todos são bem-vindos para aprender e discutir juntos.

Acho que você gosta

Origin blog.csdn.net/weixin_49559515/article/details/114640019
Recomendado
Clasificación