Android — Адаптер, таймеры и многопоточность. Android.

Содержание

Android — Адаптер, таймеры и многопоточность. Android.

Краткое описание:
Менеджер нескольких таймеров и секундомеров.

  • Без рекламы;
  • Безлимитное количество секундомеров;
  • Тёмная тема;
  • Неограниченное количество виджетов таймера;
  • Будущий функционал.

Требуется Android: 4.0+
Русский интерфейс: Да.

Сообщение отредактировал iMiKED — 05.11.19, 06:59

Multi Timer StopWatch Premium v2.0.3
What’s New
— Bug fix for adding widgets
— New notification icon for finished timer
— Bug fix for Bluetooth alarm

Multi_Timer_StopWatch_Premium_v2.0.3_build_75.apk ( 3,43 МБ )

Сообщение отредактировал Alex0047 — 17.02.15, 23:56

Спасибо выложившим товарищам.
Самый удобный таймер, из тех, что устанавливал.

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

А еще лучше вообще без перевода, прога-то несложная.

Затестив 2.2.0 на G900FD 5.1.1 (cm 12.1),
могу сообщить — исключительно доволен программой — работает как надо, как от неё и ожидалось, судя по описаниям.
TTS — работает и настройка таймера по проговариваниям устраивает по гибкости.
Работа в фоне — устойчива.
Можно настроить чтоб окно оповещения сработавшего таймера не вылазило поверх экрана блокировки (чтоб случайно не нажать).
Встроенный секундомер — устраивает, можно легко экспортировать результаты кругов, работает параллельно с таймером.
Интерфейс приятен — не раздражает ни пёстростью, ни унынием. Хороший дизайн и эргономика.
Виджеты удобны, гибки в настройках, наглядны, эргономичны, красивы.
Сохранение настроек и таймеров имеется.

Нашёл, что искал, короче.
Раньше юзал Remind Droid — за отсутствием поддержки Remind Droid автором, этот, уникальный в своё время таймер, устарел безнадёжно. Король умер.
Но, вот снова — Да здравствует король! И Мульти Таймер даже превосходит Remind Droid. Разве что организовывать цепочки таймеров невозможно или я не увидел как.

Но что меня всегда удивляло: почему набор изложенных выше достоинств не учтён программерами и не востребован юзерами?
Большинство таймеров на маркете недоделаны для пользования. А и так сойдёт, куча 5 звёзд.
Меня, например, не устраивает ни один таймер, в котором нет всех этих фич.

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

Сообщение отредактировал justmantik24 — 04.11.15, 05:24

Полный список

— разбираемся, что такое Handler и зачем он нужен

Для полного понимания урока желательно иметь представление о потоках (threads) в Java.

Так просто ведь и не объяснишь, что такое Handler. Можете попробовать почитать официальное описание, но там достаточно нетривиально и мало написано. Я попробую здесь в двух словах рассказать.

В Android к потоку (thread) может быть привязана очередь сообщений. Мы можем помещать туда сообщения, а система будет за очередью следить и отправлять сообщения на обработку. При этом мы можем указать, чтобы сообщение ушло на обработку не сразу, а спустя определенное кол-во времени.

Handler — это механизм, который позволяет работать с очередью сообщений. Он привязан к конкретному потоку (thread) и работает с его очередью. Handler умеет помещать сообщения в очередь. При этом он ставит самого себя в качестве получателя этого сообщения. И когда приходит время, система достает сообщение из очереди и отправляет его адресату (т.е. в Handler) на обработку.

Handler дает нам две интересные и полезные возможности:

1) реализовать отложенное по времени выполнение кода

2) выполнение кода не в своем потоке

Подозреваю, что стало не сильно понятнее, что такое Handler, а главное – зачем он вообще нужен :) . В ближайшие несколько уроков будем с этим разбираться, и все станет понятно.

В этом уроке сделаем небольшое приложение. Оно будет эмулировать какое-либо долгое действие, например закачку файлов и в TextView выводить кол-во закачанных файлов. С помощью этого примера мы увидим, зачем может быть нужен Handler.

Project name: P0801_Handler
Build Target: Android 2.3.3
Application name: Handler
Package name: ru.startandroid.develop.p0801handler
Create Activity: MainActivity

ProgressBar у нас будет крутиться всегда. Позже станет понятно, зачем. TextView – для вывода информации о закачке файлов. Кнопка Start будет стартовать закачку. Кнопка Test будет просто выводить в лог слово test.

В обработчике кнопки Start мы организуем цикл для закачки файлов. В каждой итерации цикла выполняем метод downloadFile (который эмулирует закачку файла), обновляем TextView и пишем в лог информацию о том, что кол-во закачанных файлов изменилось. Итого у нас должны закачаться 10 файлов и после закачки каждого из них лог и экран должны показывать, сколько файлов уже закачано.

По нажатию кнопки Test – просто выводим в лог сообщение.

downloadFile – эмулирует закачку файла, это просто пауза в одну секунду.

Все сохраним и запустим приложение.

Мы видим, что ProgressBar крутится. Понажимаем на кнопку Test, в логах появляется test. Все в порядке, приложение отзывается на наши действия.

Теперь расположите AVD на экране монитора так, чтобы он не перекрывал вкладку логов в Eclipse (LogCat). Нам надо будет видеть их одновременно.

Если мы нажмем кнопку Start, то мы должны наблюдать, как обновляется TextView и пишется лог после закачки очередного файла. Но на деле будет немного не так. Наше приложение просто «зависнет» и перестанет реагировать на нажатия. Остановится ProgressBar, не будет обновляться TextView, и не будет нажиматься кнопка Test. Т.е. UI (экран) для нас станет недоступным. И только по логам будет понятно, что приложение на самом деле работает и файлы закачиваются. Нажмите Start и убедитесь.

Экран «висит», а логи идут. Как только все 10 файлов будут закачаны, приложение оживет и снова станет реагировать на ваши нажатия.

А все почему? Потому что работа экрана обеспечивается основным потоком приложения. А мы заняли весь этот основной поток под свои нужды. В нашем случае, как будто под закачку файлов. И как только мы закончили закачивать файлы – поток освободился, и экран стал снова обновляться и реагировать на нажатия.

Для тех, кто имеет опыт кодинга на Java, я ничего нового не открыл. Для остальных же, надеюсь, у меня получилось доступно объяснить. Тут надо понять одну вещь — основной поток приложения отвечает за экран. Этот поток ни в коем случае нельзя грузить чем-то тяжелым – экран просто перестает обновляться и реагировать на нажатия. Если у вас есть долгоиграющие задачи – их надо вынести в отдельный поток. Попробуем это сделать.

Т.е. мы просто помещаем весь цикл в новый поток и запускаем его. Теперь закачка файлов пойдет в этом новом потоке. А основной поток будет не занят и сможет без проблем прорисовывать экран и реагировать на нажатия. А значит, мы будем видеть изменение TextView после каждого закачанного файла и крутящийся ProgressBar. И, вообще, сможем полноценно взаимодействовать с приложением. Казалось бы, вот оно счастье :)

Все сохраним и запустим приложение. Жмем Start.

Приложение вылетело с ошибкой. Смотрим лог ошибок в LogCat. Там есть строки:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

Смотрим, что за код у нас в MainActivity.java в 37-й строке:

При попытке выполнить этот код (не в основном потоке) мы получили ошибку «Only the original thread that created a view hierarchy can touch its views». Если по-русски, то «Только оригинальный поток, создавший view-компоненты, может взаимодействовать с ними». Т.е. работа с view-компонентами доступна только из основного потока. А новые потоки, которые мы создаем, не имеют доступа к элементам экрана.

Т.е. с одной стороны нельзя загружать основной поток тяжелыми задачами, чтобы не «вешался» экран. С другой стороны – новые потоки, созданные для выполнения тяжелых задач, не имеют доступа к экрану, и мы не сможем из них показать пользователю, что наша тяжелая задача как-то движется.

Тут нам поможет Handler. План такой:

— мы создаем в основном потоке Handler
— в потоке закачки файлов обращаемся к Handler и с его помощью помещаем в очередь сообщение для него же самого
— система берет это сообщение, видит, что адресат – Handler, и отправляет сообщение на обработку в Handler
— Handler, получив сообщение, обновит TextView

Чем это отличается от нашей предыдущей попытки обновить TextView из другого потока? Тем, что Handler был создан в основном потоке, и обрабатывать поступающие ему сообщения он будет в основном потоке, а значит, будет иметь доступ к экранным компонентам и сможет поменять текст в TextView. Получить доступ к Handler из какого-либо другого потока мы сможем без проблем, т.к. основной поток монополизирует только доступ к UI. А элементы классов (в нашем случае это Handler в MainActivity.java) доступны в любых потоках. Таким образом Handler выступит в качестве «моста» между потоками.

Перепишем метод onCreate:

Здесь мы создаем Handler и в нем реализуем метод обработки сообщений handleMessage. Мы извлекаем из сообщения атрибут what – это кол-во закачанных файлов. Если оно равно 10, т.е. все файлы закачаны, мы активируем кнопку Start. (кол-во закачанных файлов мы сами кладем в сообщение — сейчас увидите, как)

Метод onclick перепишем так:

Мы деактивируем кнопку Start перед запуском закачки файлов. Это просто защита, чтобы нельзя было запустить несколько закачек одновременно. А в процессе закачки, после каждого закачанного файла, отправляем (sendEmptyMessage) для Handler сообщение с кол-вом уже закачанных файлов. Handler это сообщение примет, извлечет из него кол-во файлов и обновит TextView.

Все сохраняем и запускаем приложение. Жмем кнопку Start.

Кнопка Start стала неактивной, т.к. мы ее сами выключили. А TextView обновляется, ProgressBar крутится и кнопка Test нажимается. Т.е. и закачка файлов идет, и приложение продолжает работать без проблем, отображая статус закачки.

Когда все файлы закачаются, кнопка Start снова станет активной.

Подытожим все вышесказанное.

1) Сначала мы попытались грузить приложение тяжелой задачей в основном потоке. Это привело к тому, что мы потеряли экран – он перестал обновляться и отвечать на нажатия. Случилось это потому, что за экран отвечает основной поток приложения, а он был сильно загружен.

2) Мы создали отдельный поток и выполнили весь тяжелый код там. И это бы сработало, но нам надо было обновлять экран в процессе работы. А из не основного потока доступа к экрану нет. Экран доступен только из основного потока.

3) Мы создали Handler в основном потоке. А из нового потока отправляли для Handler сообщения, чтобы он нам обновлял экран. В итоге Handler помог нам обновлять экран не из основного потока.

Достаточно сложный урок получился. Наверняка, мало, что понятно. Не волнуйтесь, в этом уроке я просто показал, в какой ситуации Handler может быть полезен. А методы работы с ним мы рассмотрим подробно в следующих уроках.

Eclipse может подчеркивать Handler желтым цветом и ругаться примерно такими словами: «This Handler class should be static or leaks might occur«. Тем самым он сообщает нам, что наш код немного плох и может вызвать утечку памяти. Тут он прав абсолютно, но я в своих уроках все-таки буду придерживаться этой схемы, чтобы не усложнять.

А на форуме я отдельно расписал, почему может возникнуть утечка и как это можно пофиксить. Как закончите тему Handler-ов, обязательно загляните туда и почитайте — http://forum.startandro >

На следующем уроке:

— посылаем простейшее сообщение для Handler

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Цукерберг рекомендует:  Использование Netbeans IDE для веб-разработки

Java Gym: Многопоточность в Android и Java, часть 3: Защита от ANR в Android.

Универсальные вещи — это хорошо, но и изобретать велосипеды не стоит. Android имеет особую проблему с потоками и несколько решений для нее. Эта проблема — ANR. Это выглядит вот так.

Если вы видите это то вы (… кирпичи?), на самом деле вы делаете следующие движения в консоли.

Внутри traces.txt мы видим что-то такое:

Здесь некая ViewRoot собралась перерисоваться, однако во время выполнения метода Surface.unlockCanvasAndPost(Native Method) у нее кончилось на это время. Возможно, что нажав «Wait» на окошке с предупреждением мы все исправим. Но ведь пользователь не читает глупых надписей, и такое приложение минимальный QA не пропустит.

Отсюда формулирую проблему многопоточности в Anroid: есть поток пользовательского интерфейса и в нём нельзя выполнять долгие операции, то есть по факту любые операции, про которые не известно, что это операции с O(1), то есть для неконстантных операций.

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

Итак такое приложение — это Activity. Если быть более честным, то Activity может быть в одном приложении много, но каждый такой компонент — потенциальный источник ANR.

Приведу наиболее простой подход к построению такого приложения.

  1. Создаём наследника класса Activity;
  2. Регистрируем его в AndroidManifest.xml, то есть добавляем туда
  3. Создаём в формате xml описание главного лайаута.
  4. В методе onCreate созданной Activity подключаем наш лайаут через setContentView().

При этом мы имеем доступ ко всем элементам представления через findViewById(). Проиллюстрируем весь процесс.

Оперируя терминами MVC (Model-View-Controller — англ. Модель-Представление-Поведение): желтым на рисунке показаны элементы представления, а голубым — поведения. Модель — бизнес логика, которая может находится в других классах, организация которых — часть конкретного проекта. Наша же цель сейчас:

безопасно выполнить длительную операцию в каком-нибудь из Listener’ов,

отобразить текущее состояние модели по событию на View.

Первое, что видно сразу, нужно запустить в классе Listener’а поток, в котором будет выполнятся длинное действие, и поэтому самый сложный вопрос — это второй: как безопасно отобразить. Или как перейти в поток пользовательского интерфейса из выделенного потока задачи.

Первый прием — самый простой: «runOnUIThread()».

Для примера предположим, что у нас есть пользовательский интерфейс(допусим, он находится в main.xml), на котором есть два элемента: кнопка “увеличить счётчик” и TextView, на котором мы этот счётчик можем видеть. То есть, есть элемент строго ввода и строго вывода.

Прекрасно, и процетах в 50 случаев годится. Однако что делать если нужно передать много разных состояний и объектов в поток пользовательского интерфейса, да еще и не одним махом, да еще возможно и не из одного потока. Для этого случая в Android API есть класс Handler.

Handler.

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

Кажется, что все проблемы решены, но и у этого решения есть слабые места. Во-первых плохо, если Activity имеет более одного Handler’а, а значит 5 — 7 сообщений и наш код уже плохо читаем. А 5 — 7 сообщений — это не 5 — 7 фичей, а значительно меньше, так как некоторые фичи нуждаются в 2 — 3 сообщениях. Для хорошей изоляции асинхронных задач в Android API есть класс AsyncTask.

AsyncTask или Thread и Handler в одном классе.

Больше всего этот класс походит для задач с визуализированным прогрессом, например загрузка контента и для задач, которые предусматривают вывод результатов работы на пользовательский интерфейс (почти всегда годное решение).

Покажем это на примере загрузчика файлов:

И ещё одни приём обратной связи с View.

Итак, предположим что у нас есть много однотипных процессов догрузки ресурсов. На моей практике это была загрузка картинок в ListView. Тут правило такое: есть ресурс — можно перерисовать представление. Важная деталь: таких представлений много.

Каждое представление(наследник класса View) имеет два метода:

  • void post(Runnable runnable);
  • void postDelayed(Runnable runnable, long timeout);

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

Что в этом примере важно увидеть. Во-первых важно увидеть final перед Bitmap, так как если final убрать, то фокус не отработает, так как ссылка битмап к моменту выполнения переданного в post Runnabel уже будет потеряна.

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

Однако это всё ещё не очень хорошо. Во-первых мы имеем передаем ссылку на mImageView не самым лучшим образом. Фактически эта ссылка может быть изменена в процессе загрузки. Ну и ситуация здесь скорее требует runOnUIThread. Представим себе ситуацию с адаптером для GridView или ListView.

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

Вот пожалуй и все основные технические решения для защиты вашего Android приложения от ANR.

Процессы и потоки

Когда компонент приложения запускается при отсутствии других работающих компонентов , система Android запускает новый процесс Linux для приложения с одним потоком выполнения. По умолчанию все компоненты одного приложения работают в одном процессе и потоке (называется «главным потоком»). Если компонент приложения запускается при наличии процесса для этого приложения (так как существует другой компонент из приложения), тогда компонент запускается в этом процессе и использует тот же поток выполнения. Однако можно организовать выполнение других компонентов приложения в отдельных процессах и создавать дополнительный поток для любого процесса.

В этом документе обсуждается работа процессов и потоков в приложении Android.

Процессы

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

Запись манифеста для каждого типа элементов компонента — , , и

— поддерживает атрибут android:process , позволяющий задавать процесс, в котором следует выполнять этот компонент. Можно установить этот атрибут так, чтобы каждый компонент выполнялся в собственном процессе, или так, чтобы только некоторые компоненты совместно использовали один процесс. Можно также настроить процесс android:process так, чтобы компоненты разных приложений выполнялись в одном процессе, при условии что приложения совместно используют один идентификатор пользователя Linux и выполняют вход с одним сертификатом.

Android может остановить процесс в некоторой точке, когда не хватает памяти и она необходима другим процессам, которые обслуживают пользователя в данный момент. Работа компонентов приложения, работающих в этом процессе, последовательно останавливается. Процесс для этих компонентов запускается повторно, когда для них появляется работа.

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

Жизненный цикл процесса

Система Android пытается сохранять процесс приложения как можно дольше, но в конечном счете вынуждена удалять старые процессы, чтобы восстановить память для новых или более важных процессов. Чтобы определить, какие процессы сохранить, а какие удалить, система помещает каждый процесс в «иерархию важности» на основе компонентов, выполняющихся в процессе, и состояния этих компонентов. Процессы с самым низким уровнем важности исключаются в первую очередь, затем исключаются процессы следующего уровня важности и т. д., насколько это необходимо для восстановления ресурсов системы.

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

    Процесс переднего плана

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

  • Он содержит действие Activity , с которым взаимодействует пользователь (вызван метод ActivityonResume() ).
  • Он содержит службу Service , связанную с действием, с которым взаимодействует пользователь.
  • Он содержит службу Service , которая выполняется «на переднем плане», — службу, которая называется startForeground() .
  • Он содержит службу Service , которая выполняет один из обратных вызовов жизненного цикла ( onCreate() , onStart() или onDestroy() ).
  • Он содержит ресивер BroadcastReceiver , который выполняет метод onReceive() .

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

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

  • Он содержит действие Activity , которое не находится на переднем плане, но видно пользователю (вызван метод onPause() ). Например, это может происходить, если действие на переднем плане запустило диалоговое окно, которое позволяет видеть предыдущее действие позади него.
  • Он содержит службу Service , связанную с видимым действием или действием переднего плана.

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

Процесс, который выполняет службу, запущенную с помощью метода startService() , и не попадает ни в одну из двух категорий более высокого уровня. Хотя служебные процессы не связаны непосредственно с тем, что видит пользователь, они обычно выполняют важные для пользователя действия (например, воспроизводят музыку в фоновом режиме или загружают данные в сеть), поэтому система сохраняет их выполнение, если памяти достаточно для их работы наряду со всеми видимыми процессами и процессами переднего плана.

Процесс, содержащий действия, которые не видны пользователю в настоящее время (вызван метод onStop() действия). Эти процессы не оказывают непосредственного воздействия на работу пользователя, и система может удалить их в любой момент, чтобы освободить память для процессов переднего плана, видимых или служебных процессов. Обычно выполняется множество фоновых процессов, поэтому они хранятся в списке LRU (недавно использованные), чтобы процессы, содержащие самые недавние действия, которые видел пользователь, удалялись в последнюю очередь. Если для действия правильно реализованы методы жизненного цикла, и действие сохраняет текущее состояние, удаление процесса этого действия не оказывает видимого воздействия на работу пользователя, так как когда пользователь возвращается к этому действию, оно восстанавливает все элементы своего видимого состояния. Информацию о сохранении и восстановлении состояния см. в документе Действия .

Процесс, не содержащий никаких компонентов активного приложения. Единственная причина сохранять процесс такого типа — это кэширование, которое улучшает время следующего запуска компонента в этом процессе. Система часто удаляет эти процессы для равномерного распределения всех системных ресурсов между кэшем процесса и кэшем базового ядра.

Система Android относит процесс к максимально высокому уровню на основе важности компонентов, активных в процессе в текущее время. Например, если процесс содержит служебное и видимое действие, процесс считается видимым, а не служебным процессом.

Кроме того, уровень процесса может быть повышен, поскольку имеются другие процессы, зависимые от него. Например, процесс, обслуживающий другой процесс, не может иметь уровень ниже уровня обслуживаемого процесса. Например, если поставщик контента в процессе A обслуживает клиента в процессе B или служебный процесс A связан с компонентом в процессе B, процесс A всегда считается не менее важным, чем процесс B.

Так как процесс, выполняющий службу, оценивается выше процесса с фоновыми действиям, действие, запускающее долговременную операцию, может запустить службу для этой операции, а не просто создать рабочий поток, особенно в случае, если операция продлится дольше действия. Например, действие, которое загружает изображение на веб-сайт, должно запустить службу для выполнения загрузки, так что загрузка может продолжаться в фоновом режиме даже после выхода пользователя из действия. Использование службы гарантирует, что операция будет иметь приоритет не ниже «служебного процесса», независимо от того, что происходит с действием. По этой же причине ресиверы должны использовать службы, а не просто ставить в поток операции, требующие много времени для выполнения.

Потоки

При запуске приложения система создает поток выполнения для приложения, который называется «главным». Этот поток очень важен, так как он отвечает за диспетчеризацию событий на виджеты соответствующего интерфейса пользователя, включая события графического представления. Он также является потоком, в котором приложение взаимодействует с компонентами из набора инструментов пользовательского интерфейса Android (компонентами из пакетов android.widget и android.view ). По существу, главный поток — это то, что иногда называют потоком пользовательского интерфейса.

Система не создает отдельного потока для каждого экземпляра компонента. Все компоненты, которые выполняются в одном процессе, создают экземпляры в потоке пользовательского интерфейса, и системные вызовы каждого компонента отправляются из этого потока. Поэтому методы, которые отвечают на системные обратные вызовы (такие как метод onKeyDown() для сообщения о действиях пользователя или метод обратного вызова жизненного цикла), всегда выполняются в потоке пользовательского интерфейса процесса.

Цукерберг рекомендует:  CSS cтили для SVG

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

Когда приложение выполняет интенсивную работу в ответ на действия пользователя, эта одиночная модель потока может показывать плохую производительность, если приложение реализовано неправильно. То есть, если все происходит в потоке пользовательского интерфейса, выполнение долговременных операций, таких как сетевой доступ или запросы к базе данных, будет блокировать весь пользовательский интерфейс. Когда поток заблокирован, не могут обрабатываться никакие события, включая события изменения отображения. С точки зрения пользователя приложение выглядит зависшим. Хуже того, если поток пользовательского интерфейса заблокирован более нескольких секунд (в настоящее время около 5 секунд), отображается печально известное диалоговое окно «приложение не отвечает». После этого недовольный пользователь может выйти из вашего приложения и удалить его.

Кроме того, набор инструментов пользовательского интерфейса Android не является потокобезопасным. Поэтому, вы не должны работать с пользовательским интерфейсом из рабочего потока. Манипуляции с пользовательским интерфейсом необходимо выполнять из потока пользовательского интерфейса. Таким образом, существует только два правила однопоточной модели Android:

  1. Не блокируйте поток пользовательского интерфейса
  2. Не обращайтесь к набору инструментов пользовательского интерфейса Android снаружи потока пользовательского интерфейса

Рабочие потоки

Вследствие описанной выше однопоточной модели для динамичности пользовательского интерфейса ваших приложений очень важно не блокировать поток пользовательского интерфейса. Если требуется выполнять операции, занимающие некоторое время, обязательно выполняйте их в отдельных потоках (»фоновых» или «рабочих» потоках).

Например, ниже приведен код контроля нажатий, который загружает изображение из отдельного потока и отображает их в виджете ImageView :

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

Для устранения этой проблемы Android предлагает несколько путей доступа к потоку пользовательского интерфейса из других потоков. Ниже приведен список полезных методов:

Например, можно исправить приведенный выше код с помощью метода View.post(Runnable) :

Теперь реализация является потокобезопасной: сетевая операция выполняется из отдельного потока, тогда как ImageView работает из потока пользовательского интерфейса.

Однако по мере роста сложности, код такого типа может становиться запутанным и сложным для поддержания. Чтобы обрабатывать более сложные взаимодействия с рабочим потоком, можно использовать метод Handler в рабочем потоке для обработки сообщений, поступающих из потока пользовательского интерфейса. Вероятно, самым лучшим решением является расширение класса AsyncTask , которое упрощает выполнение заданий рабочего потока, которые должны взаимодействовать с пользовательским интерфейсом.

Использование AsyncTask

Метод AsyncTask позволяет выполнять асинхронную работу в пользовательском интерфейсе. Он выполняет операции блокирования в рабочем потоке и затем публикует результаты в потоке пользовательского интерфейса без необходимости самостоятельно обрабатывать потоки и/или обработчики.

Для использования этого метода необходимо создать подкласс AsyncTask и реализовать метод обратного вызова doInBackground() , который работает в пуле фоновых потоков. Чтобы обновить пользовательский интерфейс, следует реализовать метод onPostExecute() , который доставляет результат из doInBackground() и работает в потоке пользовательского интерфейса, так что вы можете безопасно обновлять пользовательский интерфейс. Задача выполняется через вызов метода execute() из потока пользовательского интерфейса.

Например, можно реализовать предыдущий пример с помощью метода AsyncTask следующим образом:

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

Прочитайте статью AsyncTask , чтобы полностью понять использование этого класса. Здесь приведен краткий обзор его работы:

  • Можно указывать тип параметров, значения хода выполнения и конечное значение задания с помощью универсальных компонентов
  • Метод doInBackground() выполняется автоматически в рабочем потоке
  • Методы onPreExecute() , onPostExecute() и onProgressUpdate() запускаются в потоке пользовательского интерфейса
  • Значение, возвращенное методом doInBackground() , отправляется в метод onPostExecute()
  • Можно вызвать publishProgress() в любой момент в doInBackground() для выполнения onProgressUpdate() в потоке пользовательского интерфейса
  • Задание можно отменить в любой момент из любого потока

Предупреждение! Другая проблема, с которой вы можете столкнуться при использовании рабочего потока, состоит в непредсказуемом перезапуске действия вследствие изменения конфигурации в режиме выполнения, (например, когда пользователь изменяет ориентацию экрана), что может разрушить рабочий поток. Чтобы увидеть, как можно сохранить задание во время одного из подобных перезапусков и как правильно отменить задание при разрушении действия, изучите исходный код примера приложения Shelves.

Потокобезопасные методы

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

В первую очередь это относится к методам, которые можно вызывать удаленно, например, к методам в связанной службе. Когда вызов метода реализуется в классе IBinder , происходящем из того же процесса, в котором выполняется IBinder , метод выполняется в потоке вызывающего метода. Однако, когда вызов происходит из другого процесса, метод выполняется в потоке, выбранном из пула потоков, которые система поддерживает в том же процессе, что и IBinder (он не выполняется в потоке пользовательского интерфейса процесса). Например, поскольку метод onBind() службы будет вызываться из потока пользовательского интерфейса процесса службы, методы, реализованные в объекте, который возвращает onBind() (например, подкласс, который реализует методы RPC), будут вызываться из потоков в пуле. Так как служба может иметь несколько клиентов, несколько потоков из пула могут одновременно использовать один и тот же метод IBinder . Поэтому методы IBinder должны быть реализованы с сохранением потокобезопасности.

Аналогичным образом поставщик контента может получать запросы данных, которые происходят из другого процесса. Хотя классы ContentResolver и ContentProvider скрывают подробности управления взаимодействием процессов, методы ContentProvider , которые отвечают на эти запросы, —методы query() , insert() , delete() , update() и getType() —вызываются из пула потоков в процессе поставщика контента, а не в процессе потока пользовательского интерфейса. Поскольку эти методы могут вызываться из любого числа потоков одновременно, они также должны быть реализованы с сохранением потокобезопасности.

Взаимодействие процессов

Система Android предлагает механизм взаимодействия процессов (IPC) с помощью удаленного вызова процедуры (RPC), при котором метод вызывается действием или другим компонентом приложения, но выполняется удаленно (в другом процессе) с возвратом всех результатов вызывающему компоненту. Это влечет разложение вызова метода и его данных до уровня, понятного операционной системе, передачу его из локального процесса и адресного пространства удаленному процессу и адресному пространству, а затем повторную сборку и восстановление вызова. После этого возвращенные значения передаются в обратном направлении. Система Android содержит все коды для выполнения этих механизмов IPC, так что вы можете сосредоточиться на определении и реализации программного интерфейса RPC.

Для выполнения IPC приложение должно быть привязано к службе с помощью метода bindService() . Дополнительные сведения представлены в разделе Службы руководства для разработчиков.

Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.

Сообщества › Электронные Поделки › Блог › WebastoGSM. Часть 4. Андроид – шмандроид…

Всем привет! Продолжим…
Ну вот, все собрано и отлажено, и работает как часы. Но набирать вручную команды в виде SMS как то лениво :) Решено, пишем приложение под Android!
Правда я этого никогда не делал, но когда это нас останавливало :)
Итак, под андроид никогда не писал, яву не знаю, но что-то же нужно с этим делать. Набрел на сайт startandroid.ru/ru/, и давай форум читать вдоль и поперек, и чем больше читал, тем больше мне не нравилась эта затея. В итоге отважился и скачал себе Andriod Studio, сел разбираться, как вдруг, совершенно случайно, в разговоре, мой коллега обмолвился о существовании онлайн IDE для написания программ под андроид, в которых программу может написать даже домохозяйка. И снова я пошел гуглить, и нагуглил. Разрешите представить Вам — MIT App Inventor 2 . Отличный инструмент для написания программ под андроид, интуитивно понятный, разобраться в нем не составит труда. Во Вконтакте есть группы посвященные MIT App Inventor 2, если что, Вам там с радостью помогут, а на youtube можно найти видео уроки.
По сути это графическая IDE, выглядит программирование в ней следующим образом:

Как видите, ничего сложного, единственная минус – это иногда появляющиеся ошибки при компиляции. Связанно это с корявым переводом на русский язык, поэтому в случае ошибок компиляции, нужно переключить язык на английский, и все компилируется на ура, естественно если нет синтаксических ошибок :)
В общем времени на написание программы ушло не мало, по ходу написания программы и изучения IDE, постоянно хотелось что ни будь улучшить, процесс затягивает, к тому же к процессу написания программы подключился мой коллега, ему тоже стало интересно :) В итоге мои задумки + его задумки, и на свет у нас появилось приложение — «WebastoGSM»!
Да, возможно оно не такое оптимизированное и шустрое, как могло бы быть, будь оно написано в Android Studio, но скорость написания приложения решает :) От процесса написания программы в MIT App Inventor 2 получаешь удовольствие, чего не скажешь об Android Studio.

Итак, разрешите представить — «WebastoGSM»!
Иконка приложения выглядит следующим образом:

Создание таймера CountDownTimer в Android на примере

Проблема

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

Решение

Платформа Android поставляется со встроенным классом для создания таймера обратного отсчета CountDownTimers . Он прост в использовании, эффективен и работоспособен (это само собой разумеется!).

Обсуждение

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

  1. Создайте подкласс Конструктор этого класса принимает два аргумента: CountDownTimer (long millisInFuture, long countDownlnterval) . Первый — это количество миллисекунд с того момента, когда нужно сделать интервал; на данный момент будет вызываться метод подкласса onFinish() . Второй — частота в миллисекундах, определяющая, как часто вы хотите полу­чать уведомления о том, что таймер все еще работает. Это типично для обнов­ления монитора прогресса или другого обмена данными с пользователем. Ваш метод подкласса onTick() будет вызываться каждый раз по истечении данного количества миллисекунд.
  2. Переопределите методы onTick()
  3. Создайте новый экземпляр класса
  4. Вызовите метод start() для вновь созданного экземпляра!

Пример программы для таймера обратного отсчета состоит из компоновки XML (показанной в примере 1) и некоторого кода Java (показанного в примере 2). При запуске он должен выглядеть примерно так, как показано на рис. 1, хотя вре­мя, вероятно, будет иным.

Пример 1. Файл main.xml

Пример 2. Файл Main, java

Рис. 1. Сброс таймера

URL-адрес для загрузки исходного кода

Исходный код этого примера можно посмотреть и скачать на странице Github.

Android UI thread

Большая часть кода Android приложения работает в контексте компонент, таких как Activity, Service, ContentProvider или BroadcastReceiver. Рассмотрим, как в системе Android организованно взаимодействие этих компонент с потоками.

При запуске приложения система выполняет ряд операций: создаёт процесс ОС с именем, совпадающим с наименованием пакета приложения, присваивает созданному процессу уникальный идентификатор пользователя, который по сути является именем пользователя в ОС Linux. Затем система запускает Dalvik VM где создаётся главный поток приложения, называемый также «поток пользовательского интерфейса (UI thread)». В этом потоке выполняются все четыре компонента Android приложения: Activity, Service, ContentProvider, BroadcastReceiver. Выполнение кода в потоке пользовательского интерфейса организованно посредством «цикла обработки событий» и очереди сообщений.

Рассмотрим взаимодействие системы Android с компонентами приложения.

Activity. Когда пользователь выбирает пункт меню или нажимает на экранную кнопку, система оформит это действие как сообщение (Message) и поместит его в очередь потока пользовательского интерфейса (UI thread).

Service. Исходя из наименования, многие ошибочно полагают, что служба (Service) работает в отдельном потоке (Thread). На самом деле, служба работает так же, как Activity в потоке пользовательского интерфейса. При запуске локальной службы командой startService, новое сообщение помещается в очередь основного потока, который выпонит код сервиса.

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

ContentProvider. Вызов локального ContentProvider немного отличается. ContentProvider также выполняется в основном потоке но его вызов является синхронным и для запуска кода ContentProvider не использует очередь сообщений.

Исходя из вышесказанного можно заметить, что если главный поток в данный момент обрабатывает пользовательский ввод или выполняет иное действие, выполнение кода, полученного в новом сообщении, начнётся только после завершения текущей операции. Если какая либо операция в одном из компонентов потребует значительного времени выполнения, пользователь столкнётся или с анимацией с рывками, или с неотзывчивыми элементами интерфейса или с сообщением системы «Приложение не отвечает» (ANR).

Цукерберг рекомендует:  Профессия веб-дизайнер

Для решения данной проблемы используется парадигма параллельного программирования. В Java для её реализации используется понятие потока выполнения (Thread).

Thread: поток, поток выполнения, иногда ещё упоминается как нить, можно рассматривать как отдельную задачу, в которой выполняется независимый набор инструкций. Если в вашей системе только один процессор то потоки выполняются поочередно (но быстрое переключение системы между ними создает впечатление параллельной или одновременной работы). На диаграмме показано приложение, которое имеет три потока выполнения:

Но, к сожалению, для взаимодействия с пользователем, от потока мало пользы. На самом деле, если вы внимательно посмотрите на диаграмму выше, вы поймёте, что как только поток выполнить все входящие в него инструкции он останавливается и перестаёт отслеживать действия пользователя. Чтобы избежать этого, нужно в наборе инструкций реализовать бесконечный цикл. Но возникает проблема как выполнить некое действие, например отобразить что-то на экране из другого потока, иными словами как вклиниться в бесконечный цикл. Для этого в Android можно использовать Android Message System. Она состоит из следующих частей:

Looper: который ещё иногда ещё называют «цикл обработки событий» используется для реализации бесконечного цикла который может получать задания используется. Класс Looper позволяет подготовить Thread для обработки повторяющихся действий. Такой Thread, как показано на рисунке ниже, часто называют Looper Thread. Главный поток Android на самом деле Looper Thread. Looper уникальны для каждого потока, это реализованно в виде шаблона проектирования TLS или Thread Local Storage (любопытные могут посмотреть на класс ThreadLocal в Java документации или Android).

Message: сообщение представляет собой контейнер для набора инструкций которые будут выполнены в другом потоке.

Handler: данный класс обеспечивает взаимодействие с Looper Thread. Именно с помощью Handler можно будет отправить Message с реализованным Runnable в Looper, которая будет выполнена (сразу или в заданное время) потоком с которым связан Handler. Код ниже иллюстрирует использование Handler. Этот код создаёт Activity которая завершиться через определённый период времени.

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

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

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

Подготовлено на основе материалов AndroidDevBlog

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

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

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

Handler — маленький помошник Andro >

Всем привет, хочу поделиться методологией применения инструмента созданного облегчить разработку приложений (на сцену выходит 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»);
>
>

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 более удобный.

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

Пример использования Java Timer и TimerTask

Java Timer и TimerTask. Теория

Класс java.util.Timer является служебным и может быть использован для отложенного запуска потока в определенное время. Класс Java Timer может быть использован для запланированной задачи, которая должна будет выполниться один раз или запускаться регулярно в определенное время.

Класс java.util.TimerTask является абстрактным классом, который реализует интерфейс Runnable. Мы должны унаследоваться от этого класса, создавая наш собственный TimerTask.

  • Класс Timer является потокобезопасным, поэтому несколько потоков могут совместно использовать один объект Timer без необходимости внешней синхронизации.
  • Класс Timer использует java.util.TaskQueue для выполнения задач с определенной периодичностью.
  • В одно и то же время может быть выполнен только один поток TimerTask. Например, если вы создаете Timer, который должен запускаться каждые 10 секунд, но выполнение одного потока занимает целых 20 секунд, то объект Timer будет добавлять задачи в очередь пока один поток не закончит свое выполнение. Как только он отработает свои 20 секунд, то об этом будет уведомлена очередь и следующий поток из этой очереди начнет работать.

Класс Timer использует методы wait() и notify() для планирования задач.

Java Timer и TimerTask. Практика

Давайте на примере рассмотрим использование Timer и TimerTask в Java:

Многопоточность в 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 потоке), но они должны выполняться с некоторой задержкой:

Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих