Многооконный режим android habr

Содержание
  1. 5 советов по подготовке вашего приложения к мультиоконному режиму в Android N
  2. И вам даже не понадобятся API из Android N!
  3. Первый совет: Используйте правильный контекст
  4. Второй совет: Правильно обрабатывайте изменения конфигурации
  5. Третий совет: работайте со всеми ориентациями экрана
  6. Четвёртый совет: создавайте отзывчивый (responsive) интерфейс для всех размеров экрана
  7. Пятый совет: Activity , запущенные другими приложениями, всегда должны поддерживать работу в мультиоконном режиме
  8. Повторю ещё раз: тестируйте!
  9. Системный подход к тестированию Android-приложений, или О чем молчали разработчики
  10. Активити и фрагменты (activities and fragments)
  11. Жизненный цикл активити
  12. Первый запуск приложения
  13. Переход с первого экрана на второй
  14. Возврат со второго экрана на первый
  15. Сворачивание и выход из приложения
  16. Поворот экрана
  17. Многооконный режим
  18. Don’t keep activities (Не сохранять действия)
  19. Broadcast receiver (широковещательный приемник)
  20. Сервисы (Services)
  21. Аудиофокус

5 советов по подготовке вашего приложения к мультиоконному режиму в Android N

И вам даже не понадобятся API из Android N!

Если вы просматривали недавно видеоролик, озаглавленный «Что нового в Android N», то вы, возможно, успели уже задуматься о поддержке мультиоконности.

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

Оказывается, их не так уж и много. Несколько XML атрибутов, указывающих, собираетесь ли вы вообще поддерживать мультиоконность, да несколько методов Activity , позволяющих понять, работает ли ваше приложение сейчас в режиме мультиоконности. И где тут магия? Магия вообще-то была здесь всегда.

И под магией подразумевается система ресурсов Android’а. Одна из самых мощных её частей это способность предоставлять альтернативные ресурсы — менять размеры, макеты, drawables, меню, и прочее в зависимости от различных квалификаторов.

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

И это приводит нас к первому совету.

Первый совет: Используйте правильный контекст

Загрузка правильных ресурсов требует правильного контекста. Если для получения ресурсов, создания иерарахии виджетов по файлу макета, и прочего вы иcпользуете контекст Activity, то у вас всё хорошо.

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

Второй совет: Правильно обрабатывайте изменения конфигурации

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

По умолчанию изменение конфигурации происходит так: вся ваша Activity уничтожается, а затем создается заново, восстанавливая все те данные, что вы сохранили в onSaveInstanceState(), и перезагружая все ресурсы/макеты. При этом вы точно знаете, что все что нужно будет консистентным с новой конфигурацией, и что каждый тип конфигурации обработан.

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

Но вы так же можете обрабатывать смену конфигурации самостоятельно — в этом случае ваши активити (и фрагменты) уничтожаться не будут. У них будет вызван метод onConfigurationChanged(), в котором вам и нужно будет вручную обновить свои виджеты, перезагрузить ресурсы и так далее.

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

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

Это относится и к перезагрузке тех ресурсов, которые раньше вы считали константами. Например, у вас есть ресурс, обозначающий размер (dimension), определённый в values и в values-sw600dp. До появления мультиоконности вы не стали бы переключаться между этими двумя значениями во время работы приложения, так как наименьшая ширина никогда не менялась (она всегда была бы равной меньшему из двух измерений экрана вашего устройства). Теперь же вы можете и должны будете переключаться между этими ресурсами после изменения размеров области, в которой отрисовывается ваше приложение.

Третий совет: работайте со всеми ориентациями экрана

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

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

Читайте также:  Touyinger h5 mini android

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

Изменение ориентации устройства не должно приводить к неожиданным изменениям интерфейса. Например, приложение, воспроизводящее видео в одной из двух половиной экрана (в портретном режиме), не должно переходить к воспроизведению видео в полноэкранном режиме если устройство переходит в ландшафтный режим.

Замечание: если вам всё-таки нужно реализовать такое поведение в случае, когда ваше приложение находится в полноэкранном режиме, вы можете использовать метод inMultiWindowMode(), чтобы узнать, в каком режиме вы находитесь в данный момент.

Мультиоконность также влияет и на возможность жёстко задать ориентацию экрана атрибутом android:screenOrientation. Использование этого атрибута в приложениях, не поддерживающих Android N (у которых в качестве targetSdkVersion прописан Marshmallow или ниже) означает, что мультиоконность вами поддерживаться не будет — пользователь будет всегда выкидываться из мультиоконного режима. Если же вы решите поддержать Android N, то поведение изменится: поведение атрибута android:screenOrientation будет игнорироваться в случае, если вы переходите в мультиоконный режим.

Не забудьте также о том, что попытка зафиксировать ориентацию через setRequestedOrientation() не возымеет в мультиоконном режиме никакого эффекта, поддерживаете вы N или нет.

Четвёртый совет: создавайте отзывчивый (responsive) интерфейс для всех размеров экрана

При проектировании дизайна с учётом возможности мультиоконного режима учитывать придётся не только размер экрана. Мультиоконный режим — это первый случай, когда интерфейс, созданный вами для планшета (он у вас есть, да? Не забывайте, 12.5% от 1.4 миллиарда устройств — это много устройств. ) будет ужиматься до миниатюрных размеров.

Если вы спроектировали отзывчивый интерфейс, который не привязан к конкретному размеру экрана, и макеты которого для телефонов и планшетов относительно похожи друг на друга, то вы уже хорошо подготовились к мультиоконности. Вообще, есть хорошая рекомендация по тому, как можно подготовиться к грядущим изменениям: представьте, что по высоте/ширине вам доступно только 220dp, и рассматривайте всё остальное пространство как дополнительное, которого может и не быть. И переделывайте интерфейс с учётом этого ограничения.

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

Пятый совет: Activity , запущенные другими приложениями, всегда должны поддерживать работу в мультиоконном режиме

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

И обратное тоже верно, цитируем ту же самую страницу:

Если вы запускаете Activity в существующем стеке задачи, новая Activity замещает Activity , находившуюся на экране, наследуя при этом все её мультиоконные свойства.

Это означает, что если у вас есть Activity , которая может быть запущена другими приложениями, то она унаследует при старте мультиоконные свойства той Activity , которая её запустила. В том числе и минимальный размер. В случае startActivityForResult() ваша Activity станет частью того же самого стека задачи, и даже если она будет запущена через явный Intent , нет никакой гарантии, что она будет запущена с флагом FLAG_ACTIVITY_NEW_TASK.

Таким образом, каждая из ваших Activity , которые могут быть запущены из других приложений (а также активити, запускаемые из ваших активити) должны поддерживать мультиоконность, вплоть до самых маленьких размеров экрана. Тестируйте тщательно!

Повторю ещё раз: тестируйте!

Лучший способ приготовиться к мультиоконности — это протестировать ваше приложение. Даже если вы не станете сразу же переписывать свой код, или же устанавливать Android N SDK, а просто поставите своё приложение на эмулятор, или же устройство с Android N — вы сделаете большой шаг вперёд, к тому, чтобы сделать свои приложения лучше.

Подпишитесь на Android Development Patterns Collection, если хотите узнать больше!

Источник

Системный подход к тестированию Android-приложений, или О чем молчали разработчики

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

Как внести ясность в такой ситуации? Некуда деваться, пора разбираться, что же там происходит «под капотом» приложения.

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

Понимая, из каких компонентов состоит приложение, вы сможете:

  • Оптимальнее проектировать тест-кейсы (добавляя проверки, которые связаны с работой компонентов приложения).
  • Увереннее локализовывать баги и понимать, что конкретно нужно проверить после их исправления.
  • Обсуждать проблемы приложения с разработчиками, не плавая в непонятных терминах.

Предлагаю разобрать некоторые компоненты Андроид-системы и со стороны тестирования рассмотреть кейсы, которые надо проверять для них.

Читайте также:  Восстановление данных внутренняя память android

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

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

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

Активити и фрагменты (activities and fragments)

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

Если активити – это контейнер, то фрагменты – это UI-компоненты активити. Фрагмент тоже, в свою очередь, является контейнером для UI-компонентов.

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

Внутри окна может быть открыто несколько вкладок. Это фрагменты внутри активити.

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

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

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

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

Жизненный цикл активити

Для тех, кто не знал или знал, но забыл.

Жизненный цикл активити представлен следующими состояниями:

Теперь приведу примеры. Они покрывают основные кейсы, с которыми мы чаще всего сталкиваемся при тестировании.

Первый запуск приложения

При первом запуске приложения создается и загружается главная активити в приложении.
При переходе в состояние «Resumed» активити доступна для взаимодействия с пользователем. Все замечательно, проблем нет.

Переход с первого экрана на второй

Шаг 1: При переходе с первого экрана на второй активити первого экрана сначала встает на паузу. В этот момент могут выполняться такие действия, как сохранение введенных данных и остановка ресурсоемких операций (например, проигрывание анимаций). Это происходит довольно быстро и неощутимо для пользователя.

Шаг 2, 3, 4: Запускается активити второго экрана.

Шаг 5: Останавливается активити первого экрана. Также в случае, если Андроид в этот момент пытается освободить память, активити первого экрана дополнительно может перейти в состояние «Destroyed» (то есть уничтожиться).

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

Возврат со второго экрана на первый

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

Сворачивание и выход из приложения

При сворачивании приложения (например, по кнопке Home) активити останавливается.
Важно, чтобы при разворачивании приложения активити корректно восстановилась и данные сохранились.

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

При выходе из приложения (по аппаратной кнопке назад) помимо шага 1 и шага 2, выполняется шаг 3. Активити уничтожается и при следующем запуске приложения создается заново.

Поворот экрана

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

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

Многооконный режим

Начиная с Андроида 7 (с Андроида 6 как экспериментальная фича) стал доступен многооконный режим. Пользователи получили возможность видеть сразу несколько приложений на экране одновременно. При этом только то приложение, с которым пользователь сейчас взаимодействует, находится в состоянии «Resumed». Остальные устанавливаются в состояние «Paused».

Don’t keep activities (Не сохранять действия)

Надо ли проверять полный жизненный цикл активити, если приложение не поддерживает поворот экрана? Конечно, надо.

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

Читайте также:  Убираем рекламу с андроида

Как проверить корректность работы приложения вручную, если оно не поддерживает поворот экрана? Довольно просто: установить галку «don’t keep activities» в настройках разработчика и перезапустить приложение.

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

Это не значит, что галка должна всегда быть включенной при тестировании, но периодически смотреть приложение с опцией «don’t keep activities» полезно, чтобы пользователи со слабыми устройствами не сильно страдали и не ругали нас.

Broadcast receiver (широковещательный приемник)

Для обработки внешних событий используется компонент Broadcast receiver. Приложение может подписываться на события системы и других приложений. Андроид доставляет событие приложению-подписчику, даже если оно не запущено, и таким образом, может «мотивировать» его запуск.

При тестировании нам важно понимать, какие ожидаются события и как они обрабатываются.

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

Сервисы (Services)

Еще одна очень важная вещь в Андроиде — это сервисы. Они нужны для выполнения фоновых задач. При этом приложение не обязательно должно быть открыто пользователем в этот момент.

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

Если вы услышали волшебное слово «сервис» от разработчика, не поленитесь, выясните, какую логику работы заложили в него:

  • Что делает сервис и в каком виде проявляет себя?
  • Как ведет себя сервис, когда приложение не активно?
  • Восстанавливается ли сервис после прерывания (входящего звонка, перезапуска телефона)?

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

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

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

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

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

Раз речь зашла о музыкальных плеерах, то стоит отметить такое понятие, как аудиофокус.

Аудиофокус

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

Если ваше приложение не очень вежливое, то оно спокойно может проигнорировать запрос. Наша задача – проверять эти ситуации.

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

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

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

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

Источник

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