Postdelayed android что это

Класс 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. Нам уже ничего не надо обрабатывать.

Читайте также:  Что такое ускорить работу gpu андроиде

Для отправки кода в работу используется метод 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. Часть 1

Этой статьей мы открываем цикл статей на Хабре о нашей разработке под Android.
Согласно докладу компании Crittercism от 2012 года, OutOfMemoryError — вторая по распространенности причина «крашей» мобильных приложений.
Честно говоря, и в Badoo эта ошибка была в топе всех крашей (что неудивительно при том объеме фотографий, которые просматривают наши пользователи). Борьба с OutOfMemory — занятие кропотливое. Мы взяли в руки Allocation Tracker и начали играться с приложением. Наблюдая за данными зарезервированной памяти, мы выявили несколько сценариев, при которых выделение памяти росло с подозрительной стремительностью, забывая при этом уменьшаться. Вооружившись несколькими дампами памяти после этих сценариев, мы проанализировали их в MAT (http://www.eclipse.org/mat/).
Результат был занимательный и позволил нам в течение нескольких недель снизить количество крашей в разы. Что-то было специфично для нашего кода, но также выявились типичные проблемы, присущие большинству Android приложений.
Сегодня поговорим о конкретном случае утечки памяти. О нем многие знают, но часто закрывают на это глаза (а зря).

Речь пойдет об утечках памяти, связанных с неправильным использованием android.os.Handler. Не совсем очевидно, но все, что вы помещаете в Handler, находится в памяти и не может быть очищено сборщиком мусора в течении некоторого времени. Иногда довольно длительного.
Чуть позже мы покажем на примерах, что происходит и почему память не может быть освобождена. Если вы не любопытный, но хотите знать, как бороться с проблемой, то перейдите к выводам в конце статьи. Или сразу отправляйтесь на страничку маленькой библиотеки, которую мы выложили в открытый доступ: https://github.com/badoo/android-weak-handler.

Итак, что же там «течет»? Давайте разберемся.

Простой пример

Это очень простой класс Activity. Предположим, что нам нужно поменять текст по прошествии 800 секунд. Пример, конечно, нелепый, но зато хорошо продемонстрирует нам, как текут ручьи нашей памяти.
Обратите внимание на анонимный Runnable, который мы постим в Handler. Так же важно обратить внимание на длительный тайм-аут.
Для теста мы запустили этот пример и повернули телефон 7 раз, тем самым вызвав смену ориентации экрана и пересоздание Activity. Затем сняли дамп памяти и открыли его в MAT (http://www.eclipse.org/mat/).

С помощью OQL запускаем простой запрос, который выводит все инстансы класса Activity:

В памяти висит 7 инстансов Activity. Это в 7 раз больше, чем нужно. Давайте разберемся, почему сборщик мусора не смог удалить отработавшие объекты из памяти. Откроем кратчайший граф ссылок на один из Activity:

На скриншоте видно, что на Activity ссылается this$0. Это неявная ссылка из анонимного класса на внешний класс. В Java любой анонимный класс всегда имеет неявную ссылку на внешний класс, даже если вы никогда не обращаетесь к внешним полям или методам. Java не идеальна, а жизнь — это боль. Такие дела, котаны.

Далее, ссылка на this$0 хранится в callback, который хранится в связанном списке сообщений. В конце цепочки — локальная ссылка в стеке главного потока. По всей видимости, это локальная переменная в главном цикле UI потока, которая освободится, когда цикл отработает. В нашем случае это произойдет после того, как приложение завершит свою работу.

Итак, после того как мы поместили Runnable или Message в Handler, он будет хранится в списке сообщений в LooperThread до тех пор, пока сообщение не отработает. Вполне очевидно, что если мы поместим отложенное сообщение, то оно будет лежать в памяти до тех пор, пока не настанет его время. Вместе с сообщением в памяти будут лежать все объекты, на которые ссылается сообщение, явно и неявно.
И с этим нужно что-то делать.

Решение с использованием статического класса

Давайте попробуем решить нашу проблему, избавившись от ссылки this$0. Для этого переделаем анонимный класс в статический:

Читайте также:  Android request location permission

Запускаем, пару раз поворачиваем телефон и собираем дамп памяти.

Снова больше одной Activity? Давайте посмотрим, почему сборщик мусора не смог их удалить.

Обратите внимание на самый низ графа ссылок: Activity сохранен в ссылке mContext из mTextView внутри класса DoneRunnable. Очевидно, что использование статического класса самого по себе недостаточно, чтобы избежать утечки памяти. Нам нужно сделать кое-что еще.

Решение с использованием статического класса и WeakReference

Продолжим последовательный метод избавления от ссылки на TextView, которую мы нашли в ходе изучения дампов памяти.

Обратите внимание, что мы сохраняем ссылку на TextView в WeakReference. Использование WeakReference требует особой аккуратности: такая ссылка в любой момент может обнулиться. Поэтому сначала сохраняем ссылку в локальную переменную и работаем только с последней, проверив ее на null.

Запускаем, поворачиваем и собираем дамп памяти.

Мы добились желаемого! Только один Activity в памяти. Проблема решена.

Для использования данного подхода нам необходимо:

  • использовать статический внутренний или внешний класс;
  • использовать WeakReference для всех объектов, на которые мы ссылаемся.

Хорош ли данный метод?
Если сравнивать оригинальный код и «безопасный» код, то в глаза бросается большое количество «шума». Он отвлекает от понимания кода и усложняет его поддержку. Написание такого кода — то еще удовольствие, не говоря уж о том, что можно что-то забыть или забить.

Хорошо, что есть решения получше.

Очистка всех сообщений в onDestroy

У класса Handler есть занимательный и очень полезный метод — removeCallbacksAndMessages, который принимает null в качестве аргумента. Он удаляет все сообщения, находящиеся в очереди данного Handler’а. Давайте используем его в onDestroy.

Запустим, повернем и снимем дамп памяти.

Прекрасно! Только один класс Activity.

Этот метод намного лучше предыдущего: количество сопутствующего кода минимально, риски допустить ошибку намного ниже. Одна беда — не забыть бы вызвать очистку в методах onDestroy или там, где вам нужно почистить память.

У нас в запасе есть еще один метод, который, возможно, понравится вам намного больше.

Решение с использованием WeakHandler

Команда Badoo написала свой Handler — WeakHandler. Это класс, который ведет себя совершенно как Handler, но исключает утечки памяти.

Он использует мягкие и жесткие ссылки для избежания утечек памяти. Принцип его работы мы опишем немного позже, а пока давайте взглянем на код:

Очень похоже на оригинальный код, не так ли? Лишь одна маленькая деталь: вместо использования android.os.Handler мы использовали WeakHandler. Давайте запустим, повернем телефон несколько раз и снимем дамп памяти.

Наконец-то! Код чист как слеза и память не течет.

Если вам понравился этот метод, то вот хорошая новость: использовать WeakHandler очень просто.

Добавьте maven-зависимость в ваш проект:

Импортируйте WeakHandler в вашем коде:

Принцип работы WeakHandler

Главная идея — держать жесткую ссылку на сообщения или Runnable до тех пор, пока существует жесткая ссылка на WeakHandler. Как только WeakHandler может быть удален из памяти, все остальное должно быть удалено вместе с ним.

Для простоты объяснения мы покажем простенькую диаграмму, демонстрирующую разницу между помещением анонимного Runnable в простой Handler и в WeakHandler:

Обратите внимание на верхнюю диаграмму: Activity ссылается на Handler, который постит Runnable (помещает его в очередь сообщений, на которые ссылается Thread). Все неплохо, за исключением неявной обратной ссылки из Runnable на Activity. Пока Message лежит в очереди, которая живет, пока жив Thread, весь граф не может быть собран сборщиком мусора. В том числе и толстая Activity.

В нижней диаграмме Activity ссылается на WeakHandler, который держит Handler внутри. Когда мы просим его поместить Runnable, он заворачивает его в WeakRunnable и постит в очередь. Таким образом, очередь сообщений ссылается только на WeakRunnable. WeakRunnable содержит WeakReference на изначальный Runnable, т.е. сборщик мусора может его очистить в любой момент. Что бы он его не очистил раньше времени, WeakHandler держит жесткую ссылку на Runnable. Но как только сам WeakHandler может быть удален, Runnable так же может быть удален.

Нужно быть аккуратным и не забывать, что на WeakHandler должна быть ссылка извне, иначе все сообщения будут очищены вместе с ним сборщиком мусора.

Выводы

Использование postDelayed в Android не так просто, как кажется: нужно совершать дополнительные действия, чтобы память не текла. Для этого можно применять следующие методы:

  • использовать статический внутренний класс Runnable/Handler с WeakReferences на внешний класс;
  • чистить все сообщения в классе Handler из метода onDestroy;
  • использовать WeakHandler от Badoo (https://github.com/badoo/android-weak-handler).

Выбор за вами. Первый метод точно не для ленивых. Второй метод выглядит довольно простым, но требует дополнительной работы. Третий же — наш фаворит, но нужно быть внимательным: на WeakHandler должна быть внешняя ссылка до тех пор, пока он вам нужен, иначе сборщик мусора его удалит вместе со всеми сообщениями из очереди.

Удачной вам борьбы! Оставайтесь с нами — у этой темы будет продолжение.

Источник

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