Відповіді:
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
.
MessageQueue
що a MessageQueue
- це " клас низького рівня, що містить список повідомлень, які має відправити a Looper
. "
Широко відомо, що незаконно оновлювати компоненти інтерфейсу користувача безпосередньо з потоків, відмінних від основного потоку в 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
як показано нижче:
Почнемо з 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.
MessageQueue
: Це клас низького рівня, що містить список повідомлень, які має відправити a Looper
. Повідомлення додаються не безпосередньо до a MessageQueue
, а через Handler
об'єкти, пов'язані з Looper
. [ 3 ]
Looper
: Він циклічно перемикається над a, MessageQueue
який містить повідомлення, які потрібно відправити. Фактичне завдання управління чергою виконується тим, Handler
хто відповідає за обробку (додавання, видалення, відправлення) повідомлень у черзі повідомлень. [ 2 ]
Handler
: Це дозволяє передавати і обробляти Message
і Runnable
об'єкти , пов'язані з потоку MessageQueue
. Кожен екземпляр обробника пов'язаний з одним потоком та чергою повідомлень цього потоку. [ 4 ]
Коли ви створюєте новий Handler
, він прив'язується до черги потоків / повідомлень потоку, який його створює - з цього моменту він буде доставляти повідомлення та виконувані файли до цієї черги повідомлень і виконуватиме їх, коли вони вийдуть із черги повідомлень .
Будь ласка, перегляньте зображення нижче [ 2 ] для кращого розуміння.
Розширюючи відповідь @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() ;