Який взаємозв’язок між Looper, Handler та MessageQueue в Android?


95

Я перевірив офіційний Android документації / керівництво для Looper, Handlerі MessageQueue. Але я не зміг його отримати. Я новачок в android і дуже заплутався з цими поняттями.

Відповіді:


103

A Looper- це цикл обробки повідомлень: він зчитує та обробляє елементи з a MessageQueue. LooperКлас, як правило , використовується в поєднанні з HandlerThread(підклас Thread).

A Handler- це клас утиліти, який полегшує взаємодію з a Looper- головним чином, розміщуючи повідомлення та Runnableоб'єкти в потоці MessageQueue. Коли a Handlerстворюється, він прив'язується до певного Looper(і пов'язаного з ним потоку та черги повідомлень).

Зазвичай ти створюєш і запускаєш HandlerThread, а потім створюєш Handlerоб’єкт (або об’єкти), за допомогою якого інші потоки можуть взаємодіяти з HandlerThreadекземпляром. HandlerПовинні бути створені у час роботи на HandlerThread, хоча після створення не існує ніяких обмежень на те , що потоки можуть використовувати Handler«и методи планування ( post(Runnable)і т.д.)

Основний потік (він же потік інтерфейсу користувача) у додатку Android налаштовується як обробник потоку перед створенням вашого екземпляра програми.

Крім класу Docs, є хороша дискусія все це тут .

PS Усі класи, згадані вище, є в комплекті android.os.


@Ted Hopp - Чи відрізняється черга повідомлень Looper від черги повідомлень Thread?
CopsOnRoad

2
@Jack - Це одне і те ж. Документи Android API вказують,MessageQueue що a MessageQueue- це " клас низького рівня, що містить список повідомлень, які має відправити a Looper. "
Тед Хопп,

95

Широко відомо, що незаконно оновлювати компоненти інтерфейсу користувача безпосередньо з потоків, відмінних від основного потоку в android. Цей документ для Android ( Обробка дорогих операцій в потоці інтерфейсу користувача ) пропонує кроки, яких слід виконати, якщо нам потрібно запустити окремий потік, щоб виконати якусь дорогу роботу та оновити інтерфейс після його завершення. Ідея полягає в тому, щоб створити об'єкт Handler, пов'язаний з основним потоком , і опублікувати до нього Runnable у відповідний час. Це Runnableбуде викликано в основному потоці . Цей механізм реалізований з класами Looper та Handler .

LooperКлас підтримує MessageQueue , який містить список повідомлень . Важливим символом Looper є те, що він пов'язаний з потоком, у якому Looperстворено . Ця асоціація зберігається назавжди і не може бути порушена або змінена. Також зверніть увагу на те , що потік не може бути пов'язаний з більш ніж однієї Looper. Для того, щоб гарантувати цю асоціацію, Looperвона зберігається у локальному сховищі потоків і не може бути створена безпосередньо за допомогою конструктора. Єдиний спосіб його створення є викликом підготувати статичний метод Looper. метод підготовки спочатку перевіряє ThreadLocalпоточного потоку, щоб переконатися, що з потоком вже не пов’язаний Looper. Після обстеження Looperстворюється і зберігається нове ThreadLocal. Підготувавши Looper, ми можемо викликати на ньому метод циклу, щоб перевірити наявність нових повідомлень і мати Handlerсправу з ними.

Як випливає з назви, Handlerклас головним чином відповідає за обробку (додавання, видалення, відправлення) повідомлень поточного потоку MessageQueue. HandlerПримірник також пов'язаний з різьбленням. Зв'язування між оброблювачем і нитки досягається з допомогою Looperі MessageQueue. HandlerБуде завжди прив'язаний доLooper , і згодом зв'язується з ниткою , пов'язаної з Looper. На відміну від цього Looper, кілька екземплярів обробника можуть бути прив'язані до одного потоку. Кожного разу, коли ми викликаємо post або будь-які подібні методи Handler, нове повідомлення додається до пов'язаного MessageQueue. Для цільового поля повідомлення встановлено поточний Handlerекземпляр. КолиLooperотримавши це повідомлення, воно викликає dispatchMessage у цільовому полі повідомлення, так що повідомлення повертається до екземпляра обробника, який потрібно обробити, але у правильному потоці. Відносини між Looper, Handlerі MessageQueueяк показано нижче:

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


5
Дякую! але який сенс, якщо обробник спочатку розміщує повідомлення в черзі повідомлень, а потім обробляє повідомлення з тієї самої черги? чому він просто не обробляє повідомлення безпосередньо?
Blake

4
@Blake b / c, ви публікуєте повідомлення з одного потоку (не петлевий потік), але обробляєте повідомлення в іншому потоці (нитка
петлі

Набагато краще, ніж те, що задокументовано на developer.android.com - але було б непогано побачити код для діаграми, яку ви надали.
tfmontague

@numansalati - Чи не може Хендлер розміщувати повідомлення з нитки петлі?
CopsOnRoad

78

Почнемо з Looper. Ви можете легше зрозуміти взаємозв'язок між Looper, Handler та MessageQueue, коли зрозумієте, що таке Looper. Також ви можете краще зрозуміти, що таке Looper у контексті графічного інтерфейсу. Looper змушений робити 2 речі.

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

2) Looper надає чергу, де завдання розміщуються в черзі, що також потрібно в рамках графічного інтерфейсу.

Як ви вже могли знати, під час запуску програми система створює для програми потік виконання, який називається «основним», а додатки 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 - це клас, "який використовується для запуску циклу повідомлень для потоку". Тепер ви можете зрозуміти, що це означає.

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

Тоді, що таке Хендлер? Якщо є черга, тоді повинен бути метод, який повинен дозволити нам поставити нове завдання в чергу, чи не так? Це те, що робить Хендлер. Ми можемо поставити нове завдання в чергу (MessageQueue) за допомогою різних post(Runnable r)методів. Це воно. Це все про Looper, Handler та MessageQueue.

Моє останнє слово, так що в основному Looper - це клас, який створений для вирішення проблеми, яка виникає в рамках графічного інтерфейсу. Але подібні потреби можуть траплятися і в інших ситуаціях. Насправді це досить відомий зразок для застосування в декількох потоках, і ви можете дізнатись більше про нього в "Одночасному програмуванні на Java" Дуга Лі (особливо, розділ 4.1.4 "Робочі нитки" буде корисним). Крім того, ви можете собі уявити, що такий тип механізму не є унікальним в рамках Android, але всі фреймворки графічного інтерфейсу можуть потребувати дещо подібного до цього. Ви можете знайти майже такий самий механізм у фреймворку Java Swing.


4
Краща відповідь. Дізнався більше з цього детального пояснення. Цікаво, чи є якась публікація в блозі, яка йде більш докладно.
capt.swag

Чи можна додавати повідомлення до MessageQueue без використання обробника?
CopsOnRoad

@CopsOnRoad ні, їх не можна додати безпосередньо.
Фейсал Насір

Зробив мій день ... багато любові тобі :)
Рахул Матте

26

MessageQueue: Це клас низького рівня, що містить список повідомлень, які має відправити a Looper. Повідомлення додаються не безпосередньо до a MessageQueue, а через Handlerоб'єкти, пов'язані з Looper. [ 3 ]

Looper: Він циклічно перемикається над a, MessageQueueякий містить повідомлення, які потрібно відправити. Фактичне завдання управління чергою виконується тим, Handlerхто відповідає за обробку (додавання, видалення, відправлення) повідомлень у черзі повідомлень. [ 2 ]

Handler: Це дозволяє передавати і обробляти Messageі Runnableоб'єкти , пов'язані з потоку MessageQueue. Кожен екземпляр обробника пов'язаний з одним потоком та чергою повідомлень цього потоку. [ 4 ]

Коли ви створюєте новий Handler, він прив'язується до черги потоків / повідомлень потоку, який його створює - з цього моменту він буде доставляти повідомлення та виконувані файли до цієї черги повідомлень і виконуватиме їх, коли вони вийдуть із черги повідомлень .

Будь ласка, перегляньте зображення нижче [ 2 ] для кращого розуміння.

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


0

Розширюючи відповідь @K_Anas, на прикладі, як зазначалося

Широко відомо, що незаконно оновлювати компоненти інтерфейсу користувача безпосередньо з потоків, відмінних від основного потоку в android.

наприклад, якщо ви намагаєтесь оновити інтерфейс користувача за допомогою Thread.

    int count = 0;
    new Thread(new Runnable(){
        @Override
        public void run() {
            try {
                while(true) {
                    sleep(1000);
                    count++;
                    textView.setText(String.valueOf(count));
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


   ).start();

програма вийде з ладу за винятком.

android.view.ViewRoot $ CalledFromWrongThreadException: Тільки оригінальний потік, який створив ієрархію подання, може торкатися його поглядів.

іншими словами вам потрібно використовувати те, Handlerщо зберігає посилання на MainLooper тобто, Main Threadабо UI Threadі передавати завдання як Runnable.

  Handler handler = new Handler(getApplicationContext().getMainLooper);
        int count = 0;
        new Thread(new Runnable(){
            @Override
            public void run() {
                try {
                    while(true) {
                        sleep(1000);
                        count++;
                        handler.post(new Runnable() {
                           @Override
                           public void run() {
                                 textView.setText(String.valueOf(count));
                           }
                         });

                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    ).start() ;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.