Технология разработки мобильных приложений: учебно-методическое пособие
Министерство науки и высшего образования Российской Федерации
Тольяттинский государственный университет
Институт математики, физики и информационных технологий
Кафедра «Прикладная математика и информатика»
ТОНКИХ АРТЁМ ПЕтрович
технология разработки мобильных приложений
Учебно-методическое пособие
для студентов ИТ-направлений подготовки
очной и заочной форм обучения
Тольятти
Издательство ТГУ
2022
УДК 004.4
ББК 32.973.5
Рецензенты:
к.п.н., доцент кафедры прикладной математики и информатики
Тольяттинского государственного университета О.А. Крайнова;
к.п.н., доцент кафедры вычислительной техники Самарского государственного технического университета Е.В. Панюкова
Тонких Артём Петрович Технология разработки мобильных приложений: учеб.-метод. пособие / Артём Петрович Тонких. – Тольятти : ТГУ, 2022. – 106 с.
Учебно-методическое пособие содержит теоретические представления о технологии разработки мобильных приложений. В пособии приведены примеры проектных решений, позволяющие сформировать практические навыки применения инструментов среды разработки Android Studio для решения задач профессиональной деятельности.
Учебно-методическое пособие предназначено для использования при работе со студентами в рамках работы МИЦ IT Student и может быть полезно студентам, профессорско-преподавательскому составу высших учебных заведений, а также любому желающему получить знания в области разработки мобильных приложений.
Рекомендовано к изданию научно-методическим советом Тольяттинского государственного университета.
© ФГБОУ ВО «Тольяттинский государственный университет», 2022
© Артём Петрович Тонких, 2022
Оглавление
Глава 1. Основы проектирования и разработки мобильных приложений 6
Тема 1. Знакомство с операционной системой Android 6
Тема 3. Элементы управления 21
Тема 4. Множественные активности и интенты 30
Тема 5. Управление жизненным циклом Activity 41
Глава 2. Многопоточность в мобильных приложениях 81
Тема 9. Многопоточность в Java 81
Тема 10. Потоки в ОС Android 84
Тема 11. Взаимодействие потоков 99
Учебно-методическое пособие «Технология разработки мобильных приложений» предназначено для студентов, обучающихся по направлению подготовки 09.03.03, очная, заочная (в т.ч. с использованием ДОТ), и всех тех, кто хочет получить базу теоретических знаний и практических навыков в области разработки мобильных приложений.
В предлагаемом учебно-методическом пособии представлены базовые темы для начала разработки приложений для мобильных устройств на платформе Android.
Цель данного пособия заключается в представлении основных программных компонентов и решений для разработки мобильных приложений под управлением операционной системы Android.
К задачам учебно-методического пособия относятся:
Заложить основы знаний по разработке приложений для мобильных устройств операционной системы Android.
Рассмотреть этапы разработки мобильного приложения.
Проверить знания теоретического материала с помощью вычленения ответов на сформированные в конце глав контрольных вопросов и выполнения практических заданий.
Реализовать пилотный проект для осуществления процесса разработки мобильного приложения для операционной системы Android.
Первая глава пособия направлена на знакомство с основными понятиями в области разработки мобильных приложений. Она описывает средства разработки, основные компоненты, способы создания, компиляции и развёртывания проекта. Вторая глава рассматривает основы параллельного выполнения задач в языке Java.
В результате изучения учебно-методического пособия обучающийся должен:
знать:
особенности архитектуры и аппаратной среды мобильных устройств;
особенности архитектуры мобильных устройств с точки зрения программирования;
основные приемы разработки программ для мобильных устройств;
возможности инструментария Java по разработке мобильных приложений;
особенности реализации пользовательского интерфейса в мобильных устройствах;
устройство и архитектуру ОС Android;
основные компоненты архитектуры мобильных платформ;
жизненный цикл мобильных приложений и их структуру;
основные элементы пользовательского интерфейса мобильных приложений;
работу с файлами, базами данных, пользовательскими настройками в мобильных устройствах;
инструменты для программирования и основ проектирования мобильных приложений;
возможности инструментария для разработки приложений для ОС Android;
уметь:
использовать и применять на практике полученные знания для проектирования и создания мобильных приложений на современном уровне;
самостоятельно разрабатывать приложения и программы для различных платформ и устройств под управлением операционных систем Android;
владеть навыками:
разработки и программной реализации мобильных приложений;
работы с инструментами программирования и отладки мобильных приложений.
Глава 1. Основы проектирования и разработки мобильных приложенийВ этой теме мы поговорим о мобильной разработке в целом, сделаем первые шаги в мир индустрии программирования приложений.
Что вам понадобится для полноценного изучения курса?
Для начала это знание любого объектно-ориентированного языка, будь то Java, C#, Python, Objective-C, а также языка разметки XML – по синтаксису он почти идентичен языку HTML.
Следующим пунктом для быстрого погружения в мир разработки будет знание английского языка. Английский язык является языком международного общения, и, как правило, все новости, статьи и материалы выходят именно на этом языке. Поэтому знать его непременно нужно, если вы нацелены на дальнейшее развитие своих навыков.
На курсе мы установим среду интегрированной разработки под названием Android Studio, системные требования которой доступны по ссылке http://developer.android.com/studio/#Requirements.
Данная программа достаточно мощная, она собирает код и ресурсы приложения, давая на выходе уже готовое приложение.
Обратите внимание, что Android Studio – достаточно тяжеловесная программа. Нежелательно запускать одновременно с ней игры, приложения и сторонние программы во избежание зависания программы.
Теперь поговорим немного о системе Android.
Android – сейчас наиболее популярная операционная система для смартфонов, планшетов, электронных книг, цифровых проигрывателей, наручных часов, фитнес-браслетов, игровых приставок, ноутбуков, нетбуков, смартбуков, телевизоров, очков Google Glass, бытовых роботов и других устройств.
Благодаря этой операционной системе у нас имеется огромный выбор смартфонов по цене от $50, что почти в шесть раз дешевле продуктов от компании Apple.
Мобильные устройства на платформе Android быстро дешевеют в связи с высокой конкуренцией между производителями.
«Система с открытым исходным кодом» означает, что любой разработчик может бесплатно скачать исходный код Android, изменить его и опубликовать свою версию.
Многие компании так и делают, что позволяет им выпускать недорогие устройства и продавать по всему миру. В результате всё больше людей начинают пользоваться смартфонами и мобильным интернетом под эгидой Android'а.
Становиться разработчиком мобильных приложений под Android весьма выгодно, потому что аккаунт разработчика стоит всего $25. В результате появляется возможность показать свои приложения всему миру через официальный маркет Google Play.
Потребуется единоразово приобрести аккаунт разработчика всего за $25 в отличие от аккаунта системы iOS, который требует оплаты $99 ежегодно.
Android является катализатором экономического роста.
По данным журнала Forbes, в России в сфере мобильных технологий работают более миллиона человек.
В 2003 году инженер-программист Энди Рубин основал компанию Android Incorporated, которая разработала мобильную операционную систему Android, а в 2005 году эта компания была куплена Google.
Первый телефон, который работал на Android, поступил в продажу в 2008 году, и назывался он T-Mobile G1 (рис. 1).
В первой версии Android не было возможности воспроизведения видео, не было браузера, не было даже виртуальной клавиатуры.
Рис. 1. T-Mobile G1
С тех пор прошло много лет, было выпущено множество версий системы, и в каждой версии добавлялось всё больше и больше функционала, так привычного нам сейчас.
Система становилась производительнее, защищённее, да и во многом приветливее для пользователя.
Помимо этого, система стала доступна не только для телефонов, а также охватила широкий круг устройств: от часов до автомобилей.
Давайте немного поговорим о разработке.
В качестве языка программирования для приложений на Android был выбран язык Java.
Возможно, это произошло из-за широкой известности языка или из-за меньшего порога вхождения относительно других C-подобных языков.
Но, несомненно, одним из самых главных плюсов стало то, что язык работает на виртуальной машине, и, как следствие, приложение не нужно заново компилировать для каждого нового устройства.
В первых Android использовалась шестая версия Java, с 2013 года Android стал поддерживать седьмую Java, а начиная с версии Android 7.0 Google добавил поддержку восьмой версии.
К сожалению, устройства не обновляются так быстро, и даже сейчас, чтобы охватить хотя бы 80 % устройств, необходимо поддерживать четвёртый Android. Соответственно, пока в коммерческих проектах не удаётся использовать все возможности восьмой Java.
Однако есть способ использовать новый функционал языка с помощью различных библиотек.
Весной 2017 года Google объявил об официальной поддержке Kotlin в качестве основного языка разработки под Android.
Kotlin – это очень мощный JVM-язык, в котором много новых интересных возможностей, недоступных на Java. Поэтому Kotlin стал быстро набирать популярность.
Помимо Java и Kotlin на Android можно писать и на других JVM-языках: Scala, Groovy и прочих.
Но они менее распространены и не имеют широкой поддержки среди разработчиков.
С развитием сообщества всё больше и больше стало появляться новых инструментов, помогающих в разработке.
Появились библиотеки, упрощающие взаимодействие с сервером, фреймворки для хранения данных, какие-то архитектурные решения.
Так как нам приходится поддерживать старые версии устройств, ощутимое количество библиотек было направлено на поддержку нового функционала в старых версиях Android, на так называемую обратную совместимость.
А с выходом гугловской библиотеки AppCompat поддерживать старые устройства стало намного проще.
Ответы практически на любые вопросы можно найти в официальной документации.
Система Android представляет собой разностороннюю платформу приложений, на основе которой можно создавать приложения и игры для мобильных устройств в среде языка Java.
Android – это операционная система, основанная на ядре Linux.
Android разработан в Android Open Source Project, и сейчас данный проект возглавляет Google.
Операционную систему Android можно разделить на четыре области, как показано на рис. 2.
Рис. 2. Внутренняя архитектура Android
Разработчик Android обычно работает с двумя верхними слоями для создания новых приложений для платформы Android.
Остановимся более подробно на уровнях.
Уровни можно охарактеризовать по следующим слоям.
1-й уровень – приложения.
Он содержит кастомные приложения, такие как браузер, камера, галерея, музыка и телефон.
Следующий уровень – это структура приложения, которая позволяет осуществлять взаимодействия на высоком уровне системы Android.
Третий уровень содержит библиотеки и среду выполнения Runtime.
Библиотеки созданы для работы многих общих функций фре́ймворка, таких как графический рендеринг, хранение данных, просмотр web-страниц, а выполняемая среда Runtime – для работы с приложениями Android.
И последний, четвертый уровень – ядро Linux.
Это уровень связи для базового оборудования, а именно запуск камеры, работа дисплея телефона, включение Bluetooth и прочее.
Разработка приложения для Android-платформы начинается с установки пакета JDK 8 и заканчивается запуском на устройстве приложения "Hello World!".
Алгоритм установки можно условно разделить на три пункта.
Первый пункт – это установка инструмента Java Standard Edition Development Kit 8.
Возможно, вы заметите, что на сайте Oracle вышла уже 17-я версия Java, но не спешите устанавливать её, поскольку она всё ещё нестабильна в связке с Android Studio и, по словам многих разработчиков Android, она может стать причиной ошибок, поэтому мы устанавливаем стабильную 8-ю версию Java, чтобы у вас всё работало и запускалось.
Если у вас уже установлена Java, то переходите сразу к установке программы Android Studio.
Следующим пунктом на пути установки программного обеспечения является установка интегрированной среды разработки Android Studio, но кроме неё существуют и другие среды, как NetBeans, Eclipse, Visual Studio, IntelliJ IDEA.
Поскольку рекомендуемой средой разработки, согласно документации, является всё же Android Studio, мы её и будем использовать.
Прекрасным и вдохновляющим стартом для вас будет то, что всё программное обеспечение абсолютно бесплатно и подходит для всех операционных систем, будь то Windows, Mac или Linux.
Устанавливайте только с официальных сайтов и смело запускайте у себя на компьютере. И последним пунктом является установка виртуального эмулятора, если у вас нет реального физического устройства.
Теперь мы познакомимся с интерфейсом программы Android Studio.
Вы можете открыть проект или создать новый проект, чтобы рассмотреть детально все панели и кнопки управления для работы со средой разработки (рис. 3).
Рис. 3. Интерфейс среды Android Studio
В правом верхнем углу у нас располагается панель Toolbar, которая предоставляет быстрый доступ к наиболее часто используемым командам.
Например, здесь находится команда для запуска приложения.
Здесь также есть команда старта SDK менеджера – она запускает его из самой программы, а не из консоли стартового окна.
Если нам потребуется подгрузить дополнительные компоненты, то мы можем, не выходя из программы, запустить Manager либо запустить Manager из вкладки Tools.
В левой части панели находится панель навигации – она находится напротив панели Toolbar.
Она называется Navigation bar и помогает перемещаться по проекту и открывать файлы для редактирования, тем самым обеспечивая более сжатый вид структуры проектов из окна Project.
Окно редактора позволяет создавать и редактировать код.
В зависимости от типа открытого файла вид редактора может измениться. Например, при просмотре файла макета activity_main окно редактора отображает редактор макета.
Мы можем переместиться на вкладку Design, чтобы увидеть, как выглядит наш интерфейс.
На этот макет мы можем перемещать кнопки, текст, картинки и другие компоненты из вкладки Palette.
Сейчас мы находимся в окне Project (рис. 4).
Рис. 4. Структура папок проекта
Здесь также есть окно Android – более сжатый вид, и для того чтобы раскрыть проект, чтобы рассмотреть все компоненты детально, мы можем перейти на вкладку Project.
В нижней части программы находится окно инструментов, предоставляющее доступ к определенным задачам. Например, управление проектами, поиск, отображение информации о процессе запуска приложения и так далее.
Данное окно можно свернуть и развернуть.
Status bar – это нижняя панель программы. Она отображает состояние проекта и самой программы, а также различные сообщения и предупреждения.
В любой момент вы можете выполнить поиск по командам и элементам, просто щелкнув на увеличительное стекло в правом верхнем углу окна Android Studio либо дважды нажать на клавишу Shift.
Чтобы не потеряться в проекте, мы изучим иерархию файлов.
Раскроем список и увидим полную структуру папок проекта.
Папки проекта выглядят точно так же, как на диске, где сохранён наш проект.
В данной структуре проекта много файлов и папок.
Если раскрыть папку app, мы увидим следующие папки: build, libs и src.
В папке build хранятся файлы, создаваемые системой в процессе компиляции.
Лучше там ничего не менять.
Папка libs – папка для сторонних библиотек, подключаемых в проект.
Папка src – это папка для исходного кода и ресурса.
Внутри src находится папка main (рис. 5). Это основная рабочая папка, с которой мы будем всегда работать.
Рис. 5. Содержание папки src.
Внутри папки main находятся две подпапки. Это java и res – соответственно, папка для кода и папка для ресурсов.
Раскроем папку res.
Данная папка содержит все ресурсы для нашего приложения, включая изображения, файлы макетов экранов, строковые ресурсы, значки, иконки, цвета и стили оформления.
Она включает следующие вложенные папки.
Это drawable – здесь у нас сохраняются все изображения для нашего приложения.
Вторая папка – drawable-v24 – сохраняет изображения для версии Android 24 и выше.
Следом идет папка layout – она содержит файлы макетов для activity-экранов приложения.
В настоящее время наше приложение имеет одну activity [акти́вити] с файлом макета activity_main.
Открыть файл макета мы можем, раскрыв папку и дважды нажав на название файла.
Следом идёт папка mipmap – она содержит иконки запуска приложений. Это те иконки, которые отображаются на Android-устройстве после установки приложения.
Всего 6 папок под таким названием, но с разными разрешениями, куда надо вставлять изображения под каждое разрешение экрана, чтобы иконка была высокого качества на больших дисплеях и не была размытой на маленьких экранах смартфонов.
Папка values содержит ресурсы: цвета, строки и стили, используемые в приложении.
Также в окне структуры проекта есть два файла с именем build.gradle.
Это файлы сборки для системы Gradle, которую используют для компиляции, построения и упаковки приложений и библиотек.
Файл сборки уровня проекта содержит настройки для всего проекта.
Файл сборки уровня модуля app содержит настройки для модуля.
Чаще всего мы будем работать именно с файлом сборки уровня модуля.
Файл сборки уровня модуля содержит такие основные секции, как android, buildTypes и dependencies.
В android указаны версии инструментов разработки, минимальная поддерживаемая версия, целевая поддерживаемая версия, версия приложения для Google Play, название версии, инструмент для тестирования и другие параметры.
В показанном на рис. 6 примере минимальная поддерживаемая версия – 21-я.
Рис. 6. Содержимое файла build.gradle уровня app
В показанном на рис. 6 примере целевая поддерживаемая версия – 30-я.
Секция dependencies содержит список библиотек, подключаемых к проекту.
Здесь могут быть подключены как локальные, помещенные в папку libs, так и хранящиеся удаленно библиотеки.
Все файлы кода на языках Kotlin и Java организованы в папке java.
Папка java содержит одну папку с доменом нашего проекта.
Папка java содержит файлы исходного кода Kotlin и Java для нашего приложения.
Мы познакомились с интерфейсом программы Android Studio.
Теперь мы научимся работать в редакторе макета.
Второй вид окна редактора отображает код Java в файле MainActivity.java.
Откроем файл MainActivity (рис. 7).
Рис. 7. Содержимое файла MainActivity.java
Данный класс является activity.
Термин переводится как «активность» или «деятельность», но никто его не называет активностью или деятельностью.
Используется термин просто activity без перевода.
MainActivity изначально был создан средой разработки, когда мы создавали новый проект.
Каждая activity в приложении для Android имеет свой макет, который определяет пользовательский интерфейс.
На 12-й строчке присваивается макет под именем activity_main.
Для того чтобы их различать, запомните: в activity мы пишем логику приложения – соответственно, пишем код Java для работы с компонентами, а в макете мы настраиваем пользовательский интерфейс – соответственно, пишем код XML или перетаскиваем с панели Palette кнопки, изображения и так далее.
В Android Studio есть редактор макетов, в котором можно настраивать и определять макеты.
Сами макеты описаны на языке разметки XML.
Редактор макета позволяет нам определять и изменять макет путем написания кода XML либо работать с компонентами через интерактивный визуальный редактор.
Чтобы открыть макет под именем activity_main, необходимо его найти в папке ресурсов.
Чтобы открыть макет под именем activity_main, нужно раскрыть папку res.
В папке layout находится наш макет activity_main.
Чтобы его открыть, нужно выполнить по нему двойной щелчок мыши.
Чтобы отобразить визуальный редактор макета, нужно открыть вкладку Design.
По умолчанию у нас открываются два экрана устройства: на одном мы видим сам макет дизайна, а на втором экране – макет с отображением привязок.
Он называется Blueprint.
Макет дизайна слева показывает, как наше приложение появляется на устройстве.
Схема чертежа, показанная справа, представляет собой схематический вид макета.
Используйте значок компоновки в левом верхнем углу панели инструментов дизайна, чтобы отобразить проектный вид либо вид чертежа или оба вида вместе.
Последняя функция в данном списке – это обновление отображения, если вдруг в макете не отображаются обновленные данные.
Используйте значок ориентации для смены ориентации макета.
Существуют два варианта ориентации экрана – это вертикальный и горизонтальный вид.
На рис. 8 представлен вертикальный вид.
Рис. 8. Макет дизайна
Можно настроить ночной режим и режимы для разных устройств: телевизоров, часов, виртуальных очков и машин.
Можно создать новый макет для горизонтального отображения, планшета или еще дополнительный вид по желанию.
Используйте меню выбора устройств, чтобы видеть, как макет будет выглядеть на разных Android-устройствах с разными версиями Android, разрешением и плотностью экранов.
Затем у нас следуют две вкладки: настройка темы и настройка локализации. Последняя означает добавление строковых ресурсов для мультиязычного приложения.
В правом нижнем углу редактора макетов вы видите кнопки «плюс» и «минус» для увеличения и уменьшения масштаба.
Используйте эти кнопки для настройки вида или нажмите кнопку Zoom to Fit Screen снизу от кнопок масштабирования, чтобы обе панели соответствовали нашему экрану.
Теперь настало время построения макетов.
К сожалению, Android Studio по умолчанию во всех проектах добавляет родительский макет ConstraintLayout (рис. 9).
Рис. 9. Редактор макета
Для новичков, которые впервые сталкиваются с макетами и самим кодом XML, понять данный макет будет сложно, поэтому мы заменим ConstraintLayout на LinearLayout, то есть линейный макет.
Чтобы заменить ConstraintLayout на LinearLayout, выделим и удалим строку с пакетом ConstraintLayout и впишем название нового макета: LinearLayout.
Обратите внимание, что студия начинает подсвечивать код, если видит, что есть ошибки в нашем файле.
Нам потребуется удалить лишний код, который принадлежит ConstraintLayout.
У LinearLayout есть обязательный атрибут для ориентации родительского макета.
В данном случае атрибут называется orientation.
Мы выберем значение «вертикально».
Как вы заметили, кода стало намного меньше, поэтому начинать работу по построению интерфейса необходимо с легких родительских макетов.
Что же представляет собой компонент View?
Он может содержать картинку, часть текста, кнопку или элемент, который может отобразить макет экрана.
Несколько элементов View создают индивидуальный интерфейс приложения.
Всё, с чем вы взаимодействуете в приложении, называется пользовательским интерфейсом, или user interface на английском, или еще короче – UI.
Мы рассмотрим три наиболее часто используемых элемента View: это TextView, Button и ImageView.
TextView существует для отображения текста (рис. 10).
Рис. 10. TextView
ImageView предназначен для отображения картинок, а Button – для работы с кнопками.
Так как элемент TextView у нас уже определен в макете, можно добавить ImageView.
Добавить элементы – картинки и кнопки – можно как с помощью программного кода, так и с помощью визуального редактора.
При добавлении напротив элементов ImageView и Button может появиться предупреждение о возможных ошибках.
При наведении на элемент появляется подробное описание возникшей ошибки.
Рассмотрим основной элемент управления, называемый View.
View-класс представляет собой основной строительный блок для компонентов пользовательского интерфейса.
Вид занимает прямоугольную область на экране и отвечает за рисование и обработку событий.
Также View – это базовый класс для элементов, которые используются для создания компонентов интерактивного интерфейса, таких как кнопки, TextView, ImageView и так далее.
Первый элемент – это надпись.
Для простого вывода текста на экран предназначен элемент TextView. Вероятно, это самый распространённый элемент интерфейса. Он просто отображает текст без возможности его редактирования.
Иногда встречаются TextView, которые используются в качестве плоских кнопок, без видимой рамки.
Ему также можно назначить атрибуты для выравнивания текста внутри блока, назначить шрифт, направление текста, цвет текста и многое другое.
Часто используемые атрибуты:
text – сам текст;
textSize – размер шрифта;
textColor – цвет текста
textStyle – обычный, полужирный, курсив;
textAlignment или gravity – выравнивание текста внутри TextView.
Следующий элемент – текстовое поле – является подклассом класса TextView.
Он также представляет текстовое поле, но теперь уже с возможностью ввода и редактирования текста.
Таким образом, в EditText мы можем использовать всё те же возможности, что и в TextView.
Из тех атрибутов, что не рассматривались для TextView, следует отметить атрибут android:hint.
Он позволяет задать текст, который будет отображаться в качестве подсказки, если элемент EditText пуст.
Кроме того, мы можем использовать атрибут InputType, который позволяет задать клавиатуру для ввода. В качестве вариантов есть просто текст, номер, электронная почта, пароль, дата и многие другие.
В частности, среди его значений можно выделить следующие: textPassword – клавиатура для ввода пароля, phone – клавиатура для ввода номеров, textAutoCorrect – автоматически исправляет вводимый текст – и textCapSentences – при вводе первого введённого символа слова представляет заглавную букву, а остальные – строчные, как в предложениях.
Также есть возможность кастомизировать кнопку ввода на клавиатуре и показать вместо неё какую-нибудь другую через атрибут imeOptions: варианты actionDone, actionSearch, actionNext и другие.
Также для того чтобы задать максимальное количество символов в элементе, используется атрибут maxLength.
Следующий элемент – это кнопка (рис. 11). Он является одним из часто используемых элементов, которые представлены классом android.widget.Button. По сути, Button – это тот же TextView, но с особенным стилем отображения, и это выражается в том, что в Android класс Button – наследник класса TextView. Естественно, можно использовать все атрибуты из класса TextView. Ключевой особенностью кнопок является возможность взаимодействия с пользователем через нажатие с помощью метода onClick, который задаёт обработчик нажатия кнопки.
Для того чтобы при нажатии на кнопку что-то происходило, на неё нужно навесить слушателя. Возможностей две – в коде и в XML. В коде мы вызываем метод кнопки setOnClickListener и передаём в качестве аргумента View.OnClickListener и переопределяем его метод onClick. В XML мы добавляем к кнопке атрибут onClick и передаём туда название метода, который должен вызываться при нажатии. Сам метод должен в качестве аргумента принимать View и иметь модификатор доступа public. Также существует вариант кнопки с изображением вместо текста. Он называется ImageButton и, в свою очередь, наследуется от ImageView. Для ImageButton можно задать изображение через атрибут src. При этом можно убрать сам фон кнопки, оставив только изображение, задав значение background как @null. Чтобы вывести на кнопке текст, справа от которого находится графическое изображение, используйте атрибут android:drawableRight и укажите нужное изображение, путь к нему, или android:drawableLeft – для вывода изображения слева. Также можно вывести изображение над и под текстом.
Рис. 11. Кнопки
Следующий элемент – это двухпозиционная кнопка (рис. 12).
Компонент ToggleButton по своей функциональности похож на флажок CheckBox или переключатель RadioButton.
Рис. 12. Элемент управления ToggleButton
ToggleButton – кнопка, которая может находиться в одном из двух состояний: активном со значением On и неактивном со значением Off.
ToggleButton находится в разделе Widgets.
По умолчанию ToggleButton находится в выключенном состоянии.
Основное событие для ToggleButton – это изменение состояния кнопки onToggleClicked.
Схожим элементом, как ToggleButton, является переключатель (рис. 13). Он появился в Android версии 4.0.
Рис. 13. Элемент управления Switch
Находится в разделе Commands и в разделе Buttons.
Фактически это замена немного устаревшего ToggleButton.
В новых проектах лучше использовать всё-таки Switch.
Компонент используется в том случае, когда нужно переключиться на противоположное состояние: да/нет, включить/выключить, открыть и закрыть.
Момент переключения можно отслеживать при помощи прослушки событий onSwitchClicked.
Элемент CheckBox является флажком, с помощью которого пользователь может отметить определённую опцию (рис. 14). Компонент находится в группе Buttons. Для управления состоянием флажка используйте методы setChecked или isChecked.
Рис. 14. Элемент управления CheckBox.
Чтобы узнать текущее состояние флажка, следует вызвать метод isChecked.
Следующий элемент – переключатели, или RadioButton на английском (рис. 15).
Главная особенность элемента состоит в том, что он не используется в одиночестве.
Всегда должно быть два и более переключателей, и только один из них может быть выбранным.
Рис. 15. Переключатели (RadioButton)
Checkbox и RadioButton – элементы интерфейса, которые используются для выбора тех или иных опций, причём CheckBox’ы используются для множественного выбора, а RadioButton – для единственного, в пределах своей RadioGroup.
Находится RadioButton в разделе Widgets.
Компоненты RadioButton используются в составе контейнера RadioGroup.
Обратите внимание на два момента.
Первое: в контейнеры RadioGroup можно включать не только RadioButton, но и другие элементы – например, картинки и текст.
Второе: переключатели работают в своём контейнере, то есть если у вас два контейнера RadioGroup, то переключатели из одного контейнера никак не повлияют на поведение переключателя из второго контейнера.
Чтобы определить, какой переключатель будет установлен, используйте метод getCheckedRadioButtonId.
В принципе, и на CheckBox и на RadioButton можно повесить обработчик нажатия onClickListener. Однако он не даёт никакой информации о состоянии view, выделена она или нет. Это нужно будет выяснять отдельно, через метод isChecked().
Раскрывающийся список Spinner из раздела Widgets похож на выпадающий список ComboBox, используемый в операционной системе Windows.
В закрытом состоянии компонент показывает одну строчку, при раскрытии – выводит список в виде диалогового окна с переключателями.
Следующий элемент – это графическое представление ImageView.
Компонент ImageView предназначен для отображения изображений.
Он находится в разделе Images.
Для загрузки изображения в xml-файле используется атрибут android:src.
Здесь также у нас тот самый атрибут contentDescription, который позволяет писать описание картинки.
ImageView является базовым элементом-контейнером для использования графики.
Можно загружать изображения из разных источников – например, из ресурсов программы или из сети.
Следующий элемент – это графическая кнопка ImageButton. Он находится в разделе Widgets.
Компонент ImageButton представляет собой кнопку с изображением вместо текста.
По умолчанию ImageButton похож на обычную кнопку.
Следующий элемент – это прокручиваемые представления. Он существует для того, чтобы добавить вертикальную полосу прокрутки или заключить существующий макет в элемент ScrollView, как и группы RadioButton.
Каждый Android-проект содержит файл AndroidManifest.xml.
Android manifest – файл, в котором содержится вся самая важная информация о нашем приложении, которую вы же и описываете.
Манифест нужен, чтобы задавать имя пакета Java для приложения. Это имя пакета служит уникальным идентификатором приложения.
Манифест нужен, чтобы описать компоненты приложения – activity, service, broadcast receiver и provider. Он содержит имена классов, которые реализуют каждый компонент, и публикует их возможности, например, указывает, какие сообщения Intent они могут принимать.
Манифест нужен, чтобы определить, в каких процессах будут размещаться компоненты приложения.
Манифест нужен, чтобы объявить, какие разрешения должны быть выданы приложению, чтобы оно могло получить доступ к защищённым частям API-интерфейса и взаимодействовать с другими приложениями.
Манифест нужен, чтобы объявить разрешения, требуемые для взаимодействия с компонентами данного приложения.
Манифест нужен, чтобы объявить минимальный уровень API-интерфейса Android, который требуется приложению.
Манифест нужен, чтобы содержать список библиотек, с которыми должно быть связано приложение.
Тег <application> – основной элемент манифеста (рис. 16). Он содержит описание компонентов приложения, доступных в пакете: стили, иконки и другие.
Тег <application> cодержит дочерние элементы. Они объявляют все компоненты, входящие в состав приложения, например: <activity>, <service>. В манифесте может быть только один элемент <application>.
Рис. 16. Структура файла AndroidManifest
Тег <activity> для системы указывает о существовании конкретной activity. Каждая activity, которая используется в приложении, должна быть описана в манифесте. Это делается с помощью тега <activity>. Если activity не описана в манифесте, то система не будет знать о том, что она существует. При запуске приложения и переходе в эту activity будет выдана соответствующая ошибка.
Тег <service> говорит о существовании и описывает service для системы. Если его не объявить, то будет похожая ситуация, как и с activity – service просто не будет найден и не будет запущен.
Тег <receiver> говорит о существовании и описывает broadcast receiver для системы. Broadcast receiver позволяет отправлять или принимать какие-то системные сообщения, например, сообщения о низком уровне батареи, установке нового сообщения, подключении зарядного устройства. Чаще всего у вас будет необходимость принимать такие сообщения, а не отправлять их.
Элемент <provider> объявляет контент-провайдера для управления доступом к базам данных, также он содержит свой набор дочерних элементов для установления разрешений доступа к данным.
Иногда приложению требуются разрешения на доступ к определённым ресурсам, например, доступ к камере, Интернету, смс и так далее. Чтобы приложение могло работать с Интернетом, в манифесте нужно записать соответствующие разрешения. Для этого используется тег <uses-permission>.
Интент представляет собой объект обмена сообщениями, с помощью которого можно запросить выполнение действия у компонента другого приложения.
Объекты Intent используются для запуска конкретных компонентов приложения – например, определённой активности или службы, переход на новый экран (рис. 17). Также интенты бывают двух видов: явные и неявные.
Рис. 17. Схема работы интента
Явные объекты Intent указывают компонент, который требуется запустить, с указанием имени класса.
Явные объекты Intent обычно используются для запуска компонента из вашего собственного приложения, поскольку вам известно имя класса Activity или службы, которую необходимо запустить. Например, можно запустить новую Activity в ответ на действие пользователя или запустить службу, чтобы загрузить файл в фоновом режиме.
Неявные объекты Intent не содержат имени конкретного компонента или адресата. Вместо этого они в целом объявляют действие, которое требуется выполнить, что даёт возможность компоненту из другого приложения обработать данный запрос.
Например, если требуется показать пользователю место на карте, то с помощью неявного объекта Intent можно запросить, чтобы это сделало другое приложение, в котором такая возможность предусмотрена.
Обычно Intent, объект интента, содержит информацию, на основании которой система Android определяет, какой компонент требуется запустить, например, точное имя компонента или категорию компонентов, которые должны получить данный объект Intent, а также сведения, которые необходимы компоненту-получателю, чтобы надлежащим образом выполнить действие, а именно: выполняемое действие и данные, с которыми его требуется выполнить.
Основные сведения, содержащиеся в объекте Intent, – это имя компонента, который требуется запустить.
Данная информация является необязательной, но именно она и делает объект Intent явным.
Её наличие означает, что объект Intent следует доставить только компоненту приложения, определённому по имени.
При отсутствии имени компонента объект Intent является неявным, а система определяет, какой компонент получит данный объект Intent, по другим сведениям, которые в нём содержатся.
Поэтому если вам требуется запустить определённый компонент из своего приложения, следует указать его имя.
Следующее содержание объекта интента – это строка, определяющая стандартное действие, которое требуется выполнить, например, просмотр или выбор.
Действие в значительной степени определяет, каким образом структурирована остальная часть объекта Intent, в частности, что именно содержится в разделе данных и дополнительных данных.
Для использования объектами Intent в пределах своего приложения либо для использования другими приложениями, чтобы вызвать компоненты из вашего приложения, можно указать собственные действия.
Обычно же следует использовать константы действий, определённые классом Intent или другими классами платформы.
Используйте действие ACTION_VIEW в объекте Intent с методом startActivity, когда имеется определённая информация, которую операция может показать пользователю, например, фотография в приложении галереи или адрес для просмотра в картографическом приложении.
Также существует константа ACTION_SEND.
Константу ACTION_SEND называют Share, что подразумевает намерение предоставить общий доступ.
Это действие следует использовать в объекте Intent с методом startActivity при наличии определённых данных, доступ к которым пользователь может предоставить через другое приложение – например, приложение для работы с электронной почтой или социальными сетями.
Другие константы, определяющие стандартные действия, можно посмотреть в документации по Android. Здесь определены самые популярные константы – например, константа ACTION_DIAL существует для вызова приложения для совершения звонков.
Следующее содержание объекта интента – это категория.
Категория является строкой, содержащей прочие сведения о том, каким компонентом должна выполняться обработка объекта Intent.
В объект Intent можно поместить любое количество описаний категорий, однако большинству объектов Intent категория не требуется.
Существует несколько стандартных категорий: это CATEGORY_LAUNCHER и CATEGORY_BROWSABLE.
Категория BROWSABLE – это целевая операция, которая позволяет запускаться отображению данных, указанных по ссылке.
Категория LAUNCHER является начальной операцией задачи, она указана в средстве запуска приложений системы.
Полный список категорий также можно посмотреть на сайте документации Android.
Следующее – это данные.
Здесь встречается новый термин – объект URI.
URI – это объект, который берёт строку, разбирает её на составляющие и хранит в себе эту информацию. Тип передаваемых данных обычно определяется действием объекта Intent. Например, если действием является ACTION_EDIT, в данных должен содержаться URI документа, который требуется отредактировать.
При создании объекта Intent помимо URI зачастую бывает важно указать тип данных – их тип MIME. MIME – это многоцелевые расширения почты Интернета, спецификация для передачи по сети файлов различного типа: изображений, музыки, текстов, видео и архивов. Например, Activity, которая может выводить на экран изображения, скорее всего, не сможет воспроизвести аудиофайл, даже если у тех и у других данных будут одинаковые форматы URI. Поэтому указание типа MIME данных помогает системе Android найти более подходящий компонент для получения вашего объекта Intent. Последний объект интента – это дополнительны параметры, пары «ключ – значение», содержащие прочую информацию, которая необходима для выполнения запрошенного действия. Точно так же, как некоторые действия используют определённые виды URI-данных, некоторые действия используют определённые дополнительные данные. Добавлять дополнительные данные можно с помощью различных методов putExtra, каждый из которых принимает два параметра: имя и значение ключа.
Также можно создать объект Bundle со всеми дополнительными данными, затем вставить объект Bundle в объект Intent с помощью метода putExtra. Например, при создании объекта Intent для отправки сообщения электронной почты методом ACTION_SEND можно указать получателя с помощью ключа EXTRA_EMAIL, а тему сообщения – с помощью ключа EXTRA_SUBJECT.
Фильтр Intent (рис. 18) представляет собой выражение в файле манифеста приложения, указывающее типы объектов Intent, которые мог бы принимать компонент. Например, объявив фильтр Intent для Activity, вы даёте другим приложениям возможность напрямую запускать вашу Activity с помощью некоторого объекта Intent. Точно так же, если вы не объявите какого-либо фильтра Intent для Activity, то её можно запустить только с помощью явного объекта Intent. В данном случае Intent Filter даёт указание в файле AndroidManifest, что класс MainActivity является запускающим. То есть данный класс запускается первым, данная активность запускается первой при открытии приложения. Несмотря на то, что объекты Intent обращают обмен данными между компонентами по нескольким аспектам, в основном они используются в трех ситуациях: для запуска Activity, для запуска сервиса, то есть службы, для запуска реси́вера, то есть для рассылки широковещательных сообщений. Компонент Activity представляет собой один экран приложения. Для запуска нового экземпляра компонента Activity необходимо передать объект Intent метода startActivity. Объект Intent описывает операцию, которую требуется запустить, а также содержит все остальные необходимые данные.
Рис. 18. Intent filter
Для запуска службы Service является компонентом, который выполняет действие в фоновом режиме без пользовательского интерфейса.
Службу можно запустить для выполнения однократного действия, например, чтобы загрузить файл, передав объект Intent методу startService.
Объект Intent описывает службу, которую требуется запустить, а также содержит все остальные необходимые данные.
Android – очень фрагментированная система. Это означает, что устройства, работающие на операционной системе Android, имеют различные размеры, разрешения и соотношения сторон, форм-факторы, железную начинку, сенсоры, вендорский софт и версии операционной системы.
Давайте немного разберемся с терминологией.
Размер экрана – это физический размер экрана, измеряется диагональ.
Для удобства Android группирует все существующие размеры экранов в 4 общие группы: маленькие – small, обычные – normal, большие – large, очень большие – extra-large.
Плотность экрана – количество пикселей на определённой площади экрана, обычно определяется как количество точек на дюйм (dpi – dots per inch).
Для удобства Android группирует все существующие плотности экрана в 6 общих групп: low (ldpi), medium (mdpi, ~160 dpi), high (hdpi), extra-high (xhdpi), extra-extra-high (xxhdpi), extra-extra-extra-high (xxxhdpi).
Разрешение экрана – суммарное количество пикселей на экране. Приложения не должны работать с разрешением экрана напрямую, вместо этого они работают с размером и плотностью.
Для того чтобы интерфейс был более-менее идентичен на различных экранах, были придуманы так называемые пиксели, не зависящие от плотности, или dp. Они рассчитываются по формуле: dp = длина в пикселях * 160 / плотность экрана. px (пиксели) = dp * плотность экрана / 160.
Следовательно, на экране с плотностью 160 точек на дюйм 1 dp = 1 px.
Разрешение экрана чаще всего указывается в пикселях. Элементы интерфейса нежелательно указывать в пикселях.
Android – это framework, то есть глобальная библиотека, которая берёт на себя всю грязную работу, например, связанную с управлением памятью, процессами, приложениями, датчиками и тому подобное.
Framework предоставляет нам несколько компонентов, наследуя и изменяя которые, мы строим свое приложение.
Всего таких компонентов пять.
Это Application, Activity, Service, BroadcastReceiver и ContentProvider.
Application – это компонент, который загружается первым при начале работы с приложением и умирает последним.
Он представляет собой singleton, то есть единственный экземпляр своего класса.
Реализация Application необязательна, но если вам нужна глобальная точка доступа к переменным либо методам, то Application – вполне подходящий вариант.
Для того чтобы система знала о вашем Application, необходимо указать его класс в манифесте, в node Application через tagname, иначе будет использоваться стандартная реализация.
Activity – это основной компонент, с помощью которого пользователь взаимодействует с приложением.
Activity представляет собой какой-либо экран приложения, логику в Java-коде и интерфейс в XML-вёрстке.
Все Activity должны наследоваться от базового класса Activity, а в методах должны обязательно вызываться методы родителя.
Activity, как и другие компоненты из списка, должны обязательно указываться в манифесте, чтобы система знала о нём и могла запустить.
Service, или сервис, – это компонент, главное предназначение которого – выполнять долгие операции, которые не требуют взаимодействия с интерфейсом.
К примеру, если вы слушаете музыку и она продолжает играть, даже если вы свернули приложение и открыли другое приложение, то, значит, музыка играет в фоновом сервисе.
BroadcastReceiver, как понятно из названия, – это приёмник широковещательных сообщений, посылаемых системой, другими приложениями либо вашим приложением и каким-либо образом реагирующий на них.
Естественно, вы сами определяете, на что и как будет реагировать ваш приёмник.
К примеру, вы можете создать приёмник, подписанный на включение устройства, а реакция будет в виде запроса на сервер с обновлением данных.
К слову, все мессенджеры работают подобным образом.
ContentProvider – это мощный компонент, представляющий собой программный интерфейс, который позволяет нескольким приложениям пользоваться одним источником данных.
Любой более-менее серьезный мессенджер имеет доступ к телефонной книге, с которой он считывает номера или добавляет новые контакты.
Банковское приложение также позволяет переводить средства по номеру телефона, сравнивая номера из списка контактов в своей базе.
Подобных примеров множество, и они не ограничиваются только телефонной книгой.
Все эти компоненты должны быть обязательно прописаны в манифесте.
Во всех этих компонентах присутствует класс Context.
Context – это абстрактный класс, за реализацию которого отвечает сам Android. Например, Activity – это одна из таких реализаций, так как Activity является потомком Context.
Зачем нам вообще нужен Context? Наиболее частый случай использования – это доступ к ресурсам приложения, будь то строки, цвета, меню, цифровые константы или RAW-файлы, не поддающиеся классификации. Через Context также осуществляется доступ к системным возможностям телефона, например, к сканеру отпечатков пальцев, будильнику, акселерометру или другим возможностям.
Также иногда может возникнуть ситуация, в которой вы не знаете до запуска приложения, как будет выглядеть интерфейс.
Предположим, что интерфейс зависит от того, что придет с сервера.
Тогда для создания элементов интерфейса прямо в коде обязательно нужно передавать в конструктор этого элемента текущий контекст.
Еще одна возможность контекста – это создание файлов, хранящихся на устройстве и используемых приложением, к примеру, база данных, скачанные файлы, сделанные фотоснимки или файлы preferences, в которых хранятся настройки приложения.
Следует помнить, что напрямую с контекстом обычно никто не работает. Всё происходит через его реализации. Эти реализации достаточно сильно отличаются друг от друга.
К примеру, Context, который реализован в Application, как и сам Application, является singleton.
При создании своих собственных singleton, которым необходим Context для работы, следует использовать именно Context Application.
Во время навигации пользователя по вашему приложению экземпляры Activity внутри приложения переключаются между разными состояниями их жизненного цикла.
Например, при первом запуске Activity она получает высокий приоритет в системе и привлекает внимание пользователя.
Во время этого процесса система Android вызывает серию методов жизненного цикла Activity, позволяя настроить пользовательский интерфейс и другие компоненты.
Жизненный цикл приложения в Android жестко контролируется системой и зависит от нужд пользователя, доступных ресурсов.
Если пользователь выполняет действие, запускающее другую Activity, или переключается на другое приложение, система вызывает другой набор методов жизненного цикла для Activity, поскольку она переносится на фоновый уровень, где Activity больше не отображается, но экземпляр и состояние остаются без изменений.
В зависимости от сложности Activity некоторые методы жизненного цикла могут не требоваться.
Однако очень важно понимать все методы и реализовать их так, чтобы приложение работало так, как этого ожидают пользователи.
Правильная реализация методов жизненного цикла Activity обеспечивает нормальную работу приложения в нескольких аспектах, в том числе:
не прекращает работу, если пользователь получает телефонный звонок или переключается на другое приложение во время использования вашего приложения;
не потребляет ценные системные ресурсы, когда пользователь не использует его активно;
сохраняет состояние приложения, если пользователь выходит из него и возвращается позднее; не закрывается с ошибкой и не теряет данные пользователя при повороте экрана, а именно при смене ориентации между книжной и альбомной.
Android всегда уничтожает активити, а не компоненты.
В течение цикла существования Activity система вызывает базовый набор методов жизненного цикла в последовательности, сходной с многоступенчатой пирамидой (рис. 19).
Таким образом, каждый этап жизненного цикла Activity представляет собой отдельную ступень пирамиды.
Когда система создаёт новый экземпляр Activity, каждый метод обратного вызова перемещает состояние действия на одну ступень вверх.
Вершина пирамиды представляет собой точку, в которой Activity выполняется в экранном режиме, и пользователь может с ней взаимодействовать.
Рис. 19. Приоритеты процессов в Android
Когда пользователь начинает выходить из Activity, система вызывает другие методы, которые перемещают состояние Activity вниз по пирамиде для уничтожения Activity.
В некоторых случаях действие перемещает Activity вниз по пирамиде только частично и ждёт, например, когда пользователь переключается на другое приложение.
А затем Activity может быть перемещена обратно вверх, если пользователь вернётся к Activity, и возобновлена там, где пользователь вышел из неё.
Система может запускать и останавливать текущие окна в зависимости от происходящих событий.
В отличие от других парадигм программирования, где приложения запускаются с использованием метода main, система Android запускает код в экземпляре Activity посредством активации определённых методов обратного вызова, соответствующих определённым этапам его жизненного цикла.
Упрощённая иллюстрация жизненного цикла Activity в виде многоступенчатой схемы показывает, что для каждого обратного вызова, поднимающего Activity на одну ступень к состоянию возобновления на вершине пирамиды, существует обратный вызов, опускающий Activity на одну ступень вниз.
Возобновление Activity также может производиться из состояний паузы и остановки.
Метод onCreate() вызывается при создании или перезапуске Activity.
Внутри данного метода настраивают статический интерфейс Activity.
Метод onCreate() связывается с необходимыми данными и ресурсами. Он задаёт внешний вид через метод setContentView().
Существует последовательность методов обратного вызова, которые запускают Activity, и последовательность методов обратного вызова, уничтожающих Activity (рис. 20).
Рис. 20. Основные методы жизненного цикла
При вызове onStart() окно ещё не видно пользователю, но вскоре будет видно. onStart() вызывается непосредственно перед тем, как Activity становится видимой пользователю.
Состояния создания и запуска являются переходными, и система быстро переходит от них к следующим состояниям посредством вызова следующего метода обратного вызова в жизненном цикле.
Таким образом, после вызова метода onCreate система быстро вызывает метод onStart, а затем сразу же вызывает метод onResume.
Метод onResume() вызывается после onStart(). Также может вызываться после onPause().
Когда пользователь возобновляет Activity после паузы, система вызывает метод onResume.
Учтите, что система вызывает этот метод каждый раз, когда Activity выполняется на экране, в том числе и при первом её запуске.
В связи с этим нужно реализовать метод onResume для инициализации компонентов, освобождаемых в состоянии метода onPause, и выполнения других операций инициализации, которые должны происходить каждый раз при возобновлении Activity.
В этот момент пользователь взаимодействует с созданным вами окном. Приложение получает монопольные ресурсы, запускает воспроизведение анимации, аудио и видео.
Когда пользователь решает перейти к работе с новым окном, система вызовет для прерываемого окна метод onPause. По сути, происходит свёртывание Activity.
Когда система вызывает метод onPause для Activity, это технически означает, что Activity остаётся частично видимой.
Однако чаще всего это означает, что пользователь покидает Activity, и вскоре она войдёт в состояние остановки.
onPause() выполняет следующие действия:
- сохраняет незафиксированные данные;
- деактивирует и выпускает монопольные ресурсы;
- останавливает воспроизведение видео, аудио и анимации.
Обратный вызов метода onPause обычно используется для следующих целей:
остановка анимации или других текущих действий, которые могут потреблять ресурсы процессора;
запись несохранённых изменений, если пользователи ожидают сохранения таких изменений при выходе, например, черновиков электронных писем;
высвобождение ресурсов системы, влияющих на время работы аккумулятора во время паузы и не требующихся пользователю.
От onPause можно перейти к вызову либо onResume(), либо onStop().
Правильное выполнение остановки и перезапуска Activity является важным шагом в жизненном цикле Activity, благодаря которому пользователи понимают, что приложение не потеряет их данные.
Например, пользователь открывает окно «Последнее приложение» и переключается из вашего приложения в другое приложение.
При этом останавливается Activity вашего приложения, работающего на экране.
Если пользователь возвращается в ваше приложение, нажав значок запуска на главном экране или через окно «Последнее приложение», данная Activity возобновляется.
Следующий пример: пользователь выполняет в вашем приложении действие, запускающее другую Activity.
Текущая Activity останавливается при создании второй Activity.
Если после этого пользователь нажимает кнопку «Назад», первая Activity запускается заново.
Метод onStop() вызывается, когда окно становится невидимым для пользователя. Всегда сопровождает любой вызов метода onRestart().
Если окно возвращается в приоритетный режим после вызова onStop(), то в этом случае вызывается метод onRestart(). Всегда сопровождает любой вызов метода onStart().
Последний пример использования метода onStop и метода onRestart: пользователь получает телефонный звонок или переключается на другое приложение во время использования вашего приложения на своём телефоне.
В отличие от состояния паузы, означающего частичное уничтожение пользовательского интерфейса, в состоянии остановки пользовательский интерфейс больше не отображается, и пользователь переключается на отдельную Activity или отдельное приложение.
Вызов метода onStop() может произойти при уничтожении Activity или если была запущена другая Activity, перекрывающая окно текущей Activity.
Метод onRestart() вызывается после того, как Activity была остановлена и снова была запущена пользователем.
Чтобы лучше разобраться с жизненным циклом, давайте рассмотрим пример запуска родительского activity, с которого мы переходим на дочернее activity и возвращаемся назад (рис. 21).
Рис. 21. Порядок вызова методов жизненного цикла при запуске дочерней Activity
Следует отметить, что все activity-приложения собираются в так называемый backstack activity.
При запуске нового activity оно добавляется на вершину стека, при нажатии кнопки назад – удаляется.
Итак, запускается первое activity, у него вызываются методы OnCreate, OnStart, OnResume.
Оно становится готовым для взаимодействия, находится на вершине своего стека.
Дальше мы запускаем дочернее activity.
В родительском activity вызывается метод OnPause, с ним мы больше не взаимодействуем.
У дочернего activity вызываются методы OnCreate, OnStart и OnResume. Оно выходит на передний план.
У родительского activity вызывается метод OnStop.
Теперь родительское activity находится в состоянии Stopped, оно невидимо.
Дочернее activity находится в состоянии Resumed, и оно готово к взаимодействию.
Если мы сейчас, будучи в дочернем activity, нажмём кнопку назад, то в дочернем activity вызовется метод OnPause, оно отойдёт, у родительского activity вызовутся методы OnRestart, OnStart и OnResume, оно станет готовым для взаимодействия.
У дочернего Activity вызовутся методы OnStop и OnDestroy, оно будет окончательно уничтожено.
Раз уж мы затронули тему уничтожения activity, давайте разберём два основным случая этого события.
Система считает, что уничтожение activity – это нормальное поведение приложения, если пользователь нажал кнопку «назад», то есть явно дал знать, что уходит, либо при программном вызове метода finish, который закрывает наше activity.
Альтернативным этому ненормальным считается поведение, когда framework вынужден закрыть activity.
Система сохраняет стейт при следующих случаях:
- изменении конфигурации (поворот экрана, смена языка, доступность клавиатуры)
- уничтожение фоновой Activity при нехватке памяти для рабочей Activity.
Например, если activity, которое находится на вершине стека, испытывает какую-либо нехватку ресурсов, то activity, которые находятся под ним, могут быть уничтожены.
Естественно, разработчики Android не могли допустить потери пользовательской работы.
Поэтому перед уничтожением такой activity между OnPause и OnStop вызывается метод OnSaveInstanceState, в котором будет сохранено текущее состояние activity.
В частности, это касается view-элементов с указанными ID.
Помимо этого, мы можем переопределить метод OnSaveInstanceState и дописать туда что-нибудь своё.
Теперь, если пользователь вернётся на это activity, уничтоженное activity, то вызов метода OnCreate будет передан bundle с сохранённым стейтом.
Если bundle не равен null, то извлекаем данные, которые мы сами записали.
Также существует метод OnRestoreInstanceState, который вызывается, только если в bundle что-то записано.
Бывают активити верхнего уровня, активити категорий и активити детализации и редактирования.
Активити верхнего уровня представляют собой операции, наиболее важные для пользователя, и предоставляют простые средства для навигации к ним.
Активити категорий выводят данные, принадлежащие конкретной категории, часто в виде списка.
Активити детализации и редактирования выводят подробную информацию по конкретной записи, предоставляют пользователю возможность редактирования существующих записей или ввода новых записей.
Например, активити верхнего уровня будет выводить список всех команд. Активити категорий отображает список курсов и специальностей. Активити детализации и редактирования выводит уже более подробную информацию по курсу или специальности.
Итак, у нас есть набор объектов и есть компонент View.
Назначение адаптера заключается в том, чтобы предоставить дочерние виды для контейнера.
Адаптер берет данные и метаданные определённого контейнера и строит каждый дочерний вид. Например, мы формируем пункты списка и передаем его списку ListView с помощью адаптера.
В Android часто используются адаптеры. Если говорить в общих чертах, то адаптеры упрощают связывание данных с элементами управления.
Адаптеры используются при работе с элементами, которые дополняют AdapterView: элементами ListView, GridView, Spinner и Gallery.
Интерфейс – это самая важная часть любого приложения с точки зрения пользователя.
Удобное расположение кнопок и текста, приятные сочетающиеся друг с другом цвета, плавные анимации – всё это формирует первое впечатление, заставляя пользователя любить или ненавидеть ваше приложение.
Даже если оно суперфункциональное или с оригинальной идеей, но при этом с неудобным, некрасивым интерфейсом, то можете быть уверены: это приложение обречено провалиться на самое дно Playmarket'а с минимальным рейтингом, низким качеством скачивания и пачкой гневных комментариев впридачу.
В Android-приложениях логика и интерфейс несколько разделены.
Логика находится в Java-классах, а интерфейс в XML-разметке.
Подобное разделение очень удобно, так как структура интерфейса сразу бросается в глаза, и в ней достаточно легко разобраться.
Для того чтобы манипулировать элементами интерфейса в Java-коде, их различают с помощью Id – идентификатора, уникального для данного XML-файла.
Напомню, что эти элементы интерфейса, по сути, обычные Java-классы, поэтому ничего не мешает создавать их программно, в рантайме, если интерфейс требует такой динамики.
Элемент интерфейса, который находится в XML-файле, делится на две главные категории: это View и его наследник ViewGroup.
ViewGroup представляет собой контейнер, в котором по тому или иному принципу располагается View – конкретный элемент интерфейса (рис. 22).
Рис. 22. View и ViewGroup
ViewGroup используется как контейнеры для обычных View-элементов, и реализации ViewGroup отличаются друг от друга способами расположениями этих элементов.
LinearLayout – это самый простой для восприятия ViewGroup.
Он располагает свои дочерние элементы друг за другом в одном направлении: вертикально либо горизонтально.
Направление задаётся с помощью атрибута orientation.
LinearLayout имеет одну интересную возможность – он умеет распределять незанятое пространство между своими дочерними элементами.
Делается это с помощью атрибута layout_weight, который принимает любое численное значение.
Чем больше это значение, тем больше пространства будет отведено под View в относительном смысле.
Рассмотрим пример.
На рис. 23 изображён корневой LinearLayout с вертикальной ориентацией (фиолетовый).
Рис. 23. Корневой LinearLayout с вертикальной ориентацией
В нём находится пара кнопок и вложенный горизонтальный LinearLayout (малиновый), в котором находятся ещё две кнопки.
Веса этих кнопок указаны в числителях в названии, в знаменателе указаны значения weightSum горизонтального LinearLayout.
Так как сумма числителя не равна знаменателю, LinearLayout оставляет пустое место размером с остаток.
Атрибут layout_width в этом случае распространяется только на ширину кнопок, поэтому в целях производительности Studio подсказывает нам установить ширину кнопок 0dp.
LinearLayout хорошо справляется с одной задачей – располагать элементы друг за другом.
Однако при более разветвлённом дизайне интерфейса вёрстка через LinearLayout может стать болью из-за большого количества вложенных контейнеров.
Чтобы избежать этого, можно воспользоваться RelativeLayout.
RelativeLayout – это ViewGroup, которая позволяет более гибко располагать свои дочерние элементы с помощью большого списка атрибутов.
Атрибуты указывают на местоположение View относительно Layout и других View.
FrameLayout – это ViewGroup, обычно используемый как контейнер для дной View, при этом добавление этой View происходит программно.
Однако если есть необходимость расположить несколько View друг на друге, то FrameLayout – вполне подходящий вариант.
При этом нужно учитывать, что Android рисует элементы в том порядке, в котором они описаны в Layout-файле.
ConstraintLayout – это относительно новая реализация ViewGroup, идейный наследник RelativeLayout, или, как его сейчас называют, RelativeLayout на стероидах.
Для расположения дочерних элементов используют так называемые constraint'ы, или правила.
Если вёрстку можно сделать с помощью другого layout'а, то лучше воспользоваться им, иначе есть вероятность погрязнуть в пучине constraint'ов, так и не добившись желаемого результата.
Давайте рассмотрим фундаментальные характеристики любой View, вне зависимости от типа.
Итак, для того чтобы система могла отрисовать View, ей необходимо знать две составляющие – это размер и расположение.
Начнём с задания размеров.
Для этого в XML-файле View задаются атрибуты layout_height и layout_width – они являются обязательными для всех View.
Высота и ширина могут быть выражены в конкретных значениях, например, 40dp.
Если указано значение WRAP_CONTENT, View займёт столько места, сколько требуется для отрисовки его содержимого.
Если указано значение MATCH_PARENT, View займёт всё расстояние до краёв его родителя.
Допустим, у нас есть две кнопки, стоящие рядом друг с другом. Одна кнопка занимает одну треть экрана, вторая – две трети.
Как это сверстать?
В этом случае нам нужно воспользоваться атрибутом layout_weight, так называемым весом, который специально создан для таких целей.
Однако он работает только в LinearLayout.
Вес определяет, сколько свободного места отойдёт под ту или иную View.
Атрибут направления, которое должно быть определено через вес, не игнорируется, а зануляется – 0dp.
В таком дизайне layout_width обоих кнопок будет 0dp, а layout_weight – 1 и 2 соответственно.
Если мы просто удалим кнопку с разметки, то вторая кнопка займет всю ширину экрана, так как ей не с чем сравнивать свой атрибут веса.
Мы плавно подошли ко второй фундаментальной части любого элемента интерфейса, а именно – расположение.
Мы использовали в качестве контейнера LinearLayout, который, как нам уже известно, располагает элементы друг за другом.
Можно воспользоваться атрибутом layout_margin и задать отступ в численном виде.
Но этот атрибут скорее подходит для того, чтобы задавать отступы между View и от ближайшего края экрана.
В нашем случае он не годится, так как мы не можем быть уверены, что этот отступ подойдёт для всех экранов.
Для указания отступов можно воспользоваться атрибутом padding. Его отличие от марджина в том, что пэдинг указывает отступ между границей View и её содержимым.
Попробуем воспользоваться гравитацией или атрибутами gravity и layout_gravity.
Значения атрибутов означают направление, к которому View и будет притянута, они говорят сами за себя.
В чем же разница между gravity и layout_gravity?
Gravity – это притяжение элементов внутри текущего элемента.
Layout_gravity – это притяжение текущего элемента относительно родительского.
То есть в нашем случае достаточно прописать gravity = end в LinearLayout – и кнопка выровняется относительно правого края.
Конечно же, из-за этой специфики сложно управлять притяжением в LinearLayout.
Ресурсы являются необходимой составляющей любого приложения.
Ресурсы представляют собой и файлы разметки, и отдельные строки, и звуковые файлы, и файлы изображений, и другие.
Хранятся все эти ресурсы в специальном каталоге res.
Там вы сможете добавлять и удалять различные ресурсы.
@android: означает, что мы используем системные ресурсы вместо своих.
Однако нужно учитывать, что на разных устройствах будут разные предустановленные ресурсы.
Например, @android:drawable/ic_search_category_default – это ссылка на системную иконку поиска в виде лупы.
Эта иконка, скорее всего, будет отличаться на различных устройствах.
Весь текст крайне желательно хранить в ресурсах приложения.
Если вам нужно будет изменить этот текст, а он у вас используется в нескольких местах, то вы просто сможете поправить текст в strings.xml – и он изменится всюду, где вы использовали этот строковый ресурс.
Если возникнет необходимость перевести приложение на другой язык, достаточно будет изменить несколько файлов со строковыми ресурсами, а не искать текст в коде.
Вам нужно для каждой локализации создать отдельный strings.xml.
В drawable можно хранить как картинку, так и xml-вектор.
Картинки надо нареза́ть под соответствующие типы экранов, вектор может неправильно считаться системой и не везде поддерживается.
C Android 5.0 появилась возможность использовать векторную графику.
Все активити приложения собираются в так называемый бэкстек активити.
При запуске новой активити оно добавляется на вершину стека, при нажатии кнопки назад – удаляется из стека.
В подавляющем большинстве приложений Android есть Activity.
Часто бывает, что таких Activity очень много и нам нужно как-то переключаться между ними – переходить на следующий экран, на предыдущий.
В этом случае должна быть какая-то сущность, которая запоминает, в каком порядке мы переходили по экранам, чтобы при нажатии кнопки «назад» она открыла верный экран.
Такая сущность называется Back Stack.
Она работает по принципу last in – first out, или LIFO.
Если открывается новая activity, то она добавляется на самый верх стека, а предыдущая activity уходит в stopped.
Одна Activity может быть в стеке столько раз, сколько была запущена.
Task – набор из нескольких Activity.
В каждом task есть свой back stack.
Когда вы сворачиваете ваше приложение, ваш task уходит в фоновый режим. Но он обязательно восстановится, когда вы вернётесь в приложение.
Но может случиться так, что системе не будет хватать памяти и она «прибьёт» ваш task, но это бывает крайне редко.
На рис. 24 представлена работа back stack. Если нажать на кнопку back, то Activity будут удаляться из стека до тех пор, пока не останется одна корневая Activity.
Рис. 24. Схема работы Activity BackStack
Если в стеке останется только корневая Activity и вы нажмете на back, то приложение закроется и task «умрёт». Все вышеописанное – это стандартное поведение Android.
Но бывают случаи, когда вам нужно изменить поведение вашей task. Есть два способа, чтобы изменить работу task:
1) установить атрибуты в манифесте в каждом Activity;
2) установить флаги для Intent, который запускает новую Activity.
Если вы будете использовать оба способа сразу, то знайте, что при противоречии флагов из первого и второго способов система будет отдавать приоритет второму способу.
В манифесте для любой Activity можно задать атрибут launchMode.
По умолчанию он принимает значение standard. В этом случае во время запуска Activity создаётся новый экземпляр в стеке. Activity может размещаться в стеке несколько раз.
Он может принимать значение singleTop: Activity может размещаться в стеке несколько раз. Новая запись появится, если данная Activity не находится в вершине стека. Если она и так в вершине стека, то просто вызовется метод onNewIntent(), в который передаётся интент, из которого при необходимости можно извлечь данные.
Он может принимать значение singleTask: если экземпляра данной Activity нет ни в одном другом task, то создаётся новый task и для него устанавливается Activity корневой. Если Activity уже находится в каком-нибудь task, то откроется этот экземпляр и вызовется метод onNewIntent(). Activity станет корневой – и все, кто был выше, удалятся. Может существовать только один экземпляр такой Activity.
Мы можем задавать флаги для Intent, который запускает новую Activity.
Флаги более приоритетны, чем launchMode.
FLAG_ACTIVITY_NEW_TASK запускает Activity в новой task. Если уже существует task с экземпляром данной Activity, то этот task становится активным, и вызывается метод onNewIntent().
В случае FLAG_ACTIVITY_SINGLE_TOP если Activity находится в вершине стека, то вместо создания нового экземпляра в стеке вызывается метод onNewIntent().
В случае FLAG_ACTIVITY_CLEAR_TOP если экземпляр данной Activity уже существует в стеке данной task, то все Activity, находящиеся поверх неё, разрушаются, и этот экземпляр становится вершиной стека. Вызовется onNewIntent().
Обычно все Activity нашего приложения работают в одном task.
Мы можем изменять такое поведение и указывать, чтобы в одном приложении Activity работали в разных task, или Activity разных приложений работали в одном task.
Для этого мы можем в манифесте для каждой Activity указывать название task параметром taskAffinity.
Это строковое значение, которое не должно совпадать с названием package, так как стандартный task приложения называется именно как наш package.
В общем случае данный параметр указывает, что Activity будет гарантированно открываться в своём отдельном task.
Данный параметр актуален, если мы указываем флаг FLAG_ACTIVITY_NEW_TASK.
Адаптер дополняет ViewGroup.
В приложениях очень часто используется список на основе ListView.
Сам список состоит из множества элементов TextView, которые идут друг за другом.
Но их количество будет зависеть от того, что мы хотим отобразить.
Если нам нужны дни недели, то достаточно 7 элементов, если месяцы, то уже 12. Ну, а если нам нужен список работников большого предприятия, то счёт пойдет на сотни.
Нам нужно составить данные, например, в виде массива, и передать его списку. Адаптер этим и занимается. Он берёт по порядку предоставленные данные и размещает их в списке по порядку (рис. 25).
При этом адаптер сразу создает нужный компонент TextView и помещает в него приготовленный текст.
Данные могут находиться не только в массиве, но и в базе данных.
Для такого случая используется другой адаптер.
Существуют также уже готовые адаптеры на самые распространённые случаи. Их предназначение можно определить по именам.
Рис. 25. Назначение адаптера
Все адаптеры, содержащиеся в Android, дополняют базовый адаптер BaseAdapter.
Весь список готовых адаптеров можно посмотреть в документации Android.
ArrayAdapter предназначен для работы с ListView.
Данные для ArrayAdapter представлены в виде массива, который размещается в отдельных элементах TextView.
CursorAdapter предназначен для работы с ListView.
CursorAdapter предоставляет данные для списка через курсор, который должен иметь колонку с именем id.
SimpleAdapter – это адаптер, позволяющий заполнить данными список более сложной структуры, например, два текста в одной строке списка.
ResourceCursorAdapter – это адаптер, который дополняет CursorAdapter и может создавать виды из ресурсов.
SimpleCursorAdapter дополняет ResourceCursorAdapter, создаёт компоненты TextView, ImageView из столбцов, содержащихся в курсоре. Компоненты определяются в ресурсах.
Класс ArrayAdapter представляет собой простейший адаптер, который связывает массив данных с набором элементов TextView, из которых, к примеру, может состоять ListView, то есть в данном случае источником данных выступает массив объектов.
ArrayAdapter вызывает у каждого объекта метод toString для приведения к строковому виду и полученную строку устанавливает в элемент TextView.
Вначале получаем по id элемент ListView и затем создаём для него адаптер.
Для создания адаптера используется следующий конструктор. ArrayAdapter<String> (this, R.layout.list_item, balticCountries).
this здесь является объектом класса Context.
Android.R.layout.simple_list_item – это стандартная разметка списка.
balticCountries – это массив данных.
Элемент ListView представляет собой прокручиваемый список элементов, который был когда-то очень популярен на мобильных устройствах из-за своего удобства.
Компонент ListView более сложен в применении по сравнению с TextView и другими простыми элементами.
Работа со списком состоит из двух частей.
Сначала мы добавляем на форму сам ListView, а затем заполняем его элементами списка.
При работе со списками мы имеем дело с тремя компонентами.
Во-первых, это сам элемент списков, который отображает данные.
Во-вторых, это источник данных, в котором находятся отображаемые данные. Источником данных может выступать массив, объект ArrayList, база данных и так далее.
В-третьих, это адаптеры – специальные компоненты, которые связывают источник данных с элементом списка.
Рассмотрим связь элемента ListView с источником данных с помощью одного из таких адаптеров класса ArrayAdapter.
Для добавления спискового представления в макет используется элемент ListView.
Чтобы заполнить списковое представление данными, используйте атрибут android:entries и присвойте ему массив строк.
Строки из массива будут отображаться в списковом представлении в виде набора надписей TextView. Например, добавление в макет спискового представления, которое получает значения из массива строк options.
Массив определяется точно так же, как это делалось ранее.
Данные включаются в массив strings.
Чтобы пункты списка реагировали на щелчки, следует реализовать слушателя событий.
Слушатель событий отслеживает события, происходящие в приложении, например, щелчки на представлениях, потерю и получение ими фокуса или нажатие физической клавиши на устройстве.
Реализация слушателя событий позволит вам обнаруживать конкретные действия пользователя, например, щелчки на вариантах списка, и реагировать на них.
Слушатель OnItemClickListener отслеживает щелчки на вариантах списка, а метод onItemClick определяет активности на щелчок.
По параметрам, передаваемым методу OnClick, можно получить дополнительную информацию о событии, например, получить ссылку на вариант из списка, узнать его позицию в списковом представлении, начиная с нуля, и идентификатор записи используемого набора данных.
OnItemClickListener является вложенным классом по отношению к классу AdapterView ListView.
Для упрощения доступа к элементам списка используется класс ListActivity. ListActivity представляет собой класс, унаследованный от активити и разработанный специально для работы со списками. ListActivity специализируется на работе со списком.
Вам не придётся строить макет самостоятельно.
Списковая активность определяет свой макет на программном уровне.
Также вам не нужно будет реализовывать собственного слушателя.
Элемент RecyclerView является усовершенствованным вариантом ListView, но не является его родственником и относится к семейству ViewGroup. Он часто используется как замена ListView, но его возможности намного шире. Элемент находится в разделе AppCompat.
Элемент RecyclerView предназначен для оптимизации работы со списками и во многом позволяет повысить производительность по сравнению со стандартным ListView.
Он входит в состав библиотеки совместимости, поэтому его можно использовать и для старых устройств, а не только для Android 5.0 и выше.
Стоит отметить, что вначале это был сырой продукт, потом его доработали, заменили несколько устаревших методов, и на данном этапе можно считать, что он стал полноценной заменой списка ListView. Поэтому RecyclerView – это подходящее представление для использования, когда у вас есть несколько элементов одного типа, например, контакты, аудиофайлы, видео, картинки и тому подобное.
При прокрутке вверх и вниз вступают в действие повторная утилизация и повторное использование.
Как только пользователь прокручивает видимый в данный момент элемент, представление этого элемента может быть переработано и повторно использовано с помощью RecyclerView.
Это иллюстрирует рис. 26.
Слева находится пример приложения после первоначального запуска.
Когда вы просматриваете экран с элементами текста, изображения, некоторые элементы становятся пригодными для повторной переработки. Красная область на правом скриншоте, например, выделяет два невидимых объекта.
Теперь переработчик Recycler может перевести эти элементы в список, который будет использоваться повторно, если необходимо новое представление. Утилизация и повторное использование элементов – это очень полезный подход. Это экономит ресурсы процессов Android, поскольку вам не нужно постоянно пересоздавать новые представления, и это экономит память, так как не содержит много невидимых элементов.
Рис. 26. Скриншот примера приложения
В то время как с ListView у нас была жёсткая связь, Google теперь использует подход, в котором сам RecyclerView вообще не интересуется визуальными эффектами. Он не заботится о размещении элементов в нужном месте. Он не заботится о разделении каких-либо элементов и внешнем виде каждого отдельного элемента. Всё, что RecyclerView делает, – это утилизация и переиспользование элементов. Отсюда и название – Recycler – «переработчик отходов». Всё, что связано с макетом, рисунком и так далее, то есть всё связанное с представлением набора данных, делегируется подключаемым классам. Это делает новый RecyclerView чрезвычайно гибким.
Вам нужен другой макет? Тогда подключите другой LayoutManager. Вы хотите разные анимации? Тогда вставьте ItemAnimator. Наиболее важные классы, которые RecyclerView использует для представления данных: LayoutManager, ItemAnimator, Adapter, ItemDecoration, ViewHolder. Все эти классы являются внутренними классами RecyclerView.
Adapter оборачивает набор данных и создаёт представления для отдельных элементов. ViewHolder удерживает все зависимые представления, которые зависят от данных текущего элемента.
ItemDecoration определяет отступы вокруг или сверху элемента. ItemAnimator – анимации элементов во время добавления, удаления или переупорядочивания.
LayoutManager помещает объекты в пределах доступной области.
Он полностью отвечает за расположение элементов на макете. Из-за этого нам не требуется задавать какой-либо родительский макет.
LayoutManager отвечает за размер и позиционирование элементов RecyclerView, а также за определение того, когда следует перерабатывать элементы, которые больше не видны пользователю.
Изменяя LayoutManager, RecyclerView может использовать для реализации стандартного списка прокрутки по вертикали, горизонтали, в виде сетки и используя сложное представление, то есть в нём динамически будут отображаться элементы, которые подстраиваются под произвольный размер.
Адаптеры выполняют две роли – они обеспечивают доступ к базовому набору данных и отвечают за создание правильной компоновки для отдельных элементов.
Адаптеры всегда были частью Android и использовались во многих местах, например, в ListView, Spinner.
Эти классы наследуются от AdapterView.
Для RecyclerView «Гугл» решил заменить старый интерфейс адаптера новым базовым классом RecyclerView.Adapter.
Поскольку RecyclerView.Adapter является абстрактным, вам придётся реализовать следующие три метода:
- public ViewHolder onCreateViewHolder (ViewGroup parent, int viewType);
- public void onBindViewHolder(ViewHolder holder, int position);
- public int getItemCount().
ViewHolder в перечисленных методах является параметром общего типа.
Вы указываете конкретный тип, который следует использовать при подклассификации RecyclerView.Adapter.
Фрагмент представляет поведение или часть пользовательского интерфейса в активити.
Разработчик может объединить несколько фрагментов в одну активити для построения многопанельного пользовательского интерфейса и повторного использования фрагмента в нескольких активностях.
Фрагмент можно рассматривать как модульную часть активити. Такая часть имеет свой жизненный цикл и самостоятельно обрабатывает события ввода. Кроме того, её можно добавить или удалить непосредственно во время выполнения активити.
Это нечто вроде вложенной активити, которую можно многократно использовать в различных активити.
Фрагмент всегда должен быть встроен в активити, и на его жизненный цикл напрямую влияет жизненный цикл активити. Например, когда активити приостановлена, в том же состоянии находятся и все фрагменты внутри неё.
Когда активити уничтожается, уничтожаются и все фрагменты.
Однако пока активити выполняется, что соответствует состоянию возобновления жизненного цикла, можно манипулировать каждым фрагментом независимо: например, добавлять или удалять их.
Когда фрагмент добавлен как часть макета активити, он находится в объекте ViewGroup внутри иерархии представлений активити и определяет собственный макет представления.
Разработчик может вставить фрагмент в макет активити двумя способами. Для этого следует объявить фрагмент в файле макета активити как элемент fragment или добавить его в существующий объект ViewGroup в коде приложения.
Впрочем, фрагмент не обязан быть частью макета активити.
Можно использовать фрагмент без интерфейса в качестве невидимого рабочего потока активити.
Фрагменты впервые появились в Android версии 3.0 главным образом для обеспечения большей динамичности и гибкости пользовательских интерфейсов на больших экранах, например, у планшетов.
Поскольку экраны планшетов гораздо больше, чем у смартфонов, они предоставляют больше возможностей для объединения и перестановки компонентов пользовательского интерфейса. Фрагменты позволяют делать это, избавляя разработчика от необходимости управлять сложными изменениями в иерархии представления.
Разбивая макет активити на фрагменты, разработчик получает возможность модифицировать внешний вид активити в ходе выполнения и сохранять эти изменения в стеке переходов назад, которым управляет активити.
Следует разрабатывать каждый фрагмент как модульный и повторно используемый компонент активити.
Поскольку каждый фрагмент определяет собственный макет и собственное поведение со своими обратными вызовами жизненного цикла, разработчик может включить один фрагмент в несколько активити. Поэтому он должен предусмотреть повторное использование фрагмента и не допускать, чтобы один фрагмент непосредственно манипулировал другим.
Это особенно важно, потому что модульность фрагментов позволяет зменять их сочетания в соответствии с различными размерами экранов.
Если приложение должно работать и на планшетах, и на смартфонах, можно повторно использовать фрагменты в различных конфигурациях макета, чтобы оптимизировать взаимодействие с пользователем в зависимости от доступного размера экрана.
На смартфоне может возникнуть необходимость в разделении фрагментов для предоставления однопанельного пользовательского интерфейса, если разработчику не удаётся поместить более одного фрагмента в одну активити.
Пример того, как два модуля пользовательского интерфейса, определённые фрагментами, могут быть объединены внутри одной активити для работы на планшетах, но разделены на смартфонах, показан на рис. 27.
Рис. 27. Схема работы с фрагментами
Например, новостное приложение может использовать один фрагмент для показа списка статей слева, а другой – для отображения статьи справа.
Оба фрагмента отображаются за одну активити рядом друг с другом и каждый имеет собственный набор методов обратного вызова жизненного цикла и управляет собственными событиями с пользовательского ввода.
Таким образом, вместо применения одной активити для выбора статьи, а другой – для чтения статей пользователь может выбрать статью и читать её в рамках одной активити, как на планшете, изображенном на рис. 27.
Таким образом, приложение поддерживает как планшеты, так и смартфоны благодаря повторному использованию фрагментов в различных сочетаниях.
Для создания фрагмента необходимо создать подкласс класса Fragment или его существующего подкласса.
Класс Fragment имеет код, во многом схожий с кодом активити. Он содержит методы обратного вызова, аналогичные методам активити: onCreate, onStart, onPause и onStop.
На практике, если требуется преобразовать существующее приложение Android так, чтобы в нём использовались фрагменты, достаточно просто переместить код из методов обратного вызова активити в соответствующие методы обратного вызова фрагмента.
Как правило, необходимо реализовать следующие методы жизненного цикла.
Система вызывает метод onCreate, когда создаёт фрагмент.
В своей реализации разработчик должен инициализировать ключевые компоненты фрагмента, которые требуется сохранить, когда фрагмент находится в состоянии паузы или возобновлён после остановки.
Метод onCreateView система вызывает при первом отображении пользовательского интерфейса фрагмента на дисплее.
Для прорисовки пользовательского интерфейса фрагмента следует возвратить из этого метода объект View, который является корневым в макете фрагмента.
Если фрагмент не имеет пользовательского интерфейса, можно возвратить null.
Метод onPause система вызывает как первое указание того, что пользователь покидает фрагмент. Это не всегда означает уничтожение фрагмента. Обычно именно в этот момент необходимо фиксировать все изменения, которые должны быть сохранены за рамками текущего сеанса работы пользователя, поскольку пользователь может не вернуться назад.
В большинстве приложений для каждого фрагмента должны быть реализованы как минимум эти три метода.
Однако существуют и другие методы обратного вызова, которые следует использовать для управления различными этапами жизненного цикла фрагмента.
Существует ряд подклассов, которые, возможно, потребуется расширить вместо использования базового класса Fragment.
DialogFragment – это отображение перемещаемого диалогового окна.
Использование класса DialogFragment для создания диалогового окна является хорошей альтернативой вспомогательным методам диалогового окна в классе Activity.
Дело в том, что он даёт возможность вставить диалоговое окно фрагмента в управляемый активити стек переходов назад для фрагментов, что позволяет пользователю вернуться к закрытому фрагменту.
ListFragment – это отображение списка элементов, управляемых адаптером, например, SimpleCursorAdapter. Он аналогичен классу ListActivity.
Данный класс предоставляет несколько методов для управления списком представлений – например, метод обратного вызова onListItemClick для обработки нажатий.
PreferenceFragment – это отображение иерархии объектов Preference в виде списка, аналогично классу PreferenceActivity. Этот класс полезен, когда в приложении создаётся активити «Настройки».
Фрагмент обычно используется как часть пользовательского интерфейса активити, при этом он добавляет в активити свой макет.
Чтобы создать макет для фрагмента, разработчик должен реализовать метод обратного вызова onCreateView, который система Android вызывает, когда для фрагмента наступает время отобразить свой макет. Реализация этого макета должна возвращать объект View, который является корневым в макете фрагмента.
Чтобы возвратить макет из метода onCreateView, можно выполнить его расширение из ресурса макета, определённого в XML-файле.
Для этого метод onCreate предоставляет объект LayoutInflater:
public class CameraFragment extends Fragment {
public CameraFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false);
}
}
В приведённом коде конструкция R.layout.fragment_camera... является ссылкой на ресурс макета по имени fragment_camera.xml, хранящийся в ресурсах приложения.
Параметр container, придаваемый методу onCreateView, является родительским классом ViewGroup из макета активити, в который будет вставлен макет фрагмента.
Параметр savedInstanceState является классом Bundle, который предоставляет данные о предыдущем экземпляре фрагмента во время возобновления фрагмента.
Метод inflate принимает три аргумента.
Первый – это идентификатор ресурса макета, раздувание которого требуется выполнить.
Второй – это объект класса ViewGroup, который должен стать родительским для макета после расширения.
Передача параметра container необходима для того, чтобы система смогла применить параметр макета к корневому представлению раздутого макета, определяемому родительским представлением, в которое направляется макет.
И третье – это логическое значение, показывающее, следует ли прикрепить макет к объекту ViewGroup во время расширения.
В данном случае это false, потому что система уже вставляет раздутый макет в объект container, и передача значения true создала бы лишнюю группу представления в окончательном макете.
Мы увидели, как создавать фрагмент, представляющий макет. Теперь необходимо добавить фрагмент в активити. Как правило, фрагмент добавляет часть пользовательского интерфейса в активити, и этот интерфейс встраивается в общую иерархию представлений активити.
Разработчик может добавить фрагмент в макет активити двумя способами.
Первый – это объявить фрагмент в файле макета активити. В этом случае можно указать свойства макета для фрагмента, как будто он является представлением. Например, файл макета активити с двумя фрагментами может выглядеть как на рис. 28.
Рис. 28. Содержимое файла макета активити
Атрибут name в элементе fragment определяет класс Fragment, экземпляр которого создается в макете.
Когда система создаёт данный макет активити, она создаёт экземпляр каждого фрагмента, определённого в макете, и для каждого вызывает метод onCreateView, чтобы получить макет каждого фрагмента.
Система вставляет объект View, возвращённый фрагментом, непосредственно вместо элемента fragment.
Каждый фрагмент должен иметь уникальный идентификатор, который система сможет использовать для восстановления фрагмента в случае перезапуска активити.
Предоставить идентификатор фрагменту можно тремя способами:
1) указать атрибут android:id с уникальным идентификатором;
2) указать атрибут android:tag с уникальной строкой.
3) ничего не предпринимать, чтобы система использовала идентификатор контейнерного представления.
Второй способ добавления фрагмента в активити – это программный способ: через добавление фрагмента в существующий объект ViewGroup.
Для хранения пар «ключ – значение» в Android можно использовать SharedPreferences. Это файл, который может быть доступным только вашему приложению либо всем.
Создание этого файла и работа с ним производится через класс SharedPreferences.
SharedPreferences чаще всего используется для хранения простых настроек пользователя, включен ли флаг уведомлений, Id пользователя и других.
Получить объект SharedPreferences можно, используя следующие три метода.
1. Метод getSharedPreferences() может быть вызван у любого Context с именем в качестве первого параметра и параметром доступа в качестве второго параметра.
2. Метод getPreferences()может быть вызван у Activity, если вам нужен только один shared preferences файл. Этот метод получает файл по умолчанию и вам не нужно указывать имя, но по-прежнему нужно указывать параметр доступа.
3. Метод PreferenceManager.getDefaultSharedPreferences() получает файл shared preferences по умолчанию. В качестве параметра принимает Context. Название указывать не нужно, так как оно генерируется само из имени пакета.
Чаще всего в качестве константы доступа используется Context.MODE_PRIVATE. В этом случае файл shared preferences будет доступен только вашему приложению.
JSON – это текстовый формат данных, легко читаемый человеком и используемый для сериализации объектов и обмена данными.
Сериализация – это сохранение состояния объекта для передачи и последующей десериализации – восстановления.
В любом правильно сформированном json-файле можно выделить два основных вида структур: это поля типа «ключ – значение» и упорядоченный набор значений.
Ключ всегда является строковым типом, а значения могут принимать вид строки, числа, литералов true, false, null, массивов либо объектов, вложенных в json.
Объекты обрамляются фигурными скобками, внутри которых хранится неупорядоченное множество пар «ключ– значение».
Ключ от значения отделяется двоеточием.
Между парами ставится запятая.
Массивы в json заключаются в квадратные скобки, между значениями также ставится запятая.
Рассмотрим пример:
{
“name”:”Иванов Иван”,
“age”:42,
“kids”:[
{
“name”:”Иванов Артём”,
“age”:8,
“kids”:null
}
]
}
Здесь мы видим простое отображение объекта, в котором угадывается модель человека.
Поля представляют собой имя, возраст и детей.
Что характерно, дети – это массив объекта такого же типа «Человек», поэтому у них тоже есть имя, возраст и поле "kids", но уже со значением null.
Со структурой разобрались.
Теперь попробуем собрать из этого сериализованного файла объект.
Прежде всего нам нужен класс «модель» или, как его ещё называют, "pojo" – plain old java object, старый добрый java-объект.
pojo-классы не нужно писать вручную.
Напишите полное название протокола HTTP.
Каково полное название средств разработки SDK на английском языке?
Сколько (в долларах, $) стоит аккаунт разработчика Android?
Сколько (в долларах, $) стоит аккаунт разработчика iOS в год?
Каково полное название компонента AVD на английском языке?
Какой организации принадлежит Android?
Какие папки находятся внутри папки main?
Какова продолжительность (в секундах) показа константы Toast.LENGTH_LONG?
Какова продолжительность (в секундах) показа константы Toast.LENGTH_SHORT?
В каком году Энди Рубин основал Android Incorporated?
2005
2003
2002
2006
Укажите язык программирования для операционной системы Android.
C++
JS
Java
Python
Первый телефон на Android – это …
T-Mobile G1
LG GW620
Galaxy I7500
Motorola DEXT
Сколько стоит программное обеспечение для разработки приложений под Android?
Оно бесплатно
25 $ ежегодно
99 $ ежегодно
25 $ один раз
Укажите программы – аналоги Android Studio.
Netbeans
Eclipse
Visual Studio
Intellij IDEA
Photoshop
Dreamweaver
Scratch
Lazarus
Что означает система с отрытым исходным кодом?
Это значит, что система может видоизменяться по запросу в любое время.
Это значит, что компания открыта для сотрудничества с любой желающей компанией или разработчиками.
Это значит, что любой разработчик может бесплатно скачать исходный код, изменить его и опубликовать свою версию.
Это значит, что любой разработчик может за определённую плату скачать исходный код, изменить его и продавать уже свою версию.
На каком уровне архитектуры Android находятся приложения (Applications)?
На втором
На четвертом
На третьем
На первом
Сколько стоит подписка на пользование продуктом Android Studio для разработки приложений?
25 $
9.99 $
Подписка бесплатна
1.99 $
В чем отличие папки drawable от mipmap?
В папке drawable хранятся иконки приложения, в папке mipmap – стили приложения
В папке mipmap хранятся стили приложения, в папке drawable – цвета приложения
В папке drawable хранятся векторные и растровые изображения приложения, в папке mipmap – иконки приложения
В папке mipmap хранятся векторные и растровые изображения приложения, в папке drawable – иконки приложения
Какое средство позволяет скачать инструментарий, позволяющий работать с API?
Android Downloader
Android SDK Manager
Java SDK Manager
Instrument Downloader
Для чего нужен Android Emulator?
Чтобы тестировать Android-приложения в различных конфигурациях
Чтобы играть в игры
Это замена Android OS, которая устанавливается на ПК
Чтобы можно было программироватьC
Что такое API?
Интегрированная среда разработки
Инструменты разработки под Android
Интерфейс прикладного программирования
Набор средств разработки
Чем являются Eclipse и IDEA?
API
IDE
SDK
ADT
К проблемам разработки под ОС Android можно отнести …
отсутствие качественного инструментария разработчика
плохую защиту ядра операционной системы Android от прикладного программного обеспечения
отсутствие возможности тестирования разрабатываемого прикладного программного обеспечения на всех устройствах
открытость файловых систем пользователю
Укажите инструментарий программиста, включающий средства для разработки, компиляции и сборки программ для телефонов.
Android SDK
Android NDK
JDK
Плагин ADT
AndroidManifest – это …
инструкции для Android-системы, указывающие, в каком режиме должно работать приложение
файл, в котором содержится вся самая важная информация о нашем приложении, которую вы же и описываете
файл, который описывает структуру вашего Android-проекта
библия всех Android-разработчиков
Разработайте мобильное приложение на Android с одной Activity, на которой расположены 4 элемента: 3 кнопки и textview. При нажатии на первую кнопку в textview отображается значение случайной цифры в диапазоне от 1 до 9. При нажатии на вторую кнопку в textview отображается значение случайной цифры в диапазоне от 10 до 99. При нажатии на третью кнопку в textview отображается значение случайной цифры в диапазоне от 100 до 999.
Разработайте мобильное приложение для ОС Android, в котором при нажатии на кнопку отображается сгенерированное случайным образом число в диапазоне от 10 до 99. На другой кнопке приложение демонстрирует произведение появившихся чисел.
Что такое процесс?
Представьте себе некую абстрактную сущность – объект, которому выделены ресурсы системы, это адресное пространство в памяти и время процессора для выполнения программы.
Соответственно, каждая программа по умолчанию запускается в отдельном процессе.
Процесс не может просто так воспользоваться чужими ресурсами, ресурсами другого процесса, например, памятью.
Для этого нужно настроить межпроцессное взаимодействие.
Помимо всего прочего, в процессе существуют потоки, как минимум один, который создаётся при запуске программы.
Поток, или Thread, – это сущность, которая выполняет программные инструкции, иначе говоря, выполняет наш код.
Допустим, в процессе мы создаём несколько потоков, которые занимаются своими задачами, выполняют свой код и так далее.
Пока они работают с разными переменными, проблем нет, каждый живёт в своём собственном мирке. Например, один поток может рисовать интерфейс, а второй выполнять сетевой запрос. Всё хорошо, друг другу они не мешают.
Но если потоки начинают использовать одну переменную, то возникают проблемы.
Представим программу, которая считает количество людей в доме.
Люди расположены на разных этажах, мы создаём по потоку для каждого этажа, создаём общую переменную, которая эти потоки будет инкриминировать.
На рис. 29 виден код этой программы.
Рис. 29. Код программы с использованием потоков
Попробуйте переписать и запустить этот код – и наверняка вы удивитесь результату. Спойлер: он будет меньше ожидаемого.
Проблема в том, что если потоков больше, чем количество процессоров, то процессор переключается между потоками, для того чтобы они выполнялись более-менее параллельно. Только вот момент переключения случаен и время, уделённое потоку, тоже случайно или может зависеть от приоритета.
В данном конкретном примере первый поток может, например, считать значения из переменной в свой кеш, увеличить это значение, но не успеть его записать из-за того, что процессор переключился на другой поток.
Другой поток в свою очередь считает то же самое значение, которое считал и первый поток, увеличит это значение, но запишет.
Потом процессор переключится обратно на первый поток, который продолжит свою работу с того места, на котором он остановился, то есть перед записью увеличенного значения в переменную.
Другими словами, вычисления второго потока просто исчезнут.
Для того чтобы избежать таких проблем, существуют механизмы синхронизации.
Синхронизация – это механизм, который позволяет потоку спокойно завершить работу, не будучи прерванным другим потоком.
Синхронизация достигается разными способами в зависимости от языка программирования, платформы, намерений программиста.
В Java можно выделить ключевое слово synchronized, а также то, что любой объект может выступать как mutex, то есть идентификатор того, что поток занят или свободен.
Это не единственные способы обеспечения синхронизации в Java.
Если рассматривать предыдущий пример, то мы могли бы выделить несколько потоков, в которые записывали бы количество людей в свою локальную переменную, а в конце синхронизировано увеличивали бы общий счётчик на рассчитанное количество, и так по всем этажам.
Вы можете попробовать реализовать такую программу, используя executer’ы.
Разделение «один этаж – один поток» позволяет запустить несколько потоков этажей параллельно. Ведь они не мешают друг другу увеличивать свои собственные счётчики.
Изменение общего счётчика «один раз за этаж» вместо «количества людей на этаже раз» хорошо скажется на производительности.
В Android есть main Thread – главный поток, который отвечает за отрисовку интерфейса (рис. 30).
Рис. 30. Схема взаимодействия потоков
Чем меньше нагрузка на main Thread, тем отзывчее и плавнее интерфейс и, соответственно, тем больше шансов того, что пользователь не «влепит» одну звёздочку. Поэтому с точки зрения производительности задачи, не связанные с отрисовкой интерфейса, желательно опрокинуть в фоновый поток.
Это относится к тяжёлым задачам, например, к сетевым запросам, запросам в базу данных, каким-то тяжёлым манипуляциям с данными или с изображениями.
Разработчики самого Android стараются надавить на разработчиков приложений под Android, чтобы они не захламляли main Thread. Поэтому было решено запретить сетевые запросы из main Thread вообще.
Параллельно с этим развитие платформы Android приводило к появлению новых инструментов многопоточного взаимодействия.
В современной разработке нативные решения постепенно уступают место сторонним библиотекам, которые реализуют тот или иной архитектурный паттерн.
Если разработчик в состоянии реализовать любую функциональность с помощью того, что предлагает Android framework, то ему не составит труда разобраться в сторонней библиотеке.
Если проект ожидается большим или постепенно разрастается до больших размеров, то уже тогда имеет смысл подключить архитектурную библиотеку.
Для создания нового потока нужно вызвать конструктор new Thread.
Чтобы запустить этот новый поток, нужно вызвать на нем метод start. Тогда выполнится код в методе run:
public class MyThread extends Thread{
@Override
public void run() {
//do something
}
}
Чтобы поток делал что-то полезное, нужно передать ему в конструктор объект типа Runnable, вписав в run, что́ нужно сделать, либо отнаследоваться от Thread и переопределить его метод run и тоже туда что-то вписать:
new Thread (new Runnable() {
@Override
public void run() {
//do something
}
});
new MyThread().start();
Поток живёт, пока выполняется код в методе run, после этого поток уничтожается.
Обратите внимание на тип возвращаемых значений в методе run – это void. Иными словами, ничего не возвращается.
Синхронизация нужна для того, чтобы несколько потоков могли безопасно оперировать общими переменными.
Ключевое слово synchronized – это самый простой способ сделать метод или блок кода в методе потокобезопасным.
Synchronized гарантирует, что метод, или блок кода, помеченный этим словом, будет выполняться только в одном потоке одновременно. Причём можно выбирать, сделать ли синхронизированным код на уровне объекта либо же на уровне класса.
Для блокировки на уровне класса нужно, чтобы метод был статическим либо передавать synchronized сам класс или статический объект.
Разница между синхронизированным методом и блоком в том, что блок требует объект в качестве монитора.
Если, например, у нас есть два метода, которые должны быть синхронизированы, но при этом нет опасности, что каждый метод может вызываться отдельно в разных потоках одновременно, то можно тело каждого из методов обернуть в synchronized-блок и передавать разные объекты в качестве мониторов.
В многопоточном программировании возможна ситуация, когда выполнение работы одного потока зависит от работы другого потока.
Рассмотрим пример на рис. 31. Поток 1 входит в синхронизированный метод, одновременно блокирует этот метод для других потоков и внезапно осознаёт, что ему не хватает каких-то условий для выполнения своего кода.
Тогда этот поток 1 вызывает метод wait, тем самым освобождая монитор и переводя себя в режим ожидания.
Поток 2 в свою очередь заходит в синхронизированный метод и захватывает монитор.
Давайте предположим, что у него всё хорошо с условиями, он выполняет свою работу и в конце вызывает notify, тем самым уведомляя поток 1, что ему можно работать дальше.
Рис. 31. Схема синхронизации двух потоков
Соответственно, поток 2 заканчивает выполнять свой код, покидает синхронизированный метод и освобождает монитор.
Только после этого поток 1 продолжает свою работу со следующего после wait выражения.
Есть разница между notify и notifyAll. Она заключается в том, что в случае notify система сама случайно выбирает, какой поток из ожидающих запустить, если их несколько.
notifyAll запускает их сразу все, и потоки соревнуются между собой, кто захватит монитор.
Возможна ситуация, что несколько потоков будут зависеть друг от друга, то есть каждому потоку нужно будет, чтобы другой поток выполнил свой код, и так по кругу.
Это называется dead lock, этого следует избегать.
Если переменная помечена как volatile, то изменение её значения в одном потоке сразу становится заметно в других потоках.
Когда поток оперирует с внешней переменной, на самом деле он оперирует с её копией и в конце концов записывает значение копии в оригинал.
Он записывает значение той переменной, с которой он работал сам, во внешнюю переменную, из которой он брал значение.
Бывают случаи, что поток может быть прерван до того, как запишет новое значение копии в оригинал. Тогда, с точки зрения прервавшего потока, в переменной будет старое значение, необновлённое, и поток отработает с ним, после этого вернёт управление первому потоку, а первый поток запишет своё значение из копии в оригинал. А Volatile приводит к тому, что при изменении переменной её новое значение сразу будет видно в других потоках.
Управлять потоками сложно.
Сложно создавать потоки, проверять условия, синхронизировать, передавать задачи, получать результаты.
К счастью, в Java есть так называемые executor'ы и executorServic'ы.
Executor'ы добавляют новый уровень абстракции.
Мы уже не создаем потоки с помощью new Thread, вместо этого мы пользуемся фабричными методами, получая уже сформированный пул потоков:
ExecutorService executorService = Executors.newFixedThreadPool(4);
Future<String> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
//do something
return result;
}
});
while (!future.isDone()) {
TimeUnit.MILLISECONDS.sleep(100);
}
String result = future.get();
Далее мы просто передаем задачи executor'у, и он сам распределяет их между потоками.
Это очень удобный и популярный механизм многопоточной работы.
В метод submit executor'а можно передать объект типа callable.
Callable, в отличие от runnable возвращает generic результат типа future.
Future – это что-то вроде контейнера для окончательного результата.
Если метод isDone вернул true, то можно достать результат из future через метод get. Но вообще-то можно просто вызвать метод get, только учтите, что этот метод блокирующий, то есть возврат значения займет столько времени, сколько потребуется.
В пакете java.util.concurrent хранится очень много разных классов, которые облегчают многопоточную работу.
Тут и синхронизированные коллекции, и замки, и atomic-объекты, и различные синхронизаторы, объекты, которые заточены решать разные задачи.
Если пройтись по компонентам Android, то решение вопроса о многопоточности напрашивается сам собой.
На самом деле сервис можно настроить на запуск в отдельном потоке, и он может работать даже в свёрнутом приложении.
Также он может работать между приложениями.
Сервисы чаще всего работают в связке с BroadcastReceiver'ом, чтобы можно было уведомлять activity о том, что работа завершена, или уведомлять о прогрессе задачи.
Проблема заключается в отсутствии гибкости и сложности этого подхода.
Сервис хорошо подходит, если нам нужно, например, скачать файл или отслеживать местоположение, то есть для задач, в которых не требуется участие пользователя.
AsynkTask – это инструмент, который был добавлен для решения недолгих задач. Одним из его преимуществ является простота интерфейса.
Другим его преимуществом является явное разделение на то, что должно быть выполнено до фоновой операции, в фоне, после фоновой операции, а также – как отображать прогресс операции.
Однако из-за некоторых особенностей реализации с AsynkTask очень легко поймать утечку контекста.
HandlerThread работает вместе со связкой HaMeR.
HandlerThread использован для гибкого, за счёт своей низкоуровности, взаимодействия с главным потоком.
Взаимодействие происходит за счёт обмена сообщениями.
Loaders – механизм столь же мощный, сколь и сложный. Они зависят от контекста приложения, что, в частности, означает, что их можно запустить на одном экране, а результат получить в другом.
Service, или сервис, – это компонент приложения, который используется для выполнения долгих операций, чаще всего в фоновом потоке без взаимодействия с пользовательским интерфейсом.
Сервисы могут работать в другом процессе – в этом случае для связи с ними используется межпроцессное взаимодействие.
В качестве примера работы сервиса можно привести прослушивание музыки в любом приложении. Если музыка продолжает играть, даже если вы свернули приложение, значит, используется сервис.
У Android различают три вида сервисов – Foreground, Background, Bound.
Foreground Service – это сервис, работа которого важна для пользователя, и внезапная остановка сервиса будет заметна и чаще всего воспринята негативно. К слову, проигрывание музыки как раз относится к такому виду сервиса.
Другой пример – скачивание файла.
Вы, вероятнее всего, замечали, что и при скачивании файла, и при проигрывании музыки в статус-баре появляется иконка, связанная с нотификацией, которая дает знать пользователю, что в телефоне происходит какая-то деятельность.
Наличие такой нотификации, уведомления, – это обязательное условие для запуска Foreground Service, это требование системы.
Foreground Service имеет один из самых высоких приоритетов среди процессов. Это означает, что очень маловероятно, что система уничтожит его, если рабочему Activity на экране не будет хватать ресурсов.
Рабочее Activity, к слову, имеет самый высокий приоритет.
Background Service, или просто – запущенный сервис – это сервис, работа которого неочевидна для пользователя. Это может быть сетевой запрос, запись в базу данных, сбор статистики и тому подобное.
Запущенный сервис живет до тех пор, пока не закончит свою работу, либо до вызова метода Stop Service. Также он может быть остановлен системой.
Background Service – это наиболее распространённый вид сервиса. Для его запуска используется метод Start Service, которому в качестве аргумента мы передаем Intent, как и в случае с методом Start Activity.
Bound Service – это сервис, который с помощью интерфейса позволяет вызывающей стороне, Activity чаще всего, взаимодействовать с ним, отправлять запросы и получать результаты.
Для привязки сервиса используется метод Bind Service.
Bound Service умрёт тогда, когда от него отвяжутся или будут убиты все компоненты, которые были к нему привязаны, если только сервис ещё не был запущен через метод Start Service.
При создании сервиса чаще всего наследуются от одного из двух вариантов базового класса. Это, собственно, сам Service, родоначальник всех сервисов, и Intent-сервис – его упрощённая версия.
По умолчанию сервис работает в главном потоке, однако это не всегда желаемое поведение.
При наследовании от Service создание потока, очистку после окончания работы, да и сами условия остановки сервиса приходится обрабатывать самому.
В качестве альтернативы используется Intent Service. Это сервис, который сам создает рабочий поток и все входящие Intent'ы обрабатывает в нем по очереди.
Intent Service завершает себя после обработки всех Intent'ов.
Работа с Intent Service – это простой и достаточно эффективный способ организации фоновых операций.
Жизненный цикл сервиса намного проще, чем жизненный цикл Activity, но может развиваться двумя разными путями в зависимости от того, как он был запущен (рис. 32).
Рис. 32. Жизненный цикл Service
Метод onCreate вызывается один раз при создании сервиса вне зависимости от того, как он был создан.
В нём происходит первоначальная настройка сервиса, инициализация необходимых переменных, создание потоков и тому подобное.
Если сервис был запущен с помощью метода Bind Service, то после onCreate вызовется метод onBind.
Этот метод возвращает объект интерфейса IBinder – как раз тот, с помощью которого и будет происходить взаимодействие между сервисом и его вызывающей стороной.
Эта вызывающая сторона может отказаться от сервиса через метод Unbind Service.
В этом случае у сервиса сработает callback onUnbind, возвращающий булево значение, которое указывает, можно ли к сервису привязаться заново.
При перепривязке сработает метод onRebind.
Метод onDestroy вызывается при уничтожении сервиса.
Если Intent Service закончил обрабатывать все Intent'ы своей очереди либо от сервиса отвязали все компоненты, которые были к нему привязаны, либо сервис был остановлен извне методом StopService, или самостоятельно методом StopSelf, то вызовется onDestroy.
BroadcastReceiver – это приемник сообщений, посылаемых системой, другими приложениями или вашим приложением при каком-либо событии, реагирующий на них и выполняющий какую-либо работу, отдельно от обычного процесса взаимодействия с приложением.
Приведем простой пример: вы запускаете музыкальное приложение, подключаете наушники и включаете какую-нибудь композицию. Теперь, если вы выдернете наушники из устройства, музыка остановится.
Откуда приложение знает, что наушники были выдернуты? Всё просто. При подключении или отключении наушников система отправляет event ACTION_HEADSET_PLUG, а приложение его обрабатывает, приостанавливая музыку.
Подобных систем сообщений достаточно много.
При создании приемника мы наследуемся от базового класса BroadcastReceiver:
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//TODO: do something
}
}
В нем мы переопределяем метод onReceive, добавляя нужную нам логику. Однако чтобы приёмник начал принимать сообщения, его необходимо зарегистрировать.
Возможны два варианта регистрация приемника: в манифесте приложения и в коде.
Приёмник, зарегистрированный в манифесте, будет принимать сообщения, даже если само приложение не запущено на момент срабатывания события. В этом его главное отличие от приёмника, зарегистрированного в коде.
Для регистрации в коде используется метод контекста registerReceiver.
Соответственно, чтобы отвязать приёмник, используется метод unregisterReceiver.
Конечно же, мы не ограничены только системными сообщениями, мы можем отправлять свои. Для этого используется метод sendBroadcast, в аргументе которого передается intent с нужным нам action'ом, на который и подписан приёмник.
Также имеет смысл передавать какие-либо данные через putExtra:
Intent intent = new Intent();
intent.setAction (“ru.rosdistant.edu.SOMETHING_JUST_HAPPEN”);
intent.putExtra (“ARG_DATA”, “SOME_VALUE”);
sendBroadcast(intent);
В Android также доступны следующие возможности: мы, например, можем использовать метод sendOrderedBroadcast, и теперь, если несколько приёмников подписаны на один и тот же action, они будут получать сообщения согласно своему приоритету, по очереди.
Каждый приёмник сможет обработать сообщение, изменить и передать его дальше либо вообще не передавать.
Приведём пример: допустим, у нас в приложении messenger есть два приёмника, настроенных получать push-уведомления с сервера о новых сообщениях.
Один из приёмников, с низким приоритетом, зарегистрирован в манифесте. Работа его заключается в том, что он показывает уведомления о новом сообщении, как только получает свое событие. Пользователь будет знать о новых сообщениях, даже если приложение на данный момент не работает.
Другой приёмник зарегистрирован в коде. У него более высокий приоритет.
Мы предполагаем, что пользователю не надо видеть уведомления о сообщении, если он и так находится в приложении и своими глазами видит это новое сообщение, поэтому наш приёмник, зарегистрированный в коде, гасит event, не передаёт его дальше.
AsyncTask был разработан для выполнения недолгих, длиной несколько секунд, операций.
Он требует, чтобы его запускали в главном потоке.
Чаще всего его создают и запускают прямо в активити.
Как и в случае с другими классами Framework Android, нам нужно создать класс-наследник AsyncTask.
Класс требует generic-параметры.
Их три. Первый – это тип входного аргумента, второй – тип значения прогресса, третий – тип возвращаемого значения.
Запускается AsyncTask с помощью метода execute, в котором передается входное значение.
Далее нам нужно реализовать методы AsyncTask:
mSampleTask.execute(10L) // запуск задачи
mSampleTask.cancel(true) // отмена задачи
class SampleTask extends AsyncTask<Long, Integer, String> {
void onPreExecute(); // UI-поток перед фоновой операцией
String doInBackground(Long… longs); // фоновый поток
void onPostExecute(String s); // UI-поток после фоновой операции
void publishProgress(1,2,3,4); // внутри фоновой операции
void onProgressUpdate(Integer… values); // в UI-потоке
boolean isCanceled(); // внутри фоновой операции
void onCanceled(); // в UI-потоке
}
Обязательным является метод doInBackground, который получает на вход переменное количество параметров первого типа и возвращает третий тип.
Как следует из названия, код в этом методе выполняется в фоновом потоке.
Однако до начала выполнения метода doInBackground выполнится код в методе onPreExecute, причём он выполнится в главном потоке, что означает, что мы имеем доступ к интерфейсу.
Например, мы можем показать progress bar перед выполнением фоновой операции.
После выполнения doInBackground результат фоновой операции попадает на вход в onPostExecute.
onPostExecute также выполняется в главном потоке, то есть мы можем отобразить результат в интерфейсе.
Что касается отображения прогресса, то в методе doInBackground можно вызвать метод publishProgress.
Методу publishProgress можно передавать на вход аргумент второго типа. Это приведет к тому, что вызовется метод onProgressUpdate, который сработает тоже в главном потоке.
Также запущенные операции можно отменить.
Для этого на AsyncTask вызывается метод Cancel, который принимает на вход булево значение.
Передаем true, если мы разрешаем системе самой «убить» процесс.
Альтернативно мы можем сами проверять значения задач внутри doInBackground с помощью метода isCanceled.
Обработка же отмененной операции будет в методе onCanceled, который также сработает в главном потоке.
Давайте создадим AsyncTask, который загружает картинку из Интернета.
На вход будет подаваться URL картинки, на выходе будет bitmap.
Создадим разметку, на которой определим imageView, button и круглую progress bar.
Progress bar сделаем изначально невидимым, visibility: invisible.
Изначально у нас пустой экран с кнопкой, при нажатии на кнопку появляется progress bar.
После окончания загрузки progress bar исчезает, а в imageView появляется картинка.
Код AsyncTask следующий:
private class DomnloadImageTask extends AsyncTask<String, Void, Bitmap> {
@Override
protected void onPreExecute() {
mProgressBar.setVisibility(View.VISIBLE);
}
@Override
protected Bitmap doInBackground(String… strings) {
return getBitmap(strings[0]);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
mImageView.setImageBitmap(bitmap);
mProgressBar.setVisibility(View.INVISIBLE);
}
}
Он расположен в теле активити и является внутренним классом.
Входной параметр – строка, так как AsyncTask будет принимать на вход URL картинки.
Параметр прогресса – Void.
Мы не будем показывать процесс прогресса.
Выходной параметр – Bitmap, мы отправляем его в imageView.
Процесс показа и скрытия progress bar очевиден и прост.
Далее на кнопку «вешаем» listener, в нем создаем и вызываем AsyncTask.
В метод execute передаем URL картинки.
Ссылаясь внутри AsyncTask на элементы activity, мы удерживаем контекст.
Если во время работы AsyncTask мы перевернем телефон и тем самым вызовем пересоздание активити, то уничтоженное активити не сможет быть собрано сборщиком мусора до тех пор, пока AsyncTask продолжает свою работу. Иначе говоря, у нас возможна утечка контекста.
Проблему с утечкой контекста можно решить по-разному. Но суть одна: избавиться от жесткой ссылки на активити.
Рассмотрим преобразование активити в статичный внутренний класс.
Мы можем передать активити в конструктор AsyncTask, а внутри самого AsyncTask хранить слабую ссылку на активити, иначе говоря, weak reference:
private class DomnloadImageTask extends AsyncTask<String, Void, Bitmap> {
private WeakReference<MainActivity> mActivityWeakReference;
private DomnloadImageTask(MainActivity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}
//-----
@Override
protected void onPostExecute(Bitmap bitmap) {
MainActivity activity = mActivityWeakReference.get();
if (activity != null) {
activity.getImageView().setImageBitmap(bitmap);
activity.getProgressBar().setVisibility(View.INVISIBLE);
}
}
Обратите внимание на конструктор: он принимает на вход наше активити, но сохраняет его не просто в поле, а в слабую ссылку.
Далее каждый раз, когда нам нужно обратиться к полям активити, мы сначала достаем активити из weak reference и проверяем его на не null.
Таким образом, мы не мешаем сборщику мусора утилизировать активити.
В конструктор AsyncTask’а теперь мы передаем нашу активити.
Если на текущий момент на устройстве нет запущенных компонентов нашего приложения, то для этого приложения система выделяет отдельный процесс, в котором оно будет работать.
Первый поток, который создаётся в этом процессе, называется главным потоком, или MainThread'ом.
Он, помимо всего прочего, отвечает за рисование интерфейсов.
Все манипуляции с интерфейсом происходят в главном потоке, поэтому главный поток также называют UI-потоком.
Из других потоков изменять UI нельзя.
Поток выполняет свои инструкции, при необходимости записывает куда-то результат и благополучно самоуничтожается.
При этом создание потока – это относительно дорогая операция с точки зрения ресурсов.
После выполнения кода в onCreate, onStart и onResume наше приложение не завершается, поток не уничтожается, и, по-видимому, ничего не происходит до тех пор, пока пользователь не нажмёт на что-нибудь.
Тогда приложение реагирует на действия пользователя должным образом: откроет новый экран, поменяется интерфейс или произойдёт ещё что-нибудь.
Затем приложение снова начнёт ждать пользователя. Причём оно может его ждать хоть до полного окончания заряда батарейки. Поток не умирает, потому что он проделывает работу.
Выглядит это следующим образом: с MainThread'ом ассоциирован объект типа Looper.
Looper находится в бесконечном цикле, во-первых, не давая потоку уничтожиться, а во-вторых, проверяя, не произошло ли каких-нибудь действий пользователя, требующих реагирования.
С Looper ассоциирована очередь сообщений – MessageQueue.
Сообщения – объекты класса Message – представляют собой действия или команды.
Looper проверяет MessageQueue, и если в нём есть новое сообщение, то извлекает его и отправляет на обработку.
Существует ещё один специальный объект типа Handler, который ассоциируется с Looper'ом.
У Handler'а может быть только один Looper, но у Looper'а может быть сколь угодно Handler'ов.
Handler умеет отправлять сообщения в очередь сообщений своего Looper'а.
У сообщения в свою очередь есть ссылка на Handler, который это сообщение должен обработать.
Системные сообщения обрабатываются системными же Handler'ами.
Аббревиатура HaMeR ссылается на Handler, Message и Runnable.
Дело в том, что Handler может отправлять в очередь не только сообщения, но и Runnable-объекты. Причём код внутри метода run будет выполняться в том потоке, к Looper'у которого привязан Handler, который это сообщение отправил.
В MainThread'е находится объект Looper.
Looper в бесконечном цикле проверяет MessageQueue.
Если в MessageQueue находится новый Message, Looper отправляет его на обработку.
Передачу и обработку таких системных сообщений, как нажатие на кнопку или swipe, Android берёт на себя.
Если же мы хотим добавить обработку своих сообщений, то нам нужно создать объект типа Handler.
Handler умеет отправлять сообщения MessageQueue.
В Message есть ссылка на Handler, который должен его обработать.
Это может быть тот же Handler, который его отправил, либо какой-то другой Handler.
Когда мы создаём Handler с помощью конструктора без параметров, он привязывается к Looper'у текущего потока.
Но мы можем передавать конструктору Looper, и тогда Handler привяжется к нему.
Очень просто можно получить Looper главного потока с помощью статического метода getMainLooper.
Для отправки Runnable используется метод post и различные его вариации:
Handler handler = new Handler();
Handler bgHandler = new Handler(bgLooper);
Handler mainHandler = new Handler(Looper.getMainLooper());
handler.post (new Runnable() {
@Override
public void run() {
}
});
Runnable затем оборачивается Message; Message попадает в MessageQueue.
Looper достаёт его и отправляет обратно тому же Handler'у на выполнение.
Просто post в этом случае не очень полезен, но мы можем воспользоваться методом, например, postDelayed, который помимо самого Runnable передаёт ещё и задержку в миллисекундах, после которого код в run выполнится:
handler.postDelayed (new Runnable(), 4000);
Объекты типа Message не надо создавать самому.
Лучше воспользоваться одним из статических методов, который возвращает сообщение из общего пула сообщений.
Какой метод запускает новую активити?
Что такое процесс?
Что такое поток?
Что такое синхронизация?
Как называется первый поток, запущенный в процессе?
Как называется главный поток, который отвечает за отрисовку интерфейса?
Как называется приемник сообщений, посылаемых системой либо другими приложениями при каком-либо событии, реагирующий на них и выполняющий какую-либо работу отдельно от обычного процесса взаимодействия с приложением?
Назовите отличия между Notify и NotifyAll.
Что такое dead lock?
Назовите метод, который вызывается при уничтожении сервиса.
Назовите метод, возвращающий булевые значения, который указывает, можно ли к сервису привязаться заново.
Укажите инструменты для обеспечения многопоточности в Android:
Service
BroadcastReceiver
AsyncTask
HandlerThread
Loader
Для чего используют HandlerThread?
Для выполнения долгих фоновых операций
Для обновления пользовательского интерфейса
Для взаимодействия с главным потоком
Для асинхронизации
Какие виды сервисов есть в Android?
Foreground-service
Background-service
Bound-service
Lisp-service
Что такое Foreground-service?
Сервис, работа которого важна для пользователя; внезапная остановка сервиса будет заметна и, скорее всего, воспринята негативно
Сервис, работа которого неочевидна для пользователя
Сервис, который с помощью интерфейса позволяет вызывающей стороне взаимодействовать с ним: отправлять запросы и получать результаты
Сервис, которому не нужно взаимодействовать с пользователем
Выберите этапы жизненного цикла сервиса.
ОnCreate()
ОnDestroy()
ОnBind()
ОnClick()
Что такое Bound-service?
Сервис, который с помощью интерфейса позволяет вызывающей стороне взаимодействовать с ним: отправлять запросы и получать результаты
Сервис, работа которого неочевидна для пользователя
Сервис, работа которого важна для пользователя; внезапная остановка сервиса будет заметна и, скорее всего, воспринята негативно
Сервис, которому не нужно взаимодействовать с пользователем
Выберите верное утверждение об AsynсTask.
AsyncTask был разработан для выполнения недолгих операций
AsyncTask не требует запуска в главном потоке
Чаще всего его создают и запускают прямо в Activity
AsyncTask является фреймворком
Разработайте многопоточное мобильное приложение для ОС Android, в котором при нажатии на кнопку отображается сгенерированное случайным образом число в диапазоне от 10 до 99. На другой кнопке приложение демонстрирует количество нажатий на первую кнопку.
В результате изучения учебно-методического пособия:
студент:
сможет создать своё первое мобильное приложение для операционной системы Android на языке программирования Java с помощью среды разработки Android Studio;
преподаватель дисциплины:
сможет направить деятельность обучающегося в необходимое русло;
специалист:
может пройти переподготовку в области разработки мобильных приложений.
Введение в разработку приложений для ОС Android : учебное пособие / Ю. В. Березовская, О. А. Юфрякова, В. Г. Вологдина [и др.]. — 3-е изд. — Москва : Интернет-Университет Информационных Технологий (ИНТУИТ), Ай Пи Ар Медиа, 2021. — 427 c.
Пирская, Л. В. Разработка мобильных приложений в среде Android Studio : учебное пособие / Л. В. Пирская. — Ростов-на-Дону, Таганрог : Издательство Южного федерального университета, 2019. — 123 c.
Семакова, А. Введение в разработку приложений для смартфонов на ОС Android : учебное пособие / А. Семакова. — 3-е изд. — Москва : Интернет-Университет Информационных Технологий (ИНТУИТ), Ай Пи Ар Медиа, 2021. — 102 c.
Основные понятия
Android – операционная система для мобильных устройств: смартфонов, планшетных компьютеров, КПК.
Activity – это отдельный экран в Android. Это как окно в приложении для рабочего стола, или фрейм в программе на Java.
Манифест – это набор правил, по которым работает приложение. Файл манифеста находится в корневой папке – AndroidManifest.xml – и содержит важную информацию, без которой система не сможет запустить приложение.
SDK – это обычно набор разработчика программного обеспечения, который позволяет создавать приложения для определенно пакета, фрэймворка, игровой консоли, операционной системы или платформы.
Макет Android – это объект, который определяет визуальную структуру пользовательского интерфейса.
AVD (Android Virtual device) – специальное устройство, которое нужно для эмулятора Android, когда Вы проверяете работу своей программы.
dpi (dots per inch) – сколько точек приходится на один дюйм. Характеристика, относящаяся к качеству отображения и печати. В контексте Android означает разрешающую способность экрана устройства.
intent (намерение) – это механизм для описания одной операции – выбрать фотографию, отправить письмо, сделать звонок, запустить браузер и перейти по указанному адресу. В Android-приложениях многие операции работают через намерения. Наиболее распространенный сценарий использования намерения - запуск другой активности в своём приложении.
MIME (Multipurpose Internet Mail Extensions) – стандарт, описывающий кодирования различных типов данных, используемые при передаче файлов через электронную почту и протоколы Интернет (HTTP).
SDK (Software Development Kit) – пакет программного обеспечения для поддержки разработки ПО.
Ответы на тесты
№ вопроса | Вариант ответа | № вопроса | Вариант ответа |
Глава 1. Основы проектирования и разработки мобильных приложений | |||
1 | 2 | 9 | 3 |
2 | 3 | 10 | 2 |
3 | 1 | 11 | 1 |
4 | 1 | 12 | 3 |
5 | 1, 2, 3, 4 | 13 | 2 |
6 | 3 | 14 | 3 |
7 | 4 | 15 | 1 |
8 | 3 | 16 | 2 |
Глава 2. Многопоточность в мобильных приложениях | |||
1 | 1, 2, 3, 4, 5 | 5 | 1, 2, 3 |
2 | 3 | 6 | 1 |
3 | 1, 2, 3 | 7 | 1,3 |
4 | 1 | | |