Noções básicas do Android: executando código no thread da interface do usuário

450

No ponto de vista da execução de código no thread da interface do usuário, existe alguma diferença entre:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

ou

MainActivity.this.myView.post(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

e

private class BackgroundTask extends AsyncTask<String, Void, Bitmap> {
    protected void onPostExecute(Bitmap result) {
        Log.d("UI thread", "I am the UI thread");
    }
}
Luky
fonte
Para esclarecer minha pergunta: eu supunha que esses códigos foram chamados de um encadeamento de serviço, normalmente um ouvinte. Também suponho que há um trabalho pesado a ser realizado na função doInBackground () do AsynkTask ou em uma nova tarefa (...) chamada antes dos dois primeiros trechos. De qualquer forma, o onPostExecute () do AsyncTask está sendo colocado no final da fila de eventos, certo?
Luky

Respostas:

288

Nenhum deles é exatamente o mesmo, embora todos tenham o mesmo efeito líquido.

A diferença entre o primeiro e o segundo é que, se você estiver no encadeamento principal do aplicativo ao executar o código, o primeiro ( runOnUiThread()) executará o Runnableimediatamente. O segundo ( post()) sempre coloca o Runnablefinal da fila de eventos, mesmo se você já estiver no encadeamento principal do aplicativo.

O terceiro, supondo que você crie e execute uma instância de BackgroundTask, perderá muito tempo capturando um encadeamento do conjunto de encadeamentos, para executar um no-op padrão doInBackground(), antes de, eventualmente, fazer o que equivale a a post(). Este é de longe o menos eficiente dos três. Use AsyncTaskse você realmente tiver trabalho a fazer em um encadeamento em segundo plano, não apenas pelo uso de onPostExecute().

CommonsWare
fonte
27
Observe também que AsyncTask.execute()requer que você chame a partir do thread da interface do usuário de qualquer maneira, o que torna essa opção inútil para o caso de uso de simplesmente executar código no thread da interface do usuário a partir de um thread de segundo plano, a menos que você mova todo o seu trabalho em segundo plano doInBackground()e o use AsyncTaskadequadamente.
Kabuko 11/10/12
@kabuko como posso verificar se estou chamando AsyncTaskdo thread da interface do usuário?
Neil Galiaskarov
@NeilGaliaskarov Parece uma opção sólida: stackoverflow.com/a/7897562/1839500
Dick Lucas
1
@NeilGaliaskarovboolean isUiThread = (Looper.getMainLooper().getThread() == Thread.currentThread());
ban-
1
@NeilGaliaskarov para versões maiores do que ou igual a M usoLooper.getMainLooper().isCurrentThread
Balu Sangem
252

Eu gosto do comentário do HPP , ele pode ser usado em qualquer lugar sem nenhum parâmetro:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});
pomber
fonte
2
Quão eficiente é isso? É o mesmo que outras opções?
EmmanuelMess
59

Existe uma quarta maneira de usar Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
vasart
fonte
56
Você deve ter cuidado com isso. Porque se você criar um manipulador em um thread que não seja da interface do usuário, postará mensagens no thread que não é da interface do usuário. Um manipulador, por padrão, envia uma mensagem para o encadeamento em que é criado.
Lujop 17/03/2013
108
executar no thread principal da interface do usuário new Handler(Looper.getMainLooper()).post(r), que é a maneira preferida como Looper.getMainLooper()faz uma chamada estática para main, enquanto que postOnUiThread()deve ter uma instância MainActivityno escopo.
HPP
1
@ HP Eu não conhecia esse método, será uma ótima maneira quando você não tiver nem Activity nem View. Funciona bem! muito obrigado muito muito!
Sulfkain
@lujop É o mesmo com o método de retorno de chamada onPreExecute do AsyncTask.
Sreekanth Karumanaghat
18

A resposta de Pomber é aceitável, no entanto, não sou muito fã de criar novos objetos repetidamente. As melhores soluções são sempre as que tentam atenuar a memória. Sim, existe uma coleta automática de lixo, mas a conservação da memória em um dispositivo móvel está dentro dos limites das melhores práticas. O código abaixo atualiza um TextView em um serviço.

TextViewUpdater textViewUpdater = new TextViewUpdater();
Handler textViewUpdaterHandler = new Handler(Looper.getMainLooper());
private class TextViewUpdater implements Runnable{
    private String txt;
    @Override
    public void run() {
        searchResultTextView.setText(txt);
    }
    public void setText(String txt){
        this.txt = txt;
    }

}

Pode ser usado em qualquer lugar como este:

textViewUpdater.setText("Hello");
        textViewUpdaterHandler.post(textViewUpdater);
Joe
fonte
7
+1 por mencionar a criação de GC e objeto. No entanto, eu não necessariamente concordo The best solutions are always the ones that try to mitigate memory hog. Existem muitos outros critérios para best, e isso tem um leve cheiro de premature optimization. Ou seja, a menos que você saiba que está chamando o suficiente, o número de objetos criados é um problema (em comparação com as dez mil outras maneiras pelas quais seu aplicativo provavelmente está criando lixo.), O que pode ser besté escrever o mais simples (mais fácil de entender) ) e passe para outra tarefa.
Home
2
Entre, textViewUpdaterHandlerseria melhor nomeado algo como uiHandlerou mainHandler, pois geralmente é útil para qualquer postagem no thread da interface principal; não está vinculado à sua classe TextViewUpdater. Eu o afastaria do restante desse código e deixaria claro que ele pode ser usado em outro lugar ... O restante do código é duvidoso, porque, para evitar a criação dinâmica de um objeto, você divide o que poderia ser uma única chamada. duas etapas setTexte post, que dependem de um objeto de longa duração que você usa como temporário. Complexidade desnecessária e não segura para threads. Não é fácil de manter.
precisa
Se você realmente tem uma situação onde isso é chamado tantas vezes que vale a pena cache uiHandlere textViewUpdater, em seguida, melhorar a sua classe, alterando a public void setText(String txt, Handler uiHandler)e adicionando linha método uiHandler.post(this); Então chamador pode fazer em uma única etapa: textViewUpdater.setText("Hello", uiHandler);. Então, no futuro, se precisar ser seguro para threads, o método poderá agrupar suas instruções dentro de um bloqueio uiHandlere o chamador permanecerá inalterado.
Home
Tenho certeza de que você só pode executar um Runnable uma vez. O que absolutamente quebra sua idéia, o que é bom no geral.
Nativ 24/04
@ Nativ Nope, Runnable pode ser executado várias vezes. Um segmento não pode.
precisa
8

No Android P, você pode usar getMainExecutor():

getMainExecutor().execute(new Runnable() {
  @Override public void run() {
    // Code will run on the main thread
  }
});

Dos documentos do desenvolvedor do Android :

Retorne um Executor que executará tarefas enfileiradas no thread principal associado a este contexto. Esse é o encadeamento usado para despachar chamadas para componentes de aplicativos (atividades, serviços, etc.).

Do CommonsBlog :

Você pode chamar getMainExecutor () no Contexto para obter um Executor que executará suas tarefas no encadeamento principal do aplicativo. Existem outras maneiras de fazer isso, usando Looper e uma implementação executora personalizada, mas isso é mais simples.

Dick Lucas
fonte
7

Se você precisar usar o Fragment, use

private Context context;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        this.context = context;
    }


    ((MainActivity)context).runOnUiThread(new Runnable() {
        public void run() {
            Log.d("UI thread", "I am the UI thread");
        }
    });

ao invés de

getActivity().runOnUiThread(new Runnable() {
    public void run() {
        Log.d("UI thread", "I am the UI thread");
    }
});

Porque haverá uma exceção de ponteiro nulo em algumas situações, como fragmento de pager

Beyaz
fonte
1

oi pessoal, essa é uma pergunta básica, qualquer coisa que eu digo

use Handler

new Handler().post(new Runnable() {
    @Override
    public void run() {
        // Code here will run in UI thread
    }
});
raghu kambaa
fonte
Existe alguma razão para você optar por usar o manipulador de uso geral em vez de runOnUiThread?
ThePartyTurtle
Isso fornecerá um java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()se não for chamado a partir do thread da interface do usuário.
Roc Boronat