Яке призначення Looper і як ним користуватися?


454

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


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

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

1
Я вважаю цю статтю корисною: codetheory.in/…
Герман

Відповіді:


396

Що таке Looper?

Looper - це клас, який використовується для виконання Повідомлень (Runnables) у черзі. Звичайні нитки не мають такої черги, наприклад, у простої нитки немає жодної черги. Він виконується один раз, і після завершення виконання методу, потік не буде запускати інше повідомлення (Runnable).

Де ми можемо використовувати клас Looper?

Якщо хтось хоче виконати кілька повідомлень (Runnables), тоді він повинен використовувати клас Looper, який відповідає за створення черги в потоці. Наприклад, під час написання програми, яка завантажує файли з Інтернету, ми можемо використовувати клас Looper для розміщення файлів для завантаження у чергу.

Як це працює?

Існує prepare()спосіб приготування петельника. Тоді ви можете використовувати loop()метод для створення циклу повідомлень у поточному потоці, і тепер ваш 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. Петля зроблена для того, щоб робити 2 речі.

1) Looper перетворює звичайний потік , який припиняється, коли його метод run () повертається, у щось, що працює постійно, поки не запущено додаток Android , що потрібно в рамках GUI (Технічно він все ще припиняється при поверненні методу 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" або що інше запускає вашу програму, і малює весь інтерфейс. Отже, перший екран відображається користувачам. І що тепер? Основна нитка закінчується? Ні, не повинно. Слід почекати, поки користувачі щось роблять, правда? Але як ми можемо досягти такої поведінки? Що ж, ми можемо спробувати з Object.wait()абоThread.sleep(). Наприклад, основний потік закінчує своє початкове завдання для відображення першого екрана і спить. Він прокидається, що означає перервану, коли нова робота, яку потрібно виконати. Поки що добре, але в цей момент нам потрібна структура даних, що нагадує чергу, для проведення кількох завдань. Подумайте про випадок, коли користувач послідовно торкається екрана, і для виконання завдання потрібно більше часу. Отже, нам потрібно мати структуру даних, щоб утримувати завдання, які можна виконати першим способом. Крім того, ви можете собі уявити, що реалізувати постійно працюючу і оброблювану роботу при надходженні потоку за допомогою переривання непросто, і це призводить до складного і часто нездійсненного коду. Ми скоріше створимо новий механізм для такої мети, і саме це і є Looper . Офіційний документ класу Looperговорить: "За замовчуванням для потоків повідомлень не пов’язана цикла повідомлень", а Looper - це клас, "який використовується для запуску циклу повідомлень для потоку". Тепер ви можете зрозуміти, що це означає.

Щоб зробити речі більш зрозумілими, давайте перевіримо код, куди перетворюється головний потік. Все це відбувається в класі ActivityThread . У його основному () методі ви можете знайти код нижче, який перетворює звичайну основну нитку в щось те, що нам потрібно.

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, але всі GUI-системи можуть знадобитися дещо подібними до цього. Ви можете знайти майже такий же механізм у Java Swing Framework.


26
Це єдина відповідь, яка насправді пояснює що-небудь про те, чому б коли-небудь використовувався клас Looper. Я не впевнений, чому це не найкраща відповідь, три вищевикладені відповіді нічого не пояснюють.
Андрій Костер

4
@AK. Ось чому я додав цю відповідь, навіть здавалося, що пізно. Я радий, що моя відповідь допомогла тобі! :)
김준호

1
@ Hey-men-whatsup Так
김준호

1
Перш ніж прочитати це, я був схожий на "Looper ???" а тепер "О так, давайте обговоримо це". Спасибі, чудова відповідь :)
umerk44

Швидке запитання. Ви заявили, що в основній нитці після того, як вона витягне всі елементи інтерфейсу, вона переводиться у режим сну. Але, скажімо, користувач взаємодіє з кнопкою на екрані, чи не натискання цієї кнопки навіть поставлено в основну чергу, тоді якийсь об’єкт відправить його до правильної активності, тоді основний потік цієї активності прокидається, і він виконає код для зворотного дзвінка для цієї кнопки?
Захоплене

76

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

Тепер ми можемо використовувати обробник в деяких інших потоках (скажімо, ui нитка), щоб розмістити завдання на Looper для виконання.

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

У потоці користувальницького інтерфейсу у нас є неявна Looper, яка дозволяє нам обробляти повідомлення на потоці ui.


це не заблокує жоден процес UI, це правда?
gumuruh

4
Дякуємо, що включили зразок того, як розмістити "завдання" у черзі
Пітер Ліллевольд

Це не пояснює, чому можна було б використовувати цей клас, а лише як.
Андрій Костер

33

Android Looperє оболонкою для приєднання MessageQueueдо Threadі управляє обробкою черги. У документації на Android це виглядає дуже виразно, і багато разів ми можемо зіткнутися з Looperвідповідними проблемами доступу до інтерфейсу користувача. Якщо ми не розуміємо основ, з цим стає дуже важко впоратися.

Ось стаття , яка пояснює , Looperжиттєвий цикл, як використовувати його і використання LooperвHandler

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

Looper = Нитка + Черга повідомлення


3
Це не пояснює, чому можна було б використовувати цей клас, а лише як.
Андрій Костер

14

Визначення Looper & Handler:

Looper - це клас, який перетворює нитку в трубопровідну нитку і обробник дає вам механізм для просування завдань у неї з будь-яких інших потоків.

Деталі:

Отже, нитка PipeLine - це нитка, яка може приймати більше завдань з інших потоків через обробник.

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

Приклад:

Дуже досконалим прикладом програми Looper and Handler або PipeLine Thread є завантаження декількох зображень або завантаження їх на сервер (Http) по одному в одну нитку, а не запуск нової теми для кожного мережевого дзвінка у фоновому режимі.

Докладніше про Looper and Handler та визначення потоку трубопроводу читайте тут:

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

Ініціалізуйте поточну нитку як петлю. Це дає вам можливість створити обробники, які потім посилаються на цей цикл, перш ніж насправді починати цикл. Не забудьте викликати цикл () після виклику цього методу та закінчити його викликом quit ().

void loop ()

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

void quit()

Виходить із петлі.

Викликає припинення методу loop (), не обробляючи більше повідомлень у черзі повідомлень.

Ця стаття Джанішара про розум розглядає основні поняття приємно.

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

Looperасоціюється з Ниткою. Якщо вам потрібен Looperпотік інтерфейсу, Looper.getMainLooper()поверне пов'язаний потік.

Вам потрібно Looperасоціюватися з обробником .

Looper, Handlerі HandlerThreadє способом вирішення проблем асинхронного програмування Android.

Після цього Handlerви можете зателефонувати нижче за API.

post (Runnable r)

Викликає додавання Runnable r до черги повідомлень. Виконання буде виконуватися на нитці, до якої прикріплений цей обробник.

boolean sendMessage (Message msg)

Натискає на кінець черги повідомлень після всіх відкладених повідомлень до поточного часу. Він буде отриманий у handleMessage (Повідомленні), у потоці, доданому до цього обробника.

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

Розуміння петлевих ниток

Ява Нитка - це одиниця виконання, яка була розроблена для виконання завдання методом run () та закінчується після цього: введіть тут опис зображення

Але в Android є багато випадків використання, коли нам потрібно тримати живу тему та чекати, коли користувачі вводять / вводять події, наприклад. Потік інтерфейсу ака Main Thread.

Головний потік в Android - це потік Java, який вперше запускається JVM при запуску програми і продовжує працювати, поки користувач не вирішить його закрити або не зіткнеться з необробленим винятком.

Коли програма запускається, система створює потік виконання для програми, який називається "основним". Цей потік дуже важливий, оскільки він відповідає за відправлення подій у відповідні віджети користувальницького інтерфейсу, включаючи події малювання.

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

Тепер зауважте, хоча головна тема - це тема Java, але вона продовжує слухати події користувача і малювати 60 кадрів в секунду на екрані, і все одно вона не буде вмирати після кожного циклу. як це так?

Відповідь - клас Looper : Looper - це клас, який використовується для збереження потоку в живих і керування чергою повідомлень для виконання завдань з цього потоку.

Нитки за замовчуванням не мають циклу повідомлень, пов’язаного з ними, але ви можете призначити його, зателефонувавши Looper.prepare () у методі run, а потім зателефонувавши Looper.loop ().

Призначення Looper полягає в тому, щоб зберегти Thread живою і чекати наступного циклу вхідного 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 або послуги намірів?

  • Завдання Async призначені для виконання короткої операції у фоновому режимі та надання прогресу та результатів у потоці інтерфейсу користувача. У завдань асинхронізації є такі обмеження, як ви не можете створити більше 128 завдань асинхронізації, і ThreadPoolExecutorбуде дозволено лише до 5 завдань асинхронізації .

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

Інші значущі випадки використання петлі Looper:

  • Використовується для двостороннього зв'язку сокета, коли сервер продовжує слухати клієнтський сокет і записувати підтвердження

  • Обробка растрових зображень у фоновому режимі. Передайте URL-адресу зображення до потоку Looper, і він застосує ефекти фільтру та збереже їх у темному місці та потім транслюватиме темп шляху зображення.


4

Ця відповідь не має нічого спільного з питанням, але використання петлі та способів, як люди створили обробник і петлю в ВСІХ відповідях тут, є звичайною поганою практикою (хоча деякі пояснення є правильними), я повинен опублікувати це:

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

і для повного впровадження


3

Оптимальним прикладом є обробка декількох записів або завантаження елементів у службі .

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

А Looperручки завдання ( Runnables, Futures ) в черзі , пов'язані ниток в фоновому режимі - навіть без будь - якої взаємодії з користувачем або відображені UI (завантаження додатків файл у фоновому режимі під час розмови).


1

Що таке Looper?

ВІД ДОКС

Looper

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

  • A Looper- це цикл обробки повідомлень:
  • Важливим характером Looper є те, що він пов'язаний з ниткою, в якій створюється Looper
  • Клас Looper підтримує a MessageQueue, який містить список повідомлень. Важливим характером Looper є те, що він пов'язаний з ниткою, в якій створюється Looper.
  • Назва Looperназивається так, тому що він реалізує цикл - приймає наступне завдання, виконує його, потім бере наступне і так далі. Це Handlerназивається обробником, тому що хтось не міг придумати кращого імені
  • Android Looper- це клас Java в інтерфейсі користувача Android, який разом з класом Handler обробляє події інтерфейсу, такі як натискання кнопок, перемальовування екрана та перемикачі орієнтації.

Як це працює?

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

Створення Looper

Нитка отримує a Looperі MessageQueueзателефонувавши Looper.prepare()після її запуску. Looper.prepare()ідентифікує викликову нитку, створює Looper та 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.