- Класс Handler
- Периодическое выполнение задачи
- Пример с индикатором прогресса
- Splash-screen
- Полный список
- Чем опасен postDelayed
- Проблема
- Что же делать?
- Coroutines
- В заключении
- Как вызвать метод после задержки в Android
- Котлин
- Kotlin И Java много способов
- 1. Использование Handler
- 2. Использование TimerTask
- 3. Использование Executors
- На яве
- 1. Использование Handler
- 2. Использование Timer
- 3. Использование ScheduledExecutorService
Класс 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() — помещает сообщение в очередь после задержки, выраженной в миллисекундах
Чтобы обрабатывать эти сообщения, для объекта Handler необходимо реализовать метод обратного вызова handleMessage(), который будет вызываться каждым сообщением из очереди сообщения.
Для примера создадим приложение с ProgressBar, который будет отображать ход выполнения длительной задачи (это будет простой цикл с приостановкой потока на 1 секунду в каждой итерации цикла) и обновлять степень завершения этой задачи через объект Handler в классе активности.
Splash-screen
Очень часто программисты используют Handler для реализации окна приветствия, которое автоматически закрывается и следом запускается основная активность игры или приложения.
Источник
Полный список
— работаем с Handler и Runnable
Кроме обработки сообщений, мы можем попросить Handler выполнить кусок кода – Runnable. В прошлых уроках мы работали с сообщениями, которые содержали атрибуты. Мы их обрабатывали в Handler и в зависимости от значений атрибутов выполняли те или иные действия. Runnable же – это кусок кода, который мы пошлем вместо атрибутов сообщения, и он будет выполнен в потоке, с которым работает Handler. Нам уже ничего не надо обрабатывать.
Для отправки кода в работу используется метод post. Как и сообщения, Runnable может быть выполнен с задержкой (postDelayed), и может быть удален из очереди (removeCallbacks). Напишем приложение, которое продемонстрирует все эти возможности.
Project name: P0841_HandlerRunnable
Build Target: Android 2.3.3
Application name: HandlerRunnable
Package name: ru.startandroid.develop.p0841handlerrunnable
Create Activity: MainActivity
ProgressBar, отображающий текущий прогресс. CheckBox, который будет включать отображение доп.информации в TextView.
В onCreate мы прописываем обработчик для CheckBox. При включении галки отображается TextView и в работу отправляется задание showInfo. При выключении галки – задание showInfo удаляется из очереди.
Далее в новом потоке эмулируем какое-либо действие — запускаем счетчик с паузами. В каждой итерации цикла отправляем в работу задание updateProgress, которое обновляет ProgressBar.
updateProgress – код, который обновляет значение ProgressBar.
showInfo – код, который обновляет TextView и сам себя планирует на выполнение через 1000 мсек. Т.е мы включаем CheckBox, showInfo срабатывает первый раз и само себя планирует на следующий раз. Т.е. этот код лежит в очереди сообщений, обрабатывается и снова кладет себя туда. Так продолжается, пока мы явно его не удалим из очереди (removeCallbacks), выключив CheckBox.
Будем выводить что-нибудь в лог из showInfo, чтобы увидеть, когда он работает, а когда нет.
Все сохраним и запустим приложение. Побежал ProgressBar.
Появился TextView, который отображает текущее значение счетчика.
В логи при этом добавляется раз в секунду запись:
Выключим CheckBox. Текст исчез.
И логи перестали идти. Значит, задание showInfo успешно удалилось из очереди и больше не работает.
Если снова включим CheckBox – оно снова начнет срабатывать и само себя помещать в очередь с задержкой исполнения. Выключаем CheckBox – удаляем его из очереди.
На следующем уроке:
— рассмотрим еще пару способов запуска Runnbale в UI-потоке
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Чем опасен postDelayed
Часто из-за особенностей работы android системы и sdk, нам необходимо подождать, когда определённая часть системы будет сконфигурирована или произойдёт какое-то необходимое нам событие. Зачастую это является костылём, но иногда без них никак, особенно в условиях дедлайнов. Поэтому во многих проектах для этого использовался postDelayed. Под катом рассмотрим, чем же он так опасен и что с этим делать.
Проблема
Для начала рассмотрим как обычно используют postDelayed():
С виду всё хорошо, но давайте изучим этот код повнимательнее:
1) Это отложенное действие, выполнение которого мы будем ожидать через некоторое время. Зная насколько динамично пользователь может совершать переходы между экранами, данное действие должно быть отменено при смене фрагмента. Однако, этого здесь не происходит, и наше действие выполнится, даже если текущий фрагмент будет уничтожен.
Проверить это просто. Создаём два фрагмента, при переходе на второй запускаем postDelayed с большим временем, к примеру 5000 мс. Сразу возвращаемся назад. И через некоторое время видим в логах, что действие не отменено.
2) Второе «вытекает» из первого. Если в данном runnable мы передадим ссылку на property нашего фрагмента, будет происходить утечка памяти, поскольку ссылка на runnable будет жить дольше, чем сам фрагмент.
3) Третье и основное почему я об этом задумался:
Падения приложения, если мы обращаемся ко view после onDestroyView
synthitec — java.lang.NullPointerException , поскольку кеш уже очищен при помощи _$_clearFindViewByIdCache , а findViewById отдаёт null
viewBinding — java.lang.IllegalStateException: Can’t access the Fragment View’s LifecycleOwner when getView() is null
Что же делать?
Необходимо отписываться от нашего действия перед тем, как view будет отсоединено от window.
Обычный doOnDetach нельзя использовать, поскольку view может быть ещё не прикреплено к window, как к примеру в onViewCreated. И тогда наше действие будет сразу же отменено.
Где то во View.kt:
Или же обобщим в extension:
В принципе на этом можно остановится. Все проблемы решены. Но этим мы добавляем ещё один тип асинхронного выполнения к нашему проекту, что несколько усложняет его. Сейчас в мире Native Android есть 2 основных решения для асинхронного выполнения кода — Rx и Coroutines.
Попробуем использовать их.
Сразу оговорюсь, что не претендую на 100% правильность по отношению к вашему проекту. В вашем проекте это может быть по другому/лучше/короче.
Coroutines
Обычно во всех проектах есть базовый фрагмент для инжекта зависимостей, настройки di и так далее. Используем его для обобщения работы с отменой отложенных действий:
Нам необходимо отменять все дочерние задачи в onDestroyView, но при этом не закрывать scope, поскольку после этого возможно вновь создание View без пересоздания Fragment. К примеру при роутинге вперёд на другой Fragment и после этого назад на текущий.
В onDestroy уже закрываем scope, так как далее никаких задач не должно быть запущено.
Все подготовительные работы сделаны.
Перейдём к самой замене postDelayed:
Так как нам не нужно, чтобы наши отложенные задачи выполнялись, если view уже уничтожено, просто не запускаем ничего и возвращаем null. Иначе запускаем наше действие с необходимой задержкой. Но теперь при уничтожении view, задача будет отменена и ресурсы будут отпущены.
Как правильно подметил Keanu_Reeves, можно подключить androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01 или выше и у нас уже будет готовый scope:
В RX за отмену подписок отвечает класс Disposable, но в RX нет Structured concurrency в отличии от coroutine. Из-за этого приходится прописывать это всё самому. Выглядит обычно это примерно так:
Также аналогично отменяем все задачи в базовом фрагменте:
И сам extension:
В заключении
При использовании каких-либо отложенных действий нельзя забывать, что это уже асинхронное исполнение, и соответственно оно требует отмены, в противном случае начинают происходить утечки памяти, краши и разные другие неожиданные вещи.
Источник
Как вызвать метод после задержки в Android
Я хочу иметь возможность вызывать следующий метод после указанной задержки. В цели c было что-то вроде:
Есть ли эквивалент этого метода в Android с Java? Например, мне нужно иметь возможность вызывать метод через 5 секунд.
Котлин
Я не мог использовать другие ответы в моем случае. Я использовал нативный таймер Java вместо этого.
Примечание. Этот ответ был дан, когда в вопросе не был указан Android в качестве контекста. Ответ на вопрос, касающийся темы пользовательского интерфейса Android, можно найти здесь.
Похоже, что API Mac OS позволяет текущему потоку продолжить и планирует выполнение задачи асинхронно. В Java эквивалентная функция предоставляется java.util.concurrent пакетом. Я не уверен, какие ограничения может наложить Android.
Для выполнения чего-либо в потоке пользовательского интерфейса через 5 секунд:
Вы можете использовать Handler внутри UIThread:
Спасибо за все отличные ответы, я нашел решение, которое наилучшим образом соответствует моим потребностям.
Kotlin И Java много способов
1. Использование Handler
2. Использование TimerTask
Или даже короче
Или самый короткий будет
3. Использование Executors
На яве
1. Использование Handler
2. Использование Timer
3. Использование ScheduledExecutorService
Смотрите это демо:
Если вам нужно использовать обработчик, но вы находитесь в другом потоке, вы можете использовать его runonuithread для запуска обработчика в потоке пользовательского интерфейса. Это избавит вас от исключений, брошенных с просьбой позвонить Looper.Prepare()
Выглядит довольно грязно, но это один из способов.
Я предпочитаю использовать View.postDelayed() метод, простой код ниже:
Вот мое самое короткое решение:
Если вы используете Android Studio 3.0 и выше, вы можете использовать лямбда-выражения. Метод callMyMethod() вызывается через 2 секунды:
Если вам нужно отменить отложенный запуск, используйте это:
Я предлагаю Таймер , он позволяет запланировать вызов метода на очень определенный интервал. Это не заблокирует ваш пользовательский интерфейс и не оставит ваше приложение отзывчивым во время выполнения метода.
Другой вариант — это wait (); метод, это заблокирует текущий поток на указанный промежуток времени. Это заставит ваш пользовательский интерфейс перестать отвечать, если вы сделаете это в потоке пользовательского интерфейса.
Для простой строки Handle Post delay вы можете сделать следующее:
надеюсь, это поможет
Вы можете использовать это для простейшего решения:
Еще, ниже может быть еще одно чистое полезное решение:
Вы можете сделать это намного чище, используя недавно введенные лямбда-выражения:
Так что здесь есть несколько вещей, которые нужно учитывать, так как есть много способов снять шкуру с этой кошки. Хотя ответы уже все были выбраны и выбраны. Я думаю, что важно, чтобы это было пересмотрено с надлежащими руководящими принципами кодирования, чтобы никто не шел в неправильном направлении только из-за «простого ответа большинства».
Итак, сначала давайте обсудим простой ответ с задержкой после публикации, который является ответом, выбранным победителем в целом в этой теме.
Несколько вещей для рассмотрения. После задержки вы можете столкнуться с утечками памяти, мертвыми объектами, ушедшими жизненными циклами и многим другим. Поэтому правильное обращение с ним также важно. Вы можете сделать это несколькими способами.
Ради современного развития я поставлю в КОТЛИН
Вот простой пример использования потока пользовательского интерфейса при обратном вызове и подтверждение того, что ваша активность все еще жива, когда вы нажимаете на обратный вызов.
Тем не менее, это все еще не идеально, поскольку нет никаких причин, чтобы ответить на ваш обратный вызов, если активность ушла. так что лучшим способом было бы сохранить ссылку на него и удалить его обратные вызовы, как это.
и, конечно же, очистка onPause, чтобы он не попадал в обратный вызов.
Теперь, когда мы обсудили очевидное, давайте поговорим о более чистом варианте с современными сопрограммами и котлином :). Если вы еще не используете их, вы действительно пропустите.
или если вы хотите всегда запускать пользовательский интерфейс для этого метода, вы можете просто сделать:
Конечно, точно так же, как PostDelayed, вы должны убедиться, что обрабатываете отмену, чтобы вы могли либо выполнять проверки активности после задержки вызова, либо вы можете отменить ее в onPause, как и другой маршрут.
Если вы поместите запуск (UI) в сигнатуру метода, задание может быть назначено в вызывающей строке кода.
Таким образом, мораль этой истории заключается в том, чтобы быть в безопасности с вашими отложенными действиями, убедитесь, что вы удалили свои обратные вызовы, или отменили свою работу, и, конечно, подтвердите, что у вас есть правильный жизненный цикл, чтобы коснуться элементов в вашем обратном обратном вызове. Coroutines также предлагает отменяемые действия.
Также стоит отметить, что вы обычно должны обрабатывать различные исключения, которые могут возникнуть с сопрограммами. Например, отмена, исключение, тайм-аут, все, что вы решите использовать. Вот более сложный пример, если вы решите действительно использовать сопрограммы.
Источник