Qual é o objetivo do Looper e como usá-lo?

454

Sou novo no Android. Quero saber o que a Looperclasse faz e também como usá-la. Eu li a documentação da classe Android Looper, mas não consigo entendê-la completamente. Eu já vi isso em muitos lugares, mas incapaz de entender seu propósito. Alguém pode me ajudar definindo a finalidade Loopere também dando um exemplo simples, se possível?

Khawar Raza
fonte
7
Acabei de encontrar uma explicação extraordinariamente completa e clara de Looper e seu uso no Safari Books Online. Infelizmente, suspeito que o acesso seja gratuito apenas por um tempo limitado. safaribooksonline.com/library/view/efficient-android-threading/…
Joe Lapp
1
Os artigos e páginas de referência do Android exigem que você tenha e compreenda um artigo anterior, antes de poder entender o artigo atual. Sugiro que você leia os artigos sobre Atividade e Serviço nos guias da API e, em seguida, leia Handler and Looper. Também ajuda se você entender o que é um tópico (não um tópico do Android, mas um tópico em geral ... por exemplo, POSIX).
FutureSci 20/02
1
Eu achei este artigo útil: codetheory.in/…
Herman

Respostas:

396

O que é Looper?

Looper é uma classe usada para executar as Mensagens (Runnables) em uma fila. Threads normais não possuem essa fila, por exemplo, thread simples não possui nenhuma fila. Ele é executado uma vez e após a conclusão do método, o thread não executa outra mensagem (executável).

Onde podemos usar a classe Looper?

Se alguém quiser executar várias mensagens (Runnables), deverá usar a classe Looper, responsável por criar uma fila no encadeamento. Por exemplo, ao escrever um aplicativo que baixa arquivos da Internet, podemos usar a classe Looper para colocar os arquivos a serem baixados na fila.

Como funciona?

Existe um prepare()método para preparar o Looper. Em seguida, você pode usar o loop()método para criar um loop de mensagem no segmento atual e agora o seu Looper está pronto para executar as solicitações na fila até você sair do loop.

Aqui está o código pelo qual você pode preparar o Looper.

class LooperThread extends Thread {
      public Handler mHandler;

      @Override
      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              @Override
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }
Dharmendra
fonte
17
Um AsyncTask é melhor para esse propósito e menos complexo, pois encapsula todo o gerenciamento de threads.
Fernando Gallego
4
Deve ter anotações @Override antes do run () e handleMessage () métodos
Andrew Mackenzie
5
A documentação indica que você deve chamar looper.quit. No seu código acima, o Looper.loop será bloqueado indefinidamente.
AndroidDev 5/07/2013
2
Como sair de um loop. Quero dizer onde incluir Looper.quit () no exemplo de código acima?
precisa
6
Eu acho que seria melhor usar HandlerThread, que é uma classe conveniente para um thread com um looper.
Nimrod Dayan
287

Você pode entender melhor o que é Looper no contexto da estrutura da GUI. Looper é feito para fazer 2 coisas.

1) O Looper transforma um encadeamento normal , que termina quando o método run () retorna, em algo executado continuamente até o aplicativo Android ser executado , o que é necessário na estrutura da GUI (tecnicamente, ele ainda termina quando o método run () retorna. esclareça o que quero dizer abaixo).

2) O Looper fornece uma fila na qual os trabalhos a serem executados são enfileirados, o que também é necessário na estrutura da GUI.

Como você deve saber, quando um aplicativo é iniciado, o sistema cria um encadeamento de execução para o aplicativo, chamado “principal”, e os aplicativos Android normalmente executam inteiramente em um único encadeamento, por padrão, o “encadeamento principal”. Mas o tópico principal não é um tópico especial e secreto . É apenas um thread normal semelhante aos threads criados com o new Thread()código, o que significa que termina quando o método run () retorna! Pense no exemplo abaixo.

public class HelloRunnable implements Runnable {
    public void run() {
        System.out.println("Hello from a thread!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}

Agora, vamos aplicar esse princípio simples aos aplicativos Android. O que aconteceria se um aplicativo Android fosse executado no encadeamento normal? Um thread chamado "main" ou "UI" ou o que quer que inicie seu aplicativo e desenha toda a interface do usuário. Portanto, a primeira tela é exibida para os usuários. E agora? O segmento principal termina? Não, não deveria. Deve esperar até que os usuários façam algo, certo? Mas como podemos alcançar esse comportamento? Bem, podemos tentar com Object.wait()ouThread.sleep(). Por exemplo, o encadeamento principal termina seu trabalho inicial para exibir a primeira tela e dorme. Ele acorda, o que significa interrompido, quando um novo trabalho é feito. Até aqui tudo bem, mas neste momento precisamos de uma estrutura de dados semelhante a uma fila para armazenar vários trabalhos. Pense em um caso em que um usuário toca na tela em série e uma tarefa leva mais tempo para ser concluída. Portanto, precisamos ter uma estrutura de dados para manter os trabalhos a serem executados da maneira primeiro a entrar, primeiro a sair. Além disso, você pode imaginar, implementar o encadeamento sempre em execução e processar o trabalho quando chegou usando interrupção não é fácil e leva a um código complexo e muitas vezes impossível de manter. Preferimos criar um novo mecanismo para esse fim, e é disso que Looper trata . O documento oficial da classe Looperdiz: "Os threads por padrão não têm um loop de mensagem associado a eles" e Looper é uma classe "usada para executar um loop de mensagem para um thread". Agora você pode entender o que isso significa.

Para deixar as coisas mais claras, vamos verificar o código em que o thread principal é transformado. Tudo acontece na classe ActivityThread . Em seu método main (), você pode encontrar o código abaixo, que transforma um thread principal normal em algo que precisamos.

public final class ActivityThread {
    ...
    public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        Looper.loop();
        ...
    }
}

e Looper.loop()método fazem um loop infinito e desenfileiram uma mensagem e processam uma de cada vez:

public static void loop() {
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        ...
        msg.target.dispatchMessage(msg);
        ...
    }
}

Portanto, basicamente Looper é uma classe criada para solucionar um problema que ocorre na estrutura da GUI. Mas esse tipo de necessidade também pode acontecer em outra situação. Na verdade, é um padrão bastante famoso para aplicativos multiencadeados e você pode aprender mais sobre isso em " Concurrent Programming in Java ", de Doug Lea (Especialmente, o capítulo 4.1.4 "Worker Threads" seria útil). Além disso, você pode imaginar que esse tipo de mecanismo não é exclusivo na estrutura do Android, mas toda a estrutura da GUI pode precisar de algo semelhante a isso. Você pode encontrar quase o mesmo mecanismo no framework Java Swing.

김준호
fonte
26
Esta é a única resposta que realmente explica qualquer coisa sobre por que a classe Looper seria usada. Não sei por que não é a resposta principal, as três respostas mais bem avaliadas não explicam nada.
Andrew Koster 10/16
4
@AK. Por isso acrescentei essa resposta, mesmo que parecesse tarde demais. Fico feliz que minha resposta tenha ajudado! :)
#
1
@ Hey-men-whatsup Sim
6/16
1
Antes de ler isso, eu era como "Looper ???" e agora "Ah, sim, vamos discutir". Graças homem, grande resposta :)
umerk44
Pergunta rápida. Você afirmou que no thread principal depois que ele desenha todos os elementos da interface do usuário, ele é colocado em suspensão. Mas digamos que o usuário interaja com um botão na tela, esse clique não é colocado em uma fila principal, então algum objeto o despacha para a atividade correta, então o thread principal dessa atividade está ativado e será executado o código para no retorno de chamada desse botão, clique em?
CapturedTree
75

O Looper permite que tarefas sejam executadas seqüencialmente em um único encadeamento. E manipulador define as tarefas que precisamos executar. É um cenário típico que estou tentando ilustrar neste exemplo:

class SampleLooper extends Thread {
@Override
public void run() {
  try {
    // preparing a looper on current thread     
    // the current thread is being detected implicitly
    Looper.prepare();

    // now, the handler will automatically bind to the
    // Looper that is attached to the current thread
    // You don't need to specify the Looper explicitly
    handler = new Handler();

    // After the following line the thread will start
    // running the message loop and will not normally
    // exit the loop unless a problem happens or you
    // quit() the looper (see below)
    Looper.loop();
  } catch (Throwable t) {
    Log.e(TAG, "halted due to an error", t);
  } 
}
}

Agora podemos usar o manipulador em alguns outros threads (por exemplo, thread da interface do usuário) para postar a tarefa no Looper para executar.

handler.post(new Runnable()
{
public void run() {
//This will be executed on thread using Looper.
    }
});

No thread da interface do usuário, temos um Looper implícito que nos permite manipular as mensagens no thread da interface do usuário.

user2834274
fonte
ele não trava nenhum processo de interface do usuário, é verdade?
precisa
4
Obrigado por incluindo exemplo sobre como postar "empregos" para a fila
Peter Lillevold
Isso não explica por que alguém usaria essa classe, apenas como.
Andrew Koster 10/10
33

Android Looperé um wrapper para anexar MessageQueueao Threade gerencia o processamento da fila. Parece muito enigmático na documentação do Android e, muitas vezes, podemos enfrentar Looperproblemas de acesso à interface do usuário relacionados. Se não entendermos o básico, torna-se muito difícil de lidar.

Aqui está um artigo que explica Looperciclo de vida, como usá-lo e uso de LoopernoHandler

insira a descrição da imagem aqui

Looper = Thread + MessageQueue

user542954
fonte
3
Isso não explica por que alguém usaria essa classe, apenas como.
Andrew Koster 10/10
14

Definição de Looper & Handler:

Looper é uma classe que transforma um encadeamento em um Pipeline Thread and Handler fornece um mecanismo para enviar tarefas a ele a partir de qualquer outro encadeamento.

Detalhes:

Portanto, um encadeamento PipeLine é um encadeamento que pode aceitar mais tarefas de outros encadeamentos através de um manipulador.

O Looper é nomeado assim porque implementa o loop - pega a próxima tarefa, executa-a, depois a próxima e assim por diante. O manipulador é chamado de manipulador porque é usado para manipular ou aceitar a próxima tarefa a cada vez de qualquer outro encadeamento e passar para o Looper (Thread ou PipeLine Thread).

Exemplo:

Um exemplo muito perfeito de Looper and Handler ou PipeLine Thread é baixar mais de uma imagem ou carregá-las em um servidor (Http), uma a uma em um único thread, em vez de iniciar um novo Thread para cada chamada de rede em segundo plano.

Leia mais aqui sobre Looper and Handler e a definição de Pipeline Thread:

Android Guts: Introdução aos Loopers e Manipuladores

ahmadalibaloch
fonte
7

Um Looper possui um synchronized MessageQueueusado para processar as mensagens colocadas na fila.

Ele implementa um Threadpadrão de armazenamento específico.

Apenas um Looperpor Thread. Os principais métodos incluem prepare(), loop()e quit().

prepare()inicializa a corrente Threadcomo a Looper. prepare()é um staticmétodo que usa a ThreadLocalclasse como mostrado abaixo.

   public static void prepare(){
       ...
       sThreadLocal.set
       (new Looper());
   }
  1. prepare() deve ser chamado explicitamente antes de executar o loop de eventos.
  2. loop()executa o loop de eventos que aguarda a chegada de mensagens na fila de mensagens de um segmento específico. Depois que a próxima mensagem é recebida, o loop()método despacha a mensagem para seu manipulador de destino
  3. quit()desliga o loop de eventos. Não encerra o loop, mas enfileira uma mensagem especial

Looperpode ser programado de Threadvárias maneiras

  1. Ampliar Thread

  2. Chame Looper.prepare()para inicializar o Thread como umLooper

  3. Crie um ou mais Handlerprocedimentos para processar as mensagens recebidas

  4. Ligue Looper.loop()para processar as mensagens até que o loop seja solicitado quit().
Theo
fonte
5

Vida útil do java O thread terminou após a conclusão do run()método. O mesmo encadeamento não pode ser iniciado novamente.

Looper transforma normal Threadem um loop de mensagem. Os principais métodos de Loopersão:

void prepare ()

Inicialize o thread atual como um looper. Isso permite criar manipuladores que fazem referência a esse looper, antes de realmente iniciar o loop. Certifique-se de chamar loop () depois de chamar esse método e encerre-o chamando quit ().

void loop ()

Execute a fila de mensagens neste encadeamento. Certifique-se de chamar quit () para finalizar o loop.

void quit()

Sai do looper.

Faz com que o método loop () seja encerrado sem processar mais nenhuma mensagem na fila de mensagens.

Este artigo dos mindorks de Janishar explica os conceitos principais de maneira agradável.

insira a descrição da imagem aqui

Looperestá associado a um thread. Se você precisar Looperde um thread da interface do usuário, Looper.getMainLooper()retornará um thread associado.

Você precisa Looperestar associado a um manipulador .

Looper,, Handlere HandlerThreadsão a maneira do Android resolver os problemas de programação assíncrona.

Depois de ter Handler, você pode ligar para as APIs abaixo.

post (Runnable r)

Faz com que o Runnable r seja adicionado à fila de mensagens. O executável será executado no encadeamento ao qual esse manipulador está anexado.

boolean sendMessage (Message msg)

Coloca uma mensagem no final da fila de mensagens, depois de todas as mensagens pendentes, antes da hora atual. Ele será recebido em handleMessage (Message), no encadeamento anexado a esse manipulador.

HandlerThread é uma classe útil para iniciar um novo thread que possui um looper. O looper pode então ser usado para criar classes de manipuladores

Em alguns cenários, você não pode executar Runnabletarefas no thread da interface do usuário. por exemplo, operações de rede: envie uma mensagem em um soquete, abra uma URL e obtenha conteúdo lendoInputStream

Nestes casos, HandlerThreadé útil. Você pode obter um Looperobjeto HandlerThreade criar um Handleron em HandlerThreadvez de um thread principal.

O código HandlerThread será assim:

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

Consulte a publicação abaixo para obter um código de exemplo:

Android: Brinde em um tópico

Ravindra babu
fonte
5

Noções básicas sobre threads Looper

Um java Thread uma unidade de execução que foi projetada para executar uma tarefa em seu método run () e terminar depois: insira a descrição da imagem aqui

Mas no Android há muitos casos de uso em que precisamos manter um Thread ativo e aguardar entradas / eventos do usuário, por exemplo. Tópico da interface do usuário aka Main Thread.

O encadeamento principal no Android é um encadeamento Java que é iniciado pela JVM no lançamento de um aplicativo e continua em execução até o usuário optar por fechá-lo ou encontrar uma exceção não tratada.

Quando um aplicativo é iniciado, o sistema cria um encadeamento de execução para o aplicativo, chamado "principal". Esse encadeamento é muito importante porque é responsável pelo envio de eventos para os widgets apropriados da interface do usuário, incluindo eventos de desenho.

insira a descrição da imagem aqui

Agora, observe que aqui, embora o segmento principal seja o Java, ele continua ouvindo eventos do usuário e desenha quadros de 60 fps na tela e ainda assim não morre após cada ciclo. como é que é?

A resposta é Looper Class : Looper é uma classe usada para manter um encadeamento ativo e gerenciar uma fila de mensagens para executar tarefas nesse encadeamento.

Os threads por padrão não têm um loop de mensagem associado a eles, mas você pode atribuir um chamando Looper.prepare () no método run e, em seguida, chamar Looper.loop ().

O objetivo do Looper é manter um Thread ativo e aguardar o próximo ciclo do Messageobjeto de entrada para executar a computação que, caso contrário, será destruída após o primeiro ciclo de execução.

Se você quiser aprofundar a maneira como o Looper gerencia a Messagefila de objetos, consulte o código-fonte de Looperclass:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/os/Looper.java

Abaixo está um exemplo de como você pode criar Looper Threade se comunicar com a Activityclasse usandoLocalBroadcast

class LooperThread : Thread() {

    // sendMessage success result on UI
    private fun sendServerResult(result: String) {
        val resultIntent = Intent(ServerService.ACTION)
        resultIntent.putExtra(ServerService.RESULT_CODE, Activity.RESULT_OK)
        resultIntent.putExtra(ServerService.RESULT_VALUE, result)
        LocalBroadcastManager.getInstance(AppController.getAppController()).sendBroadcast(resultIntent)
    }

    override fun run() {
        val looperIsNotPreparedInCurrentThread = Looper.myLooper() == null

        // Prepare Looper if not already prepared
        if (looperIsNotPreparedInCurrentThread) {
            Looper.prepare()
        }

        // Create a handler to handle messaged from Activity
        handler = Handler(Handler.Callback { message ->
            // Messages sent to Looper thread will be visible here
            Log.e(TAG, "Received Message" + message.data.toString())

            //message from Activity
            val result = message.data.getString(MainActivity.BUNDLE_KEY)

            // Send Result Back to activity
            sendServerResult(result)
            true
        })

        // Keep on looping till new messages arrive
        if (looperIsNotPreparedInCurrentThread) {
            Looper.loop()
        }
    }

    //Create and send a new  message to looper
    fun sendMessage(messageToSend: String) {
        //Create and post a new message to handler
        handler!!.sendMessage(createMessage(messageToSend))
    }


    // Bundle Data in message object
    private fun createMessage(messageToSend: String): Message {
        val message = Message()
        val bundle = Bundle()
        bundle.putString(MainActivity.BUNDLE_KEY, messageToSend)
        message.data = bundle
        return message
    }

    companion object {
        var handler: Handler? = null // in Android Handler should be static or leaks might occur
        private val TAG = javaClass.simpleName

    }
}

Uso :

 class MainActivity : AppCompatActivity() {

    private var looperThread: LooperThread? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // start looper thread
        startLooperThread()

        // Send messages to Looper Thread
        sendMessage.setOnClickListener {

            // send random messages to looper thread
            val messageToSend = "" + Math.random()

            // post message
            looperThread!!.sendMessage(messageToSend)

        }   
    }

    override fun onResume() {
        super.onResume()

        //Register to Server Service callback
        val filterServer = IntentFilter(ServerService.ACTION)
        LocalBroadcastManager.getInstance(this).registerReceiver(serverReceiver, filterServer)

    }

    override fun onPause() {
        super.onPause()

        //Stop Server service callbacks
     LocalBroadcastManager.getInstance(this).unregisterReceiver(serverReceiver)
    }


    // Define the callback for what to do when data is received
    private val serverReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val resultCode = intent.getIntExtra(ServerService.RESULT_CODE, Activity.RESULT_CANCELED)
            if (resultCode == Activity.RESULT_OK) {
                val resultValue = intent.getStringExtra(ServerService.RESULT_VALUE)
                Log.e(MainActivity.TAG, "Server result : $resultValue")

                serverOutput.text =
                        (serverOutput.text.toString()
                                + "\n"
                                + "Received : " + resultValue)

                serverScrollView.post( { serverScrollView.fullScroll(View.FOCUS_DOWN) })
            }
        }
    }

    private fun startLooperThread() {

        // create and start a new LooperThread
        looperThread = LooperThread()
        looperThread!!.name = "Main Looper Thread"
        looperThread!!.start()

    }

    companion object {
        val BUNDLE_KEY = "handlerMsgBundle"
        private val TAG = javaClass.simpleName
    }
}

Podemos usar tarefas assíncronas ou serviços de intenção?

  • As tarefas assíncronas são projetadas para executar uma operação curta em segundo plano e fornecer progres e resultados no thread da interface do usuário. As tarefas assíncronas têm limites, como você não pode criar mais de 128 tarefas assíncronas e ThreadPoolExecutorpermitirá apenas até 5 tarefas assíncronas .

  • IntentServicestambém são projetados para executar tarefas em segundo plano por um pouco mais de tempo e você pode usar LocalBroadcastpara se comunicar Activity. Mas os serviços são destruídos após a execução da tarefa. Se você quiser mantê-lo em funcionamento por um longo tempo, você deve fazer o que quiser while(true){...}.

Outros casos de uso significativos para o Looper Thread:

  • Usado para comunicação de soquete bidirecional, em que o servidor continua ouvindo o soquete do cliente e reconheça o retorno

  • Processamento de bitmap em segundo plano. Passe o URL da imagem para o segmento Looper e ele aplicará efeitos de filtro, armazená-lo no local temporário e depois transmitir o caminho temporário da imagem.

Hitesh Sahu
fonte
4

Essa resposta não tem nada a ver com a pergunta, mas o uso de looper e a maneira como as pessoas criaram o manipulador e o looper em TODAS as respostas aqui são práticas ruins (algumas explicações estão corretas), preciso postar isso:

HandlerThread thread = new HandlerThread(threadName);
thread.start();
Looper looper = thread.getLooper();
Handler myHandler = new Handler(looper);

e para uma implementação completa

TacB0sS
fonte
3

Manipular vários itens inativos ou de upload em um Serviço é um exemplo melhor.

Handlere AsnycTasksão frequentemente usados ​​para propagar Eventos / Mensagens entre a interface do usuário (thread) e um thread de trabalho ou para atrasar ações. Portanto, eles estão mais relacionados à interface do usuário.

A Looperlida com tarefas ( Runnables, Futures ) em uma fila relacionada a threads em segundo plano - mesmo sem interação do usuário ou interface do usuário exibida (o aplicativo baixa um arquivo em segundo plano durante uma chamada).

Thorsten
fonte
1

O que é Looper?

DOS DOCS

Looper

LooperClasse usada para executar um loop de mensagem para a thread. Os encadeamentos por padrão não têm um loop de mensagem associado a eles; Para criar um, chame prepare()o thread que deve executar o loop e faça loop()com que ele processe as mensagens até que o loop seja interrompido.

  • A Looperé um loop de manipulação de mensagens:
  • Um personagem importante do Looper é que ele está associado ao encadeamento no qual o Looper é criado
  • A classe Looper mantém um MessageQueue, que contém uma lista de mensagens. Um personagem importante do Looper é que ele está associado ao encadeamento no qual o Looper é criado.
  • O Loopernome é assim porque implementa o loop - pega a próxima tarefa, executa-a, depois pega a próxima e assim por diante. O Handleré chamado manipulador porque alguém não pôde inventar um nome melhor
  • Android Looperé uma classe Java na interface do usuário do Android que, juntamente com a classe Handler, processa eventos da interface do usuário, como cliques em botões, redesenhos de tela e opções de orientação.

Como funciona?

insira a descrição da imagem aqui

Criando Looper

Um segmento recebe um Loopere MessageQueuechamando Looper.prepare()após sua execução. Looper.prepare()identifica o segmento de chamada, cria um Looper e um MessageQueueobjeto e associa o segmento

CÓDIGO DE AMOSTRA

class MyLooperThread extends Thread {

      public Handler mHandler; 

      public void run() { 

          // preparing a looper on current thread  
          Looper.prepare();

          mHandler = new Handler() { 
              public void handleMessage(Message msg) { 
                 // process incoming messages here
                 // this will run in non-ui/background thread
              } 
          }; 

          Looper.loop();
      } 
  }

Para mais informações, verifique abaixo do post

Nilesh Rathod
fonte
excelente, eu entendi a mecânica. obrigado
Serg Burlaka