- Класс Handler
- Периодическое выполнение задачи
- Пример с индикатором прогресса
- Splash-screen
- Чем опасен postDelayed
- Проблема
- Что же делать?
- Coroutines
- В заключении
- Как вызвать метод после задержки в Android
- Котлин
- Kotlin И Java много способов
- 1. Использование Handler
- 2. Использование TimerTask
- 3. Использование Executors
- На яве
- 1. Использование Handler
- 2. Использование Timer
- 3. Использование ScheduledExecutorService
- Полный список
- Обработчики
- Операции
- dismiss
- cancel
- Управление из Activity
- dismissDialog
- removeDialog
Класс 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 для реализации окна приветствия, которое автоматически закрывается и следом запускается основная активность игры или приложения.
Источник
Чем опасен 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 также предлагает отменяемые действия.
Также стоит отметить, что вы обычно должны обрабатывать различные исключения, которые могут возникнуть с сопрограммами. Например, отмена, исключение, тайм-аут, все, что вы решите использовать. Вот более сложный пример, если вы решите действительно использовать сопрограммы.
Источник
Полный список
— рассматриваем обработчики событий диалога
— программно закрываем и показываем диалог
Мы закрываем диалог нажатием на кнопку, на пункт списка или кнопкой Назад. Давайте рассмотрим, какие есть программные способы закрытия. Также узнаем, какие обработчики диалога можно использовать, чтобы отследить закрытие.
Project name: P0661_AlertDialogOperations
Build Target: Android 2.3.3
Application name: AlertDialogOperations
Package name: ru.startandroid.develop.p0661alertdialogoperations
Create Activity: MainActivity
В strings.xml пропишем тексты:
Код в основном должен быть понятен по прошлым урокам. Создаем диалог, настраиваем заголовок, сообщение и одну кнопку без обработчика (нам он сейчас не нужен). Далее для диалога указываем три обработчика: отображения, отмены и закрытия диалога. Все они пишут о себе в лог.
onclick – обработчик кнопки из main.xml. Здесь мы просто запускаем диалог.
Обработчики
Давайте смотреть, когда и какие обработчики событий диалога будут срабатывать. Все сохраним и запустим. Жмем кнопку Диалог, появляется диалог.
Диалог создался и сработал обработчик отображения диалога. Нажмем кнопку ОК. Диалог закрылся, а лог показал следующее:
Сработал обработчик закрытия диалога.
Теперь еще раз запустим диалог кнопкой Диалог. В логе видим:
Метод onCreateDialog не отработал, т.к. диалог уже создан. Это мы подробно рассматривали в прошлых уроках. Сработал обработчик отображения.
Для закрытия диалога нажмем кнопку Back (Назад) на эмуляторе. В логе появились следующие строки:
Перед обработчиком закрытия (Dismiss) сработал обработчик отмены (Cancel), т.к. диалог был отменен.
Операции
Разберем программные методы управления диалогом. Для этого немного изменим код MainActivity.java. Добавим два пустых пока метода method1 и method2, и перепишем onclick:
Handler мы пока не проходили, его понимать необязательно. Сейчас просто надо принять, что вся эта конструкция в onclick покажет диалог, затем через 2 секунды выполнит метод method1 и еще через 2 секунды выполнит метод method2. Т.е. получится такая последовательность:
отображение диалога
2 сек
выполнение method1
2 сек
выполнение method2
dismiss
Мы будем работать с method1 и method2. Начнем с метода dismiss – он закрывает диалог. Перепишем метод method1:
method2 пока не трогаем. Запустим приложение и нажмем кнопку Диалог. Диалог появился, повисел две секунды и закрылся. Это нам обеспечил dismiss, вызванный через 2 секунды после отображения диалога. Смотрим лог:
Все верно. Диалог создался, отобразился и закрылся. Обратите внимание на время записей в логе. Между Show и Dismiss должно быть примерно 2 секунды.
cancel
Теперь используем метод cancel. Перепишем method1:
Все сохраним, запустим приложение и вызовем диалог. Снова диалог появился и закрылся через две секунды. Сработал метод cancel. Логи:
Create
Show
Cancel
Dismiss
Все так же, как при закрытии диалога кнопкой Back.
Снова перепишем method1, используя метод hide:
Запустим приложение, вызовем диалог. Он отобразился и закрылся. Смотрим лог:
На этот раз обработчик закрытия не сработал. Диалог просто скрылся. Зачем это нужно, я не знаю, но метод такой есть, поэтому я рассказал о нем.
Управление из Activity
Мы работали напрямую с объектом Dialog и вызывали его методы. Есть еще другой способ. Сначала немного теории о механизме взаимодействия Activity и диалога. Когда мы первый раз выполняем метод showDialog, мы передаем туда ID. Это ID далее передается в onCreateDialog. В итоге onCreateDialog возвращает созданный диалог, и Activity для себя увязывает его с ID. И если мы захотим обратиться к этому диалогу, нам нужен будет только ID, Activity сама по нему определит, какой диалог нам нужен.
Когда мы, например, следующие разы вызываем showDialog, мы передаем туда ID, но диалог не создается. Activity по ID находит ранее созданный диалог и показывает его. У Activity также есть методы по закрытию диалога – это dismissDialog и removeDialog. Первый просто закрывает диалог, а второй закрывает и заставляет Activity забыть про него. Т.е. когда мы в след.раз захотим показать этот диалог, Activity будет заново создавать его, а не брать уже готовый. Проверим это.
dismissDialog
Все сохраним и запустим. Вызовем диалог и ждем. Диалог отобразился, через 2 секунды закрылся, и еще через 2 снова открылся. Смотрим лог:
Когда диалог отобразился второй раз, не сработал метод его создания, т.к. Activity использовало созданный при первом вызове объект.
removeDialog
Перепишем метод method1:
Будем не только закрывать диалог, но и «забывать» его. method2 оставляем без изменений, он будет показывать диалог.
Запустим приложение, запустим диалог и ждем. Диалог открылся, закрылся и открылся снова. Смотрим лог:
Create
Show
Dismiss
Create
Show
Но на этот раз при втором показе он снова создавался, т.к. Activity его забыло благодаря методу removeDialog.
У объекта Dialog есть еще метод show. Чем он отличается от метода Activity showDialog? show просто покажет созданный диалог, а showDialog, начинает проверять был ли уже создан диалог, создает его, если необходимо, и вызывает для него метод onPrepareDialog.
На следующем уроке:
— работаем с ProgressDialog
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник