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,
16ms
a 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 Vsync
a produção? Quem nos notificará para atualizar o aplicativo? No Android, o Vysnc
sinal é gerado pelo HWComposer subjacente, e o aplicativo de notificação a ser atualizado é a camada Java. Choreographer
O 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 post
porcallback
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 PriorityQueue
ainda 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 Choreographer
traduçã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 {
...
}
}
Choreographerpost
O callback
diria CallbackQueue
que esta CallbackQueue
é uma lista vinculada individualmente.
Em primeiro lugar, callbackType
uma CallbackQueue
lista 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 {
...
}
}
}
scheduleFrameLocked
A função é:
Se o thread atual for Cherographer
um 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_VSYNC
essa mensagem foi executada por scheduleFrameLocked
esse 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 :, DisplayEventReceiver
o 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 onVsync
mé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 é :, FrameDisplayEventReceiver
definida Cherographer
nela:
----》类名: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);
}
}
onVsync
O 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 mFrameIntervalNano
isso, 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 CallbackQueue
interior até o tempo de execução callback
do processamento, então callback
como é isso?
Aqui, para relembrar a postCallback()
operação anterior :
Este retorno de mTraversalRunnable
chamada é 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. onDraw
Isso 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 Vsync
consiga 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
-
gityuan
Cherographer
Princípio do Grande Deus - Vídeo MOOC
- Recursos de aprendizagem gratuitos na estação B
- Blogs dos figurões do grupo de intercâmbio de aprendizagem
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.