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

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().


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

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

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

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.


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


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

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.