Какова цель Looper и как его использовать?


454

Я новичок в Android. Я хочу знать, что Looperделает класс, а также как его использовать. Я прочитал документацию по классу Android Looper, но не могу понять ее полностью. Я видел это во многих местах, но не мог понять его цель. Может ли кто-нибудь помочь мне, определив цель, Looperа также приведя простой пример, если это возможно?


7
Я только что нашел чрезвычайно подробное и четкое объяснение Looper и его использования в Safari Books Online. К сожалению, я подозреваю, что доступ, если бесплатный в течение ограниченного времени. safaribooksonline.com/library/view/efficient-android-threading/…
Джо Лапп

1
Статьи Android и справочные страницы требуют от вас понимания предыдущей статьи, прежде чем вы сможете понять текущую. Я предлагаю вам прочитать статьи об активности и обслуживании в руководствах Api, а затем прочитать Handler и Looper. Это также помогает, если вы понимаете, что такое поток (не поток Android, а поток вообще ... например, POSIX).
FutureSci

1
Я нашел эту статью полезной: codetheory.in/…
Герман

Ответы:


396

Что такое Лупер?

Looper - это класс, который используется для выполнения сообщений (Runnables) в очереди. Обычные потоки не имеют такой очереди, например, простой поток не имеет никакой очереди. Он выполняется один раз и после завершения выполнения метода поток не запускает другое сообщение (Runnable).

Где мы можем использовать класс Looper?

Если кто-то хочет выполнить несколько сообщений (Runnables), он должен использовать класс Looper, который отвечает за создание очереди в потоке. Например, при написании приложения, которое загружает файлы из Интернета, мы можем использовать класс Looper для помещения файлов для загрузки в очередь.

Как это работает?

Там есть prepare() способ приготовить лупер. Затем вы можете использовать loop()метод для создания цикла сообщений в текущем потоке, и теперь ваш Looper готов выполнять запросы в очереди, пока вы не выйдете из цикла.

Вот код, по которому вы можете подготовить 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();
      }
  }

17
AsyncTask лучше подходит для этой цели и менее сложен, поскольку включает в себя все управление потоками.
Фернандо Гальего

4
Должны иметь аннотации @Override перед методами run () и handleMessage ()
Эндрю Маккензи

5
В документации указано, что вы должны вызвать looper.quit. В вашем коде выше, Looper.loop будет блокироваться на неопределенный срок.
AndroidDev

2
Как выйти из цикла. Я имею в виду, где включить Looper.quit () в приведенном выше примере кода?
Seenu69

6
Я думаю, что было бы лучше использовать HandlerThread, который является удобным классом для потока с петлителем.
Нимрод Даян

287

Вы можете лучше понять, что такое Looper в контексте инфраструктуры GUI. Looper сделан, чтобы сделать 2 вещи.

1) Looper преобразует обычный поток , который завершается при возврате метода run (), во что-то непрерывное, пока не запустится приложение Android , что необходимо в среде графического интерфейса пользователя (Технически, он все еще завершается при возврате метода run (). Но позвольте мне уточнить, что я имею в виду ниже).

2) Looper предоставляет очередь которой выполняются задания, которые также необходимы в среде GUI.

Как вы, возможно, знаете, когда приложение запускается, система создает поток выполнения для приложения, называемый «основным», и приложения Android обычно работают полностью в одном потоке, по умолчанию «основной поток». Но главная тема не какая-то секретная, специальная тема . Это просто нормальный поток, похожий на потоки, которые вы создаете с помощью new Thread()кода, что означает, что он завершается, когда возвращается его метод run ()! Подумайте о приведенном ниже примере.

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

Теперь давайте применим этот простой принцип к приложениям для Android. Что произойдет, если приложение Android будет работать в обычном потоке? Поток с именем "main" или "UI" или чем-то еще, запускает ваше приложение и рисует весь UI. Итак, первый экран отображается для пользователей. И что теперь? Основной поток заканчивается? Нет, не должно. Следует подождать, пока пользователи что-то сделают, верно? Но как мы можем достичь этого поведения? Ну, мы можем попробовать сObject.wait() илиThread.sleep(), Например, основной поток завершает свою начальную работу для отображения первого экрана и спит. Он пробуждается, что означает прерванный, когда выбирается новая работа. Пока все хорошо, но в данный момент нам нужна структура данных в виде очереди для хранения нескольких заданий. Подумайте о случае, когда пользователь касается экрана последовательно, и выполнение задачи занимает больше времени. Таким образом, нам нужна структура данных для хранения заданий, выполняемых в порядке «первым пришел - первым вышел». Кроме того, вы можете себе представить, что реализовать постоянно работающий поток и процесс-задание-при поступлении с использованием прерывания непросто, и это приводит к сложному и часто не поддерживаемому коду. Мы предпочли бы создать новый механизм для этой цели, и это то, что Лупер это все . Официальный документ класса Looperговорит: «По умолчанию потоки не имеют связанной с ними петли сообщений», а Looper - это класс, «используемый для запуска петли сообщений для потока». Теперь вы можете понять, что это значит.

Чтобы сделать вещи более понятными, давайте проверим код, в котором преобразован основной поток. Все это происходит в классе ActivityThread . В его методе main () вы можете найти приведенный ниже код, который превращает обычный основной поток в то, что нам нужно.

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

и Looper.loop()метод зацикливается бесконечно и удаляет сообщение из очереди и обрабатывает по одному:

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

Итак, в основном Looper - это класс, созданный для решения проблемы, возникающей в среде GUI. Но такого рода потребности могут возникнуть и в другой ситуации. На самом деле это довольно известный шаблон для многопоточного приложения, и вы можете узнать о нем больше в « Параллельном программировании на Java » Дуга Ли (особенно было бы полезно, глава 4.1.4 «Рабочие потоки»). Кроме того, вы можете себе представить, что этот тип механизма не уникален в платформе Android, но все графические интерфейсы могут нуждаться в чем-то похожем на это. Вы можете найти почти такой же механизм в Java Swing Framework.


26
Это единственный ответ, который фактически объясняет что-либо о том, почему класс Looper будет когда-либо использоваться. Я не уверен, почему это не самый лучший ответ, три ответа выше ничего не объясняют.
Эндрю Костер

4
@AK. Вот почему я добавил этот ответ, даже если он показался слишком поздно. Я рад, что мой ответ помог вам! :)
김준호

1
@ Эй-мужчины-WhatsUp Да
김준호

1
До прочтения этого я был как "Looper ???" а теперь "О да, давайте обсудим это". Спасибо человек, отличный ответ :)
umerk44

Быстрый вопрос. Вы заявили, что в основном потоке после того, как он вытягивает все элементы пользовательского интерфейса, он помещается в спящий режим. Но допустим, что пользователь взаимодействует с кнопкой на экране, если этот щелчок кнопки даже не помещен в основную очередь, тогда какой-то объект отправит его на правильное действие, тогда основной поток для этого действия проснется и выполнит код для в обратном вызове для этого нажатия кнопки?
CapturedTree

75

Looper позволяет последовательно выполнять задачи в одном потоке. И обработчик определяет те задачи, которые нам нужно выполнить. Это типичный сценарий, который я пытаюсь проиллюстрировать в этом примере:

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

Теперь мы можем использовать обработчик в некоторых других потоках (скажем, в потоке пользовательского интерфейса), чтобы опубликовать задачу в Looper для выполнения.

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

В потоке пользовательского интерфейса у нас есть неявный Looper, который позволяет нам обрабатывать сообщения в потоке пользовательского интерфейса.


это не заблокирует какой-либо процесс пользовательского интерфейса, это правда?
Gumuruh

4
Спасибо за то, что включили пример того, как разместить «вакансии» в очереди
Питер Лиллевольд

Это не объясняет, почему можно использовать этот класс, только как.
Эндрю Костер

33

Android Looper- это оболочка для подключения, MessageQueueкоторая Threadуправляет обработкой очереди. Это выглядит очень загадочно в документации Android, и часто мы можем столкнуться Looperс проблемами доступа к пользовательскому интерфейсу. Если мы не понимаем основ, с этим становится очень трудно справиться.

Вот статья, которая объясняет Looperжизненный цикл, как использовать его и использование LooperвHandler

введите описание изображения здесь

Looper = Thread + MessageQueue


3
Это не объясняет, почему можно использовать этот класс, только как.
Эндрю Костер

14

Определение Looper & Handler:

Looper - это класс, который превращает поток в поток конвейера, а обработчик дает вам механизм для вставки в него задач из любых других потоков.

Подробности:

Таким образом, поток PipeLine - это поток, который может принимать больше задач из других потоков через обработчик.

Looper назван так потому , что он реализует цикл - принимает следующую задачу, выполняет его, затем берет следующую и так далее. Обработчик называется обработчиком, потому что он используется для обработки или принятия следующей задачи каждый раз из любого другого потока и передачи в Looper (Thread или PipeLine Thread).

Пример:

Отличным примером Looper and Handler или PipeLine Thread является загрузка более одного изображения или загрузка их на сервер (Http) по одному в одном потоке вместо запуска нового потока для каждого сетевого вызова в фоновом режиме.

Узнайте больше здесь о Looper и Handler и определении Threading Thread:

Android Guts: Введение в Loopers и Обработчики


7

У Looper есть функцияsynchronized MessageQueue , используемая для обработки сообщений, помещенных в очередь.

Он реализует Threadопределенный шаблон хранения.

Только один Looperза Thread. Основные методы включают в себя prepare(), loop()и quit().

prepare()инициализирует ток Threadкак Looper. prepare()это staticметод, который использует ThreadLocalкласс, как показано ниже.

   public static void prepare(){
       ...
       sThreadLocal.set
       (new Looper());
   }
  1. prepare() должен быть вызван явно перед запуском цикла обработки событий.
  2. loop()запускает цикл обработки событий, который ожидает поступления сообщений в очередь сообщений определенного потока. После получения следующего сообщения loop()метод отправляет сообщение своему целевому обработчику.
  3. quit()выключает цикл событий. Это не завершает цикл, но вместо этого ставит в очередь специальное сообщение

Looperможет быть запрограммирован в Threadнесколько этапов

  1. простираться Thread

  2. Вызов Looper.prepare()для инициализации потока какLooper

  3. Создать один или несколько Handler(ые) для обработки входящих сообщений

  4. Вызовите Looper.loop()для обработки сообщений до тех пор, пока не будет передан цикл quit().

5

Продолжительность жизни Java потока завершена после завершения run()метода. Та же тема не может быть запущена снова.

Looper превращает нормальный Threadв цикл сообщений. Ключевые методы Looper:

void prepare ()

Инициализируйте текущий поток как петлитель. Это дает вам возможность создать обработчики, которые затем ссылаются на этот петлитель, прежде чем фактически запустить цикл. Обязательно вызовите loop () после вызова этого метода и завершите его, вызвав quit ().

void loop ()

Запустите очередь сообщений в этой теме. Обязательно вызовите quit (), чтобы завершить цикл.

void quit()

Выходит из петлителя.

Заставляет метод loop () завершаться, не обрабатывая больше сообщений в очереди сообщений.

В этой статье от Janishar мы подробно объясняем основные понятия.

введите описание изображения здесь

Looperсвязан с темой. Если вам нужно Looperв потоке пользовательского интерфейса, Looper.getMainLooper()вернет связанный поток.

Вы должны Looperбыть связаны с обработчиком .

Looper, HandlerИ HandlerThreadэто способ Андроида решения проблем асинхронного программирования.

После этого Handlerвы можете позвонить ниже API.

post (Runnable r)

Заставляет Runnable r быть добавленным в очередь сообщений. Runnable будет запущен в потоке, к которому прикреплен этот обработчик.

boolean sendMessage (Message msg)

Помещает сообщение в конец очереди сообщений после всех ожидающих сообщений до текущего времени. Он будет получен в handleMessage (Message) в потоке, связанном с этим обработчиком.

HandlerThread - удобный класс для запуска нового потока с петлителем. Цикл затем может быть использован для создания классов обработчиков

В некоторых случаях вы не можете запускать Runnableзадачи в потоке пользовательского интерфейса. Например, сетевые операции: отправьте сообщение в сокет, откройте URL и получите контент, прочитавInputStream

В этих случаях HandlerThreadполезно. Вы можете получить Looperобъект из HandlerThreadи создать Handlerна HandlerThreadвместо основного потока.

Код HandlerThread будет выглядеть так:

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

Обратитесь к сообщению ниже для примера кода:

Android: тост в потоке


5

Понимание нитей Looper

Поток Java - это единица выполнения, которая была разработана для выполнения задачи в своем методе run () и завершается после этого: введите описание изображения здесь

Но в Android есть много случаев, когда нам нужно поддерживать активность потока и ждать, например, пользовательских вводов / событий. Пользовательский интерфейс ака Main Thread.

Основной поток в Android - это поток Java, который сначала запускается JVM при запуске приложения и продолжает работать до тех пор, пока пользователь не решит закрыть его или не встретит необработанное исключение.

Когда приложение запускается, система создает поток выполнения для приложения, называемый «основным». Этот поток очень важен, потому что он отвечает за отправку событий в соответствующие виджеты пользовательского интерфейса, включая события рисования.

введите описание изображения здесь

Теперь обратите внимание на то, что хотя основной поток является потоком Java, он продолжает прослушивать пользовательские события и рисует кадры со скоростью 60 кадров в секунду на экране, но после каждого цикла он не умирает. как это так?

Ответ: Looper Class : Looper - это класс, который используется для поддержания потока в рабочем состоянии и управления очередью сообщений для выполнения задач в этом потоке.

По умолчанию потоки не имеют связанного с ними цикла сообщений, но вы можете назначить его, вызвав Looper.prepare () в методе run, а затем вызвать Looper.loop ().

Цель Looper состоит в том, чтобы поддерживать активность потока и ждать следующего цикла входного Messageобъекта для выполнения вычислений, которые в противном случае будут разрушены после первого цикла выполнения.

Если вы хотите глубже понять, как Looper управляет Messageочередью объектов, вы можете взглянуть на исходный код Looperclass:

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

Ниже приведен пример того, как вы можете создать Looper Threadи общаться с Activityклассом, используяLocalBroadcast

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

    }
}

Использование :

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

Можем ли мы вместо этого использовать задачу Async или Intent Services?

  • Асинхронные задачи предназначены для выполнения коротких операций в фоновом режиме и обеспечения прогресса и результатов в потоке пользовательского интерфейса. Асинхронные задачи имеют ограничения, например, вы не можете создавать более 128 асинхронных задач и ThreadPoolExecutorразрешить только до 5 асинхронных задач .

  • IntentServicesтакже предназначены для выполнения фоновых задач на более длительный срок, и вы можете использовать LocalBroadcastдля общения с Activity. Но сервисы разрушаются после выполнения задачи. Если вы хотите, чтобы он работал долго, вам нужно делать такие хаки while(true){...}.

Другие значимые варианты использования для Looper Thread:

  • Используется для двусторонней связи через сокет, когда сервер продолжает прослушивать сокет клиента и записывает подтверждение

  • Обработка растровых изображений в фоновом режиме. Передайте URL-адрес изображения в поток Looper, и он применит эффекты фильтра и сохранит его в определенном месте, а затем передаст временный путь изображения.


4

Этот ответ не имеет ничего общего с вопросом, но использование «петлителя» и способа, которым люди создавали обработчик и петлитель во ВСЕХ ответах здесь, - просто плохая практика (хотя некоторые объяснения верны), я должен опубликовать это:

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

и для полной реализации


3

Лучшим примером является обработка нескольких загруженных или загруженных элементов в Сервисе .

Handlerи AsnycTaskчасто используются для распространения событий / сообщений между пользовательским интерфейсом (потоком) и рабочим потоком или для задержки действий. Таким образом, они больше связаны с пользовательским интерфейсом.

A Looperобрабатывает задачи ( Runnables, Futures ) в очереди, связанной с потоками, в фоновом режиме - даже без взаимодействия с пользователем или отображаемого пользовательского интерфейса (приложение загружает файл в фоновом режиме во время вызова).


1

Что такое Лупер?

ОТ ДОКУМЕНТОВ

Looper

LooperКласс, используемый для запуска цикла сообщений для thread. Потоки по умолчанию не имеют связанного с ними цикла сообщений; чтобы создать его, вызовите prepare()поток, который должен запустить цикл, а затем loop()попросите его обрабатывать сообщения до тех пор, пока цикл не будет остановлен.

  • A Looper- цикл обработки сообщений:
  • Важным символом Looper является то, что он связан с потоком, в котором создается Looper
  • Класс Looper поддерживает a MessageQueue, который содержит список сообщений. Важным символом Looper является то, что он связан с потоком, в котором создается Looper.
  • Он Looperназван так, потому что он реализует цикл - берет следующую задачу, выполняет ее, затем берет следующую и так далее. Это Handlerназывается обработчиком, потому что кто-то не может придумать лучшего имени
  • Android Looper- это класс Java в пользовательском интерфейсе Android, который вместе с классом Handler обрабатывает события пользовательского интерфейса, такие как нажатия кнопок, перерисовка экрана и переключение ориентации.

Как это работает?

введите описание изображения здесь

Создание Looper

Поток получает Looperи MessageQueueвызывая Looper.prepare()после своего запуска. Looper.prepare()идентифицирует вызывающий поток, создает лупер и MessageQueueобъект и связывает поток

ОБРАЗЕЦ КОДА

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

Для получения дополнительной информации проверьте ниже пост


отлично, я понял механику. спасибо
Сергей Бурлака
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.