Se algumas operações demoradas forem realizadas no encadeamento principal (também chamado de encadeamento de UI), ocorrerão problemas de ANR. Para evitar ANR, é necessário abrir um sub-thread para processar operações demoradas, como solicitações de rede, operações de banco de dados e operações de leitura de arquivo.
Que tal atualizar a IU diretamente no thread filho após a conclusão da operação demorada?
De modo geral, ocorre o seguinte erro:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
at android.view.View.requestLayout(View.java:19781)
at android.widget.TextView.checkForRelayout(TextView.java:7368)
at android.widget.TextView.setText(TextView.java:4480)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.TextView.setText(TextView.java:4312)
at icbc.agree.tmpapppp.MainActivity$2.run(MainActivity.java:37)
A partir da dica de código acima, não é difícil ver que a IU só pode ser atualizada no encadeamento principal, e é por isso que o encadeamento principal também é chamado de encadeamento de IU.
Mas quando iniciamos um thread filho em onCreate para alterar o texto de um TextView, ficaremos surpresos ao descobrir que o programa pode ser executado normalmente.
Na verdade, é porque o trabalho de verificação do thread é feito por ViewRootImpl, mas iniciar o thread filho em onCreate pode ter sido executado antes de o objeto ViewRootImpl ser criado. Método setText de TextView, se você não acreditar, você pode dormir um pouco antes de setText. que.
Como a IU não pode ser atualizada no thread filho, precisamos de um mecanismo para notificar o thread principal para atualizar a IU, que é de responsabilidade do Handler!
Como o Handler é implementado?
Para entender o princípio do Handler, precisamos conhecer vários conceitos:
Mensagem: A portadora de dados passados e processados pelo Handler
Message Queue: Message queue
Looper: Um iterador que constantemente obtém dados da fila de mensagens
O fluxo de trabalho do Handler é mais ou menos como mostrado na figura acima. O Handler envia uma Mensagem por meio de métodos como sendMessage e a coloca em MessageQueue. O Looper constantemente tira os dados da fila e os distribui.
A seguir, observe o princípio de funcionamento do Handler a partir do código-fonte do sistema Android:
Primeiro, vamos examinar o trabalho no thread principal
public static void main(String[] args) {
<-省略部分不相关代码->
Looper.prepareMainLooper();
<-省略部分不相关代码->
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Vemos Looper.prepareMainLooper (); nesta linha de código, você pode ir para Looper para ver sua função, na verdade, é o método de preparação chamado, e sua função é criar uma fila de mensagens. O código é o seguinte:
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
O último loop () no thread principal é chamado, agora vamos olhar o código correspondente:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
<-省略代码部分->
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
<-省略代码部分->
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
<-省略代码部分->
}
}
Vamos analisar o código acima, obtemos a Fila de Mensagens e, em seguida, iniciamos um loop infinito, retire a Mensagem por meio de queue.next (), se a mensagem estiver vazia, significa que não há mensagem na fila de mensagens, então pare a
última mensagem do loop . target.dispatchMessage (msg); Deve ser familiar, certo? Este destino é o Handler que envia a mensagem.
Agora, vamos dar uma olhada na parte Handler:
seja sendMessage ou sendEmptyMessage, sendMessageAtTime é finalmente chamado, portanto, veremos diretamente a parte do código-fonte de sendMessageAtTime.
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
Continue a olhar para baixo
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Você encontrou o alvo? Se tiver interesse, pode continuar procurando o código, na verdade é para adicionar Message à fila de mensagens
, até o momento todo o processo de Handler está resolvido!