Что такое handler андроид

Класс Handler

Класс android.os.Handler является дальнейшим развитием потоков, упрощающий код. Handler может использоваться для планирования выполнения кода в некоторый момент в будущем. Также класс может использоваться для передачи кода, который должен выполняться в другом программном потоке.

Рассмотрим максимально простой пример для знакомства

Запустите пример на эмуляторе и через некоторое время закройте его через кнопку Назад или Домой. При этом смотрите на логи на вкладе Android Monitor. Вы увидите, что приложение по-прежнему печатает текст типа после секундной задержки после запуска.

Разберёмся, что происходит и как следует читать код.

Мы объявили новый объект Handler и булевую переменную, которая по умолчанию имеет значение false — по ней будем отслеживать состояние приложения. В третьей переменной мы сохраним текущее время в момент запуска.

Приложение запустилось, сразу же запоминаем время через currentTimeMillis() — это опорная точка отсчёта.

Основная логика заключена в условии if — тут пишется то, что мы хотим поместить в новый поток. Не страшно, если не понятно, как это всё работает. Просто уясните принцип. Мы иницилизируем объект mHandler и сразу начинаем его настраивать. Переопредляем его главный метод handleMessage() и вызываем метод суперкласса. Это ничем не отличается от вызова onCreate() активности, мы не раз такое делали.

Блок if позволяет управлять кодом для потока. Сам запуск мы сделаем позже. А в самом блоке мы снова получаем текущее время и сравниваем с самой первым временем, полученным во время запуска. Для удобства вычисления идут в секундах. Результат выводится в лог. Метод sendEmptyMessageDelayed() сообщает системе, что мы хотим повторять код в handleMessage() раз в секунду.

После инициализации и настройки mHandler мы присваиваем значение true переменной gameOn, показывая готовность к запуску кода из блока if.

Последняя строка sendEmptyMessage() запускает поток.

Можно использовать более сложные приёмы запуска потока, но пока этого достаточно для понимания.

Периодическое выполнение задачи

При сложных вычислениях может понадобиться очередь Runnable-объектов. Помещая объект в очередь, вы можете задать время его запуска. Для демонстрации использования обработчика потока напишем программу, запускающую фоновый процесс, который будет каждые 200 миллисекунд получать текущее время и обновлять текст. Нам понадобится кнопка Пуск и две текстовые метки, в которых будет отображаться время и количество нажатий кнопки:

На экране будет отображаться время и одновременно мы можем нажимать на кнопку. Эти действия не мешают друг другу, так как работают в разных потоках.

Кроме метода postDelayed() вы можете использовать метод postAtTime():

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

Самый простой способ помещения объекта в очередь — метод post(), когда указывается только помещаемый объект без указания времени выполнения объекта:

Пример с индикатором прогресса

Всё, что вам нужно — создать экземпляр класса Handler в классе активности. Поток будет работать с объектом Handler, который в свою очередь, будет обновлять шкалу индикатора в основном потоке активности.

Чтобы послать сообщение в объект Handler, сначала необходимо вызвать метод obtainMessage(), чтобы извлечь объект Message из глобального пула сообщений.

Для вставки сообщения в очередь сообщений объекта Handler существует несколько методов:

  • sendMessage() — помещает сообщение в очередь немедленно (в конец очереди)
  • sendMessageAtFrontofQueue() — помещает сообщение в очередь немедленно и, кроме того, помещает это сообщение впереди очереди (по умолчанию оно ставится в конец очереди), таким образом ваше сообщение берет приоритет над всеми другими
  • sendMessageAtTime() — помещает сообщение в очередь в установленное время в миллисекундах
  • sendMessageDeiayed() — помещает сообщение в очередь после задержки, выраженной в миллисекундах
Читайте также:  Chartboost что за папка android

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

Для примера создадим приложение с ProgressBar, который будет отображать ход выполнения длительной задачи (это будет простой цикл с приостановкой потока на 1 секунду в каждой итерации цикла) и обновлять степень завершения этой задачи через объект Handler в классе активности.

Splash-screen

Очень часто программисты используют Handler для реализации окна приветствия, которое автоматически закрывается и следом запускается основная активность игры или приложения.

Источник

Находки программиста

Решения конкретных задач программирования. Java, Android, JavaScript, Flex и прочее. Настройка софта под Linux, методики разработки и просто размышления.

пятница, 24 января 2014 г.

Handler — маленький помошник Android разработчика

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

В кратце android.os.Handler это абстракция позволяющая «выполнять» в указаной очередности другие абстракции — события. А что же такое событие? На реализации событиями являются android.os.Message и обычные Runnable.

Случай №1:

Случай №2. Реализация вызова onPostExecute в AsyncTask:

private Result postResult(Result result) <
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult (this, result));
message.sendToTarget();
return result;
>
private void finish(Result result) <
if (isCancelled()) <
onCancelled(result);
> else <
onPostExecute(result);
>
>
private static class InternalHandler extends Handler <
public void handleMessage(Message msg) <
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) <
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
.
>
>
>

И так. Как видно в обоих случаях используется Handler как средство взаимодействие между потоками. Т.е. посылая в Handler сообщение оно будет передано на обработку в поток Handler’а. По умолчанию используется UiThread. С этого следует что Handler закреплен за потоком. Так как Handler’ов может быть много, а UiThread у нас один — прошу любить и жаловать — Looper.

Looper является промежуточным звеном между потоком выполнения и Handler’ом. Из другого потока Looper просто создать и запустить не получиться, для такой цели используется HandlerThread — расширенная версия стандартного потока.

Пример обработки сообщений:

Handle handler = new Handler() <
public void handleMessage(Message msg) <
switch (msg.what) <
.
>
>
>;
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);

После чего сообщение будет отправлено на обработку в метод handleMessage.

Кстати да. сообщения — по сути это структура описывающая и хранящая контекстноть события. Для этого есть целых 4 поля класса: arg1, arg2, what и obj. Первые 3 это int, последний — объект.

Все сообщения «стоят» в очереди в android.os.MessageQueue который принимает решение какое сообщение и на какой target необходимо передать для обработки.

Если открыть реализацию android.os.ActivityThread, то мы увидим самую что не есть стандартную точку входа — main. Которая запускает обработку системных событий предварительно подготовив Looper.

public static void main(String[] args) <
// .

Looper.prepareMainLooper();

if (sMainThreadHandler == null) <
sMainThreadHandler = thread.getHandler();
>
// .

Looper.loop();

throw new RuntimeException(«Main thread loop unexpectedly exited»);
>

И так.

И реализация:

class HandlerExample <
private Handler mWorkHandler;
private HandlerThread mHandlerThread;
public enum State <
stopped,
starting,
started,
stopping
>
private State mState;
private final Runnable mActionNetworkCheck = new Runnable() <
@Override
public void run() <
// проверяем данные на сервере
if (isItWork()) <
mWorkHandler.postDelayed(mActionNetworkCheck, 10000);
>
>
>;
private final Runnable mActionWifiScan = new Runnable() <
@Override
public void run() <
// работа со сканом сетей
if (isItWork()) <
mWorkHandler.postDelayed(mActionWifiScan, 30000);
>
>
>;
private final Runnable mActionUsbIo = new Runnable() <
@Override
public void run() <
// работа по usb хосту
if (isItWork()) <
mWorkHandler.postDelayed(mActionUsbIo, 4000);
>
>
>;

private synchronized boolean isItWork() <
return mState == State.started;
>
public void startup() <
synchronized (this) <
if (mState == State.stopped) <
mState = State.starting;
> else <
throw new IllegalStateException(«Example must be stopped for start»);
>
>

mHandlerThread = new HandlerThread(«WorkHandlerThread») <
@Override
protected void onLooperPrepared() <
mWorkHandler = new Handler(getLooper());
synchronized (HandlerExample.this) <
mState = HandlerExample.State.started;
>
mWorkHandler.post(mActionNetworkCheck);
mWorkHandler.post(mActionWifiScan);
mWorkHandler.post(mActionUsbIo);
>
>;
mHandlerThread.start();
>
public void shutdown() <
synchronized (this) <
if (mState == State.started) <
mState = State.stopping;
> else <
throw new IllegalStateException(«Example must be started for shutdown»);
>
>

Читайте также:  Dark mode opera android

mHandlerThread.quit();
try <
mHandlerThread.join();
> catch (InterruptedException e) <
throw new RuntimeException(e);
>
mWorkHandler = null;
mHandlerThread = null;
synchronized (this) <
mState = State.stopped;
>
>
>

Выводы: организовать работу многих событий можно и в один поток если при этом временные рамки не жесткие. Конечно можно вспомнить о java.util.Timer и TimerTask’e, но ИМХО Handler более удобный.

Спасибо всем кто дочитал до этой строчки!

Источник

Многопоточность в Android. Looper, Handler, HandlerThread. Часть 1.

Что вы знаете о многопоточности в андроид? Вы скажете: «Я могу использовать AsynchTask для выполнения задач в бэкграунде». Отлично, это популярный ответ, но что ещё? «О, я слышал что-то о Handler’ах, и даже как то приходилось их использовать для вывода Toast’ов или для выполнения задач с задержкой…» — добавите Вы. Это уже гораздо лучше, и в этой статье мы рассмотрим как и для чего используют многопоточность в Android.

Для начала давайте взглянем на хорошо известный нам класс AsyncTask, я уверен что каждый андроид-разработчик использовал его. Прежде всего, стоит заметить, что есть отличное описание этого класса в официальной документации. Это хороший и удобный класс для управления задачами в фоне, он подойдёт для выполнения простых задач, если вы не хотите тратить впустую время на изучение того как можно эффективно управлять потоками в андроид. Самая главная вещь о которой вы должны знать – только метод doInBackground выполняется в другом потоке! Остальные его методы выполняются в главном UI потоке. Рассмотрим пример типичного использования AsyncTask:

Далее в интерфейсе будем использовать следующий тривиальный макет с прогресбаром для всех наших тестов.

В макете бесконечно отображается индикация вращения прогресбара. Если прогресбар застынет – это будет что в главном UI потоке происходит выполнение тяжелой работы.

Здесь мы используем AsyncTask, потому что приложению потребуется некоторое время для получения ответа от сервера и мы не хотим что бы нам интерфейс завис в ожидании этого ответа, поэтому мы поручаем выполнить сетевую задачу другому потоку. Есть много постов о том, почему использование AsyncTask – это плохая затея(например: если это внутренний класс вашего активити/фрагмента, то он будет удерживать внутреннюю ссылку на него, что является плохой практикой, потому что активити/фрагмент могут быть уничтожены при смене конфигурации, но они будут висеть в памяти пока работает фоновый поток; если объявлен отдельным статическим внутренним классом и вы используете ссылку на Context для обновления вьюшек, вы должны всегда проверять их на null).

Все задачи в главном потоке выполняются последовательно, делая тем самым код более предсказуемым – вы не рискуете попасть в ситуацию изменения данных несколькими потоками. Значит если какая-то задача работает слишком долго, Вы получите неотвечающее приложени, или ANR(Application Not Responding) ошибку. AsyncTask является одноразовым решением. Класс не может быть повторно использован при повторном вызове execute метода на одном экземпляре – вы непременно должны создать новый экземпляр AsyncTask для новой работы.

Любопытно то, что если вы попытаетесь показать Toast из метода doInBackground, то получите ошибку, содержащую что то вроде:

Из-за чего же мы получили ошибку? Ответ прост: потому что Toast является частью интерфейса и может быть показан только из UI потока, а правильный ответ: потому что он может быть показан только из потока с Looper’ом! Вы спросите, что такое Looper?

Хорошо, пришло время копнуть глубже. AsyncTask отличный класс, но что если его функциональности недостаточно для ваших действий? Если мы заглянем под капот AsyncTask, то обнаружим устройство с крепко связанными компонентами: Handler, Runnable и Thread. Каждый из вас знаком с потоками в Java, но в андройде вы обнаружите ещё один класс HandlerThread, произошедший от Thread. Единственное существенное отличие между HandlerThread и Thread заключается в том что первый содержит внутри себя Looper, Thread и MessageQueue. Looper трудится, обслуживая MessageQueue для текущего потока. MessageQueue это очередь которая содержит в себе задачи, называющиеся сообщениями, которые нужно обработать. Looper перемещается по этой очереди и отправляет сообщения в соответствующие обработчики для выполнения. Любой поток может иметь единственный уникальлный Looper, это ограничение достигается с помощью концепции ThreadLocal хранилища. Связка Looper+MessageQueue выглядит как конвейер с коробками. Задачи в очередь помещают Handler‘ы.

Читайте также:  Экранные кнопки навигации андроид

Вы можете спросить: «Для чего вся эта сложность, если задачи всё равно обрабатываются их создателями – Handler‘ами?». Мы получаем как минимум 2 преимущества:

— это помогает избежать состояния гонки (race conditions), когда работа приложения становится зависимой от порядка выполнения потоков;

— Thread не может быть повторно использован после завершения работы. А если поток работает с Looper’ом, то вам не нужно повторно создавать экземпляр Thread каждый раз для работы в бэкграунде.

Вы можете сами создавать и управлять Thread’ами и Looper’ами, но я рекомендую воспользоваться HandlerThread (Google решили использовать HandlerThread вместо LooperThread) : В нём уже есть встроенный Looper и всё настроено для работы.

А что о Handler? Это класс с двумя главными функциями: отправлять задачи в очередь сообщений (MessageQueue) и выполнять их. По умолчанию Handler неявно связывается с потоком в котором он был создан с помощью Looper’a, но вы можете связать его явно с другим потоком, если предоставите ему другой Looper в конструкторе. Наконец-то пришло время собрать все куски теории вместе и взглянуть на примере как всё это работает! Представим себе Activity в которой мы хотим отправлять задачи(в моей статье задачи представлены экземплярами Runnable интерфейса, что такое на самом деле задача(или сообщение) я расскажу во второй части статьи) в очередь сообщений(все активити и фрагменты существуют в главном UI потоке), но они должны выполняться с некоторой задержкой:

Так как mUiHandler связан с главным потоком(он получил Looper главного потока в конструкторе по умолчанию) и он является членом класса, мы можем получить к нему доступ из внутреннего анонимного класса и поэтому можем отправлять задачи-сообщения в главный поток. Мы используем Thread в примере выше и не можем использовать его повторно, если хотим выполнить новую задачу. Для этого нам придется создать новый экземпляр. Есть другое решение? Да! Мы можем использовать поток с Looper’ом. Давайте немного модифицируем пример выше с целью заменить Thread на HandlerThread для демонстрации того, какой прекрасной способностью к повторному использованию он обладает:

Я использую HandlerThread в этом примере, потому что я не хочу управлять Looper’ом сам, HandlerThread сам справится с этим. Один раз мы стартуем HandlerThread, после чего можем отправлять задачи в любое время, но помните о вызове метода quit когда вы хотите остановить HandlerThread. mWorkerHandler связан с MyWorkerThread с помощью его Looper. Вы не сможете инициализировать mWorkerHandler за пределами конструктора HandlerThread , так как getLooper будет возвращать null, потому что поток ещё не существует. Порой вам может встретится следующий способ инициализации Handler:

Порой это отлично работает, но иногда вы будете выхватывать NPE после вызова
postTask, с сообщением о том, что ваш mWorkerHandler — null. Сюрприз!

Почему это произошло? Хитрость здесь в нативном вызове, необходимом для создании нового потока. Если мы взглянем на кусок кода, где вызывается onLooperPrepared, мы найдём следующий фрагмент в HandlerThread классе:

Хитрость заключается в том, что run метод будет вызван только после того, как новый поток создаётся и запускается. И этот вызов может иногда произойти после вызова postTask(можете проверить это самостоятельно, просто поместите точку останова внутри postTask и onLooperPreparerd методов и взгляните, какой из них будет первым), таким образом вы можете стать жертвой состояния гонки между двух потоков (main и background).

Во второй части разберем как на самом деле внутри MessageQueue работают задачи.

Источник

Оцените статью