Этой второй текст из цикла длинных и скучных статей о тестировании приложений под Android. Здесь теория, теория, теория, вперемешку с историями «из жизни», которые, конечно, наиболее ценны (для меня).
Все статьи буду идти от очень общего к частностям. Это поможет сначала разобраться с тем, как всё работает, как устроено, как называется, а затем — жонглировать этими теоретическими знаниями. Так что если уж решили подтянуть уровень понимания Android и исследовательского тестирования приложений под Android, стоит читать всё.
К чёрту, приступим!
Службы
Хотя обычно говорят сервисы (services). Это компоненты приложений, которые работают в фоновом режиме, выполняя длительные операции и не имеют интерфейса. Играющая в фоне музыка — типичный пример работы сервиса. Google Play Store скачивает в фоне обновление приложений именно сервисом. Сервисы более живучи, чем «активити». Если пользователь включил музыку в плеере и свернул приложение плеера, то активити этого плеера, как и его сервис, будут висеть и жить в фоне. Активити можно мгновенно поднять, и это будет комфортное взаимодействие с приложением. Но если при сворачивании плеера системе понадобиться свободная оперативная память (потому что пользователь запустил некое «тяжёлое» приложение), система в первую очередь убьёт активити плеера, а сервис продолжит играть музыку. Но если памяти нужно будет ещё больше, и ещё, и ещё, система всё-таки прибьёт и сервис.
Поставщики контента
Они же контент-провайдеры (content providers). Здесь мы переходим к сущностям, о которых многие из тех, кому следует их знать, всё же имеют слабое представление. А некоторые даже не знают, что лично меня — печалит. Поставщики контента управляют некими данными и дают, или не дают, возможность управлять этими данными другим приложениями, но через себя. Например в Android есть поставщики контента для календарей, для контактов, для работы с базами данных и прочее. Если вашему приложению нужно получать данные о контактах, добавлять контакты в пользовательский список контактов, удалять их, изменять данные о них, то вам не нужно делать свою реализацию приложения «Контакты». Более того, подавляющее большинство альтернативных приложений, которые дают иной, более красивый, удобный/глючный, быстрый/тормозной доступ к пользовательским контактам, не реализуют эту задачу сами. Правильный подход — обращаться ко всем контактам через поставщика контента. Формируете запрос — получаете ответ. «Правильный» — имеется в виду для типичных приложений. Скажем, если вы делаете специальное приложение, где будут храниться контакты, доступ к которым должен быть только у этого приложения и только при подтверждении через пароль/отпечаток пальцев, то логично, что системный контент-провайдер не нужен.
«Добавить Васю с телефоном «03» и фотографией кота в системный список контактов», — говорит WhatsApp контент-провайдеру. «Есть», — говорит контент-провайдер. В итоге вы заходите в приложение «Контакты» и видите тысячу совершенно ненужных вам людей. А если вы есть в Google Plus и активно его используете через штатный клиент, то у этой тысячи людей ещё и номеров телефонов не будет. Приходится фильтровать вывод. Фильтрация показа тоже может быть реализована через контент-провайдера.
Скажем, вы желаете видеть только тех, у кого номер начинается на «+1». Запрашиваете данные (конкретно этот поставщик контента поддерживает SQL-подобные запросы, что сильно облегчает работу с ним), отсеиваете лишнее, отображаете нужное.
Приёмники широковещательных сообщений
Они же «бродкаст-ресиверы» (brodcast recivers). Бродкаст-ресиверы — это компоненты приложений, которые получают эти самые бродкасты. А дальше в приложении можно предусмотреть нужные действия — в качестве реакции. Многие широковещательные сообщения рассылает сама система, если на них подписаться. Например, система громко кричит в толпу, что выключился экран, что устройство поставили на зарядку, что пришло SMS-сообщение, что произошла смена статуса сетевого подключения, и даже что внутри текущего сетевого подключения появился Интернет.
Подобных событий достаточно много «из коробки» (если у вас установлен Android SDK, то смотрите файл «%sdk_dir%\platforms\android-%API_level%\data\broadcast_actions.txt». Сверх того, приложения также могут рассылать свои широковещательные сообщения, чтобы сообщить другим о чём-то. Например, загрузчик (приложение Downloads) может сказать, что завершил загрузку файла. Вы можете написать несколько своих приложений, которые будут действовать сообща, рассылая бродкасты и получая их. Бродкаст-ресиверы сами по себе не имеют интерфейса, но они могут создавать свои уведомления в строке состояния. Что ещё крайне важно, даже принципиально важно, регистрация бродкаст-ресивера означает, что при получении нужного бродкаста, приложение получит возможность отработать некую задачу, даже если приложение было мертво. Конечно, если на это в данный момент есть ресурсы.
Например, вашему приложению нужно отслеживать установку приложений для каких-то целей. Установили в систему новое приложение, неважно откуда — ваше приложение выполняет некое действие. Скажем, проверяет по списку известных чистых и, если его нет в списке, показывает активити, где чёрным по красному написано «Опасно! Приложение не прошло проверку». Сделать монитор можно, например, просто сервисом. Этот сервис будет работать в фоновом режиме и получать каждые 5 секунд список приложений и смотреть, нет ли нового. Но это будет такое «правильно», как и тысяча элементов интерфейса на одном активити.
Правильно — это подписаться на широковещательное сообщение об установке приложений. И тогда вашему приложению даже не нужно будет добавляться в автозапуск, совсем. Оно не будет работать никогда и, соответственно, не будет влиять на расход энергии. Но как только будет установлено приложение, система пошлёт бродкаст об этом. Этот бродкаст запустит ваше приложение, и оно выполнит свою задачу. И продолжит висеть в фоне, если вы не сказали ему отключиться (что, кстати, делать не рекомендуется, это не путь повелителей Android!), до тех пор, пока сама система его не прибьёт по необходимости.
Кстати, есть предположение, почему не стоит делать самострел? Почему вообще не стоит убивать приложения, а все чистилки памяти только вредят, когда «освобождают память» путём отстрела приложения? Потому что может возникнуть ситуация, когда нужно обработать много событий за короткий промежуток времени. Запуск приложения может быть ресурсозатратной операцией (CPU, i/o), тогда как поднятие его из фона — это мгновенная и очень дешёвая операция. Очень неприятно, когда открытие браузера вызывает перезагрузку страницы, а запуск клиента Twitter— перезагрузку ленты, которую ты читал всего несколько часов назад. И очень удобно, когда вы жмёте на иконку приложения, а оно просто поднялось из фона с полностью сохранённым состоянием. Вот это — путь повелителя Android. Кроме того, приложение может защищаться от киллеров (к примеру, защитное ПО, типа антивирусов, это делает) и перезапускаться снова и снова.
Пожалуй, самым наглядным примером регистрации бродкаст-ресивера будет посадка в маршрутку. Вы — впервые в неком районе и точно не знаете, где находится остановка «Водный Стадион». Вы заходите в маршрутку и приёмник широковещательных сообщений регистрирует : «Остановите на остановке «Водный Стадион», пожалуйста». Когда маршрутка подъезжает к нужному месту, водитель посылает широковещательное сообщение: «Остановка «Водный Стадион», кто просил?». Вы получили сообщение и выполняете необходимые действия. Если бы вас не было, и никто другой не просил бы затормозить на этой остановке, то система бы просто не посылала бродкаст. А если вы и сами знаете, где эта остановка, сами её отслеживаете и выполняете нужные действия, то это уже служба, она же сервис.
Как я говорил ранее, одна из ключевых особенностей Android заключается в том, что каждое приложение может обращаться к компонентам любого другого приложения, используя конкретную, необходимую ему сейчас точку входа. Попытка сделать фотографию из приложения чата запустит приложение фотокамеры. Для этого не то что не нужно писать свой софт под камеру, для этого даже не нужно знать, как называется любимое приложение пользователя для камеры. Вы формируете определённый запрос (намерение/Intent), система сама запустит нужную активити фотокамеры и, по завершению процедуры, вернёт пользователя обратно в приложение, откуда функция фотокамеры и вызывалась. Это будет бесшовный переход, который пользователем будет восприниматься так, будто софт камера — часть вашего собственного приложения. При этом сама операция фотографирования реально будет происходить не в вашем процессе. А раз одно приложение может использовать компоненты другого приложения, бесшовно перебрасывая пользователя между приложениями, то нет и единой точки входа. Это вам не prog.exe с ключами /q и /yes запускать. В приложениях под Android, в общем случае, нет привычной функции main().
И вновь повторю сказанное вначале. Так как запускаются разные процессы, а каждое приложение не имеет прямого доступа к данным другого приложения (каждое приложение — отдельный пользователь), то вот эти переходы и взаимные вызовы одного сквозь другое реализует сам Android. Чтобы сделать фото средствами софта фотокамеры, о которых вообще ничего не знаете, вы объявляете системе о своём намерении (создаёте объект Intent с нужными условиями) системе, а система берёт на себя задачу по обработке этого намерения. В нормальной ситуации она активирует тот компонент другого приложения, который нужен вам для реализации задачи.
Продолжение следует…