Русский
Русский
English
Статистика
Реклама

Андроид

Почти unGoogled Android

28.09.2020 12:18:44 | Автор: admin
Image by andrekheren from Pixabay.Image by andrekheren from Pixabay.

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


Желаемые (основные) цели:

  • Избежать vendor lock-а.

  • Не хранить все яйца в одной корзине.

  • Свобода внутри своего устройства.

  • Использовать приложения с открытым исходным кодом.

  • Возможностью хостить данные на своих мощностях.

  • Не убирать возможность пользоваться проприетарным ПО.

  • Иметь возможность ограничивать приложения в доступе у данным.

  • Увеличить время жизни устройства от подзарядки до подзарядки.

Цели, к которым я не стремился (но некоторые частично достигнуты за счёт предыдущих):

  • Приватность и анонимизация.

  • Блокировка слежки.

  • Шифрование везде и во всём.

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

Немного подроблей про каждую цель.

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

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

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

Использовать приложения с открытым исходным кодом, а так же с возможностью хостить данные на своих мощностях я уже давно большой поклонник решений с отрытым кодом. Осознаю, что я не буду изучать код каждого на предмет закладок, и более того, я не буду их лично компилировать, а предпочту скачивание, скажем, с F-Droid (где какой-нибудь злой гений мог бы внедрять закладки при сборке). Тем не менее, у меня на душе светлей от использования OSS-приложений, а ещё в прошлом я, бывало, пересобирал какие-то приложения с изменениями необходимыми лично мне.

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

Не убирать возможность пользоваться проприетарным ПО приятнось для души это одно, а работа и жизнь это другое. Я не Столлман, и не готов настолько радикально сменить работу, банк, страну и многое другое, чтобы была возможность пользоваться исключительно OSS-софтом. Достаточно часто приходится пользоваться проприетарным ПО.

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

Увеличить время жизни устройства от подзарядки до подзарядки без комментариев.

Анонимизация, блокировка слежки и шифрование везде и во всём по этим пунктам отвечу старым добрым комиксом https://xkcd.com/538/. Если кто-нибудь, ну вдруг, получит доступ ко всем моим данным, то будет обидно, досадно, но моя жизнь глобально не ухудшиться. А если государство захочет меня нагнуть, то ему для этого достаточно лишь собственного желания.

Выбор ОС и телефона

Будучи разработчиком под Android для меня выбор ОС был прост (хотя изначально я немного думал в сторону Sailfish OS). В качестве форка я выбирал между LineageOS и /e/.

Ранее у меня уже был положительный установки и использования CyanogenMod и LineageOS, а /e/ базируется на LineageOS. У /e/ есть некоторое преимущество в виде microG искаропки, но и у microG есть собственные билды LineageOS.

Spoiler
  • LineageOS свободный (как в понятии free as freedom так и в понятии free as beer) форк Android. От AOSP отличается готовыми сборками под большое количество устройств и некоторыми дополнительными плюшками (улучшенный рабочий стол, твики в настройках).

  • microG свободная (open source) реимплементация некоторых частей GSF (Google Services Framework). Особая ценность для пользователя в том, что microG добавляет поддержку PUSH-уведомлений, которые отправляются по механизму GCM.

  • /e/ если LineageOS представляет собой просто улучшенный AOSP без экосистемы, то /e/ предоставляет экосистему в виде /e/ account-а, email-хостинга, магазина приложений, карт, а так же предоставляет улучшенные основные приложения (т.к. я сам /e/не устанавливал, то возможны неточности).

Для помощи себе в выборе телефона, я написал небольшой скрипт, который скачивает список устройств, для которых есть LineageOS, проверяет наличие /e/ и microG для этого устройства, парсит onliner и 4pda на предмет даты выхода устройства и максимальной доступной версии Android под неё у производителя и затем выводит список, отсортированный по привлекательности устройства (код не ахти какой, по этому вместо нормального репозитария выложил в gist).

Spoiler
  • хотя microG теоретически может устанавливаться на любой форк Android-а, на LineageOS её сходу установить нельзя, т.к. мейнтейнеры LineageOS не хотят добавлять в апстрим небольшой патч, который необходим для корректной работы microG. Того патча, там, буквально несколько строчек, но придётся пересобрать LineageOS самостоятельно. К счастью, мейнтейнеры microG предоставляют свои сборки LineageOS уже с патчем, с самим microG а так же с предустановленным F-Droid.

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

Взяв десяток устройств из верхушки списка, я добавил их в сравнение на онлайнере (не сочтите за рекламу, как мне кажется там действительно удобное сравнение), и выбрал наиболее подходящий для меня вариант. Несмотря на предубеждение у телефонам из поднебесной, это оказался Xiaomi Mi 8.

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

Для этого телефона доступно как LineageOS for microG так и /e/, но я отдал предпочтение более чистому Android-у (LineageOS for microG).

Установка

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

Но чтоб не оставлять раздел совсем пустым, вот что вспомнилось про установку для моего телефона:

  1. Разблокировать загрузчик по инструкции с 4pda.

  2. Обновить официальную прошивку (MIUI) до версии Android, соответствующей той, которую будете прошивать (осторожно, версии самого MIUI и Android на которм он базируется не совпадают). Кажется что виртуалка с Windows не понадобилась, но точно не помню.

  3. Установить TWRP по инструкции с 4pda (там под Windows, но bat-ники очень простые, и можно всё сделать в любой OS).

  4. Устанавливаем LineageOS по инструкции с 4dpa, но только без OpenGApps. Хоть название намекает на открытость, но открыт там лишь код, которые устанавливает GSF, а сами GSF как были проприетарными и под контролем у Google, так и остаются.

  5. Устанавливаем Magisk, и перезагружаем устройство.

Spoiler
  • MIUI форк Android-а использующийся с телефонах Xiaomi (а так же в некоторых других благодаря кастомным прошивкам). Отличается красивым внешним видом и обилием рекламы прямо в пришивке (sic).

  • TWRP Team Win Recovery Project, кастомное рекавери для андроид, с широкими возможностями и управлением тачем (в отличие от некоторых других, где управление производится кнопками громкости).

  • Magisk systemless root для Android (не требуется модификации никаких файлов в разделе system). Так же Magisk предоставляет API для плагинов, которые реализуют и другие (кроме root) вещи.

Сразу после перепрошивки и перезагрузки, нужно установить несколько важных приложений. Большинство из них находится в основном репозитории F-Droid, но некоторые в дополнительных (список других популярных репозиториев можно взять из приложение Aurora Droid).

Magisk Manager

Если он не был установлен сразу, нужно установить. Далее через него нужно включить Magisk Hide а так же переименовать имя пакета и название launcher-а это позволит более чем успешно скрывать наличие рута на устройстве. Только учтите, что Magisk Hide не применяется автоматически, и его нужно включать индивидуально для нужных приложений.

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

Spoiler

Google SafetyNet механизм в GSF, который позволяет сторонним приложениям узнавать, насколько безопасно (по мнению Google) устройство, на котором работает приложение.

Есть два вида проверок basicIntegrity и ctsProfileMatch. Первая из них не очень строгая, и отвечает на вопрос не поддельное ли (что бы это ни значило) устройство. Вторая более строгая, и успешно пройти её могут только устройства, которые прошли Android compatibility testing.

Чтобы пройти ctsProfileMatch на кастомной прошивке, нужно делать очень много странных вещей, и то, нет никаких гарантий.

Проверку basicIntegrity должно быть пройти проще (если верить многочисленным статьям), но в моём случае не проходит даже она :(

Из модулей я поставил Riru (Riru - Core) и Riru - EdXposed (сам по себе Xposed не работает на новых версиях Android, но это обходной путь через Magisk). Другие модули мне не пригодились, хотя выбор очень большой.

Spoiler

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

Для XPosed написаны тысячи (если не десятки или даже сотни тысяч) модулей, позволяющих кастомизировать Android до мельчайших деталей.

Через EdXposed я установил App Locale 2 для принудительной смены языка в приложениях (я предпочитаю интерфейс телефона и большинства приложений на английском, но некоторые приложения хочу видеть на русском. К сожалению, не все предоставляют такую возможность в настройках (например, Яндекс Деньги), так что приходится действоватб обходным путём.

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

FoxyDroid, Aurora Droid

Сразу в системе будет установлен F-Droid (при условии установки LineageOS for microG). Мне приложение не кажется ни особо быстрым, ни особо удобным, по этому я дополнительно устанавливаю FoxyDroid и Aurora Droid.

FoxyDroid быстрый и минималистичный магазин приложений. Aurora Droid очень красив, но местами глючноват. Из хорошего там сразу есть список дополнительных репозиториев с OSS-программами. Я некоторые из них переношу в FoxyDroid (Bromite, IzzyOnDroid, microG), а сам Aurora Droid потом удаляю (но ни в коем случае не призываю делать точно так же).

Aurora Store

Полностью отказаться от магазина Google Play я пока не готов, по этому использую Aurora Store для скачки приложений оттуда. Там можно войти под своим аккаунтом, и получить доступ к купленным приложениям (внутри самого Aurora Store покупать нельзя, так что покупайте через Web либо с другого устройства). Осознаю что за использование своего аккаунта вне самого Google Play меня могут безвозбранно забанить, но я иду на этот риск.

Apple UnifiedNpl Backend, Deja Vu Location Service

Сам по себе microG не умеет определять координаты без GPS, но делегирует эту задачу сторонним приложениям через механизм UnifiedNlp. Сразу в прошивке уже есть Mozilla UnifiedNpl Backend, но по крайней мере в моём городе он не особо точен, по этому я устанавливаю дополнительные.

Apple Unified Nlp Backend может показаться что это шило на мыло (зачем избавляться от слежки Google, если будет слежка от Apple), но как я писал вначале статьи, цели приватности я не преследовал, а OSS-приложений с названием похожим на Google Unified Nlp Backend я не нашёл.

Deja Vu Location Service приложение ведёт локальную базу местоположений и их соответствия WiFi-точкам и мобильным вышкам. Через некоторое время использования, позиция без GPS будет определяться в тех местах, где вы часто бываете.

Flite TTS Engine

Синтезатор речи. Умеет только английский язык. Сразу после установки нужно скачать необходимые голосовые данные и настройть их. Хочу предупредить, что иногда кажется, что настройки не применяются. Тогда нужно смахнуть приложение, и открыть заново.

DAVx5

Это приложение служит для того, чтобы была возможность синхронизации контактов и календарей по протоколам CardDAV и CalDAV. Для календарей (и почты) я пользуюсь Fastmail, и там всё достаточно тривиально. Контакты же мои всё ещё в Google, и настроить DAVx5 для синхронизации контактов оттуда немного замороченно.

Адрес будет https://www.googleapis.com/carddav/v1/principals/<ваша почта>/lists/default/, а вот с паролем хитрее. Т.к. DAVx5 не умеет гугловскую авторизацию, а гугл больше не поддерживает вход по логину и паролю, придётся сделать специальный пароль для приложений.

В процессе создания пароля придётся включить 2-step verification, и для этого пригодится следующая программа.

Authenticator Pro

Это приложения для герерации кодов для двухфакторной авторизации. Альтернативно можно установить Google Authenticator, но тот который в F-Droid уже устарел (хотя и работает), а новый уже не open source.

Немного других приложений

Остальные приложения вы смело можете заменять на свой вкус, но мне нравятся именно эти:

  • QKSMS отличная программа для СМС.

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

  • Binary Eye сканнер штрих- и бар-кодов.

  • Fennec F-Droid мой основной браузер.

  • Bromite если для просмотра какого-нибудь сайта нужен браузет на основе Chroimum. К слову, команда Bromite так же предоставляет билды чистого Chromium-а.

  • LibreOffice Viewer просмотрщий документов.

  • Librera PRO читалка книг (дизайн немного инопланетный, но программа очень функциональная).

  • MuPDF viewer минималистичный просмотрщик pdf. В F-Droid есть несколько вариантой MuPDF, но именно этот более-менее работает на последних версиях Android, и в котором есть хоть какие-то приятные анимации.

  • NewPipe, SkyTube замена приложению YouTube.

  • OsmAnd на мой вкус самые функциональные offline-карты на базе open street maps.

  • Telegram FOSS просто чтоб вы знали, что есть отдельная OSS-сборка. Там есть нюанс с настройкой уведомлений (в OSS-версии не используются уведомления через GCM), и постоянно будет висеть нотификашка, но по тапу на неё в самом телеграме будут инструкции как её скрыть.

  • Tasks я всё ещё не перешёл на него (пользуюсь проприетарным Todoist), но выглядит многобещающе, и умеет синхронизироваться с CalDAV-сервером (с Fastmail работает).

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

Это далеко не весь список моих приложений установленных из F-Droid и других свободных репозиториев, но остальные мне показались очень спецефичными, по этому я не стал их упоминать, как и приложения установленные из Google Play (тут у каждого будет свой набор любимых приложений).

Единственное, чего я не нашёл в Open Source, так это достойного почтового клиента, который умеет много email-аккаунтов, поддерживает Google-аутентификацию, не перенаправляет все письма на какие-то странные сервера, умеет unified inbox и прилично выглядит. K9-Mail мне неудобен, FairEmail выглядит интересно, но я пока не готов врываться в настройку (кроме того в OSS-версии недоступно много функций). По этому я продолжаю пользоваться проприетарным и платным приложением AquaMail (не сочтите за рекламу), но с радостью приму советы по смене приложения.

Итоги

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

  • Избежать vendor lock-а и не хранить все яйца в одной корзине в какой-то мере удалось. Телефон теперь принадлежит мне (если не рассматривать возможные закладки в драйверах), а данные разнесены по разным провайдерам.

  • Свобода внутри своего устройства да, есть полноценный рут, мощное рекавери и много модулей для Magick и XPosed, что позволяет исследовать устройство, и изменять его поведение.

  • Использовать приложения с открытым исходным кодом частично удалось. Многие OSS-приложения ничуть не хуже, или даже лучше своих проприетарных аналогов (для моих сценариев использования), но для некоторых проприетарных приложений не удалось найти адекватную замену (Evernote, Pocket), а некоторые никогда не будут в OSS (приложения для банкинга). Ещё местами есть проблема, когда приложение open source, но билд, который есть в F-Droid либо устарел по сравнению с билдом в Google Play, либо не работает (привет, Rocket.Chat).

  • Возможностью хостить данные на своих мощностях частично удалось, и потенциально можно перенести ещё больше информации к себе. Почта, календари, контакты, задачи всё это можно хостить либо вообще на своём сервере, либо выбрать провайдера себе по вкусу. Так же можно огранизовать свои сервера для некоторых чатов и мессенджеров. Но из-за специфики рабочих процессов что-то остаётся проприетарным (скажем, не получается перейти с Google Docs на OwnCloud или NextCloud).

  • Не убирать возможность пользоваться проприетарным ПО всё хорошо, можно без проблем устанавливать приложения из Google Play, в том числе и когда-либо купленные. Но вот что касается возможности покупать приложения на самом устройстве, либо пользоваться in-app покупками, то тут не всё так однозначно. Чтобы покупать, нужно иметь какое-либо другое устройство с установленным GSF тогда можно будет покупать либо с этого устройства, либо через Web. С in-app-ами хуже, т.к. если статус покупки / подписки не хранится на сервере, то при оплате in-app-а на дополнительном устройстве, он не появится на вашем основном. Хотя с приложениями, которыми я пользуюсь (Todoist, Evernote, Яндекс.Музыка) всё хорошо.

  • Иметь возможность ограничивать приложения в доступе у данным да, это возможно через модули к XPosed.

  • Увеличить время жизни устройства от подзарядки до подзарядки это получилось, и да, это случилось после замены GSF на microG. Читал статьи, где авторы утверждают что GSF сами по себе отжирают совсем немного батареи, а вся вина на приложениях, которые часто получают пуши и / или просыпаются в фоне. Так вот, приложений у меня установлено очень много, большинство из них пользуются пушами через GCM, а некоторые и вовсе держат своё соединение. И несмотря на то, что я достаточно активно пользуюсь телефоном, заряжаю я его раз в несколько дней. Если хочется большего, то в настройках microG можно увеличить интервал проверки пушей, тогда батарея будет тратиться ещё меньше (но ценой того, что пуш-нотификация может прийти со значительно задержкой).

Подробнее..

Обновляемся на новую версию API Android по наставлению Google

27.05.2021 20:23:24 | Автор: admin

Скоро выходит Android 12, но в этом августе уже с 11-й версии разработчикам придётся использовать новые стандарты доступа приложений к внешним файлам. Если раньше можно было просто поставить флаг, что ваше приложение не поддерживает нововведения, то скоро они станут обязательными для всех. Главный фокус повышение безопасности.

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

Что происходит

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

В Android есть внутреннее Internal Storage (IS) и внешнее хранилище External Storage (ES). Исторически это были встроенная память в телефоне и внешняя SD-карта, поэтому ES был больше, но медленнее и дешевле. Отсюда и разделение настройки и критически важное записывали в IS, а в ES хранили данные и большие файлы, например, медиа. Потом ES тоже стал встраиваться в телефон, но разделение, по крайней мере логическое, осталось.

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

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

В Android решили всё это переделать ещё в 10-й версии, а в 11-й это стало обязательным.

Чтобы минимизировать риски для пользователя в Google решили внедрить Scoped Storage (SS) в ES. Возможность проникнуть в папки других приложений убрали, а доступ есть только к своим данным теперь это сугубо личная папка. А IS с 10-й версии ещё и зашифрована по умолчанию.

В Android 11 Google зафорсировала использование SS когда таргет-версия SDK повышается до 30-й версии API, то нужно использовать SS, иначе будут ошибки, связанные с доступом к файлам. Фишка Android в том, что можно заявить совместимость с определённой версией ОС. Те, кто не переходили на 11, просто говорили, что пока не совместимы с этой версий, но теперь нужно начать поддерживать нововведения всем. С осени не получится заливать апдейты, если не поддерживаешь Android 11, а с августа нельзя будет заливать новые приложения.

Если SS не поддерживается (обычно это для девайсов ниже 10-й версии), то для доступа к данным других приложений требуется получить доступ к чтению и записи в память. Иначе придётся получать доступ к файлам через Media Content, Storage Access Framework или новый, появившийся в 11-м Android, фреймворк Datasets в зависимости от типа данных. Здесь тоже придётся получать разрешение доступа к файлу, но по более интересной схеме. Когда расшариваемый файл создаёшь сам, то доступ к нему не нужен. Но если переустановить приложение доступ к нему опять потребуется. К каждому файлу система привязывает приложение, поэтому когда запрашиваешь доступ, его может не оказаться. Особо беспокоиться не нужно, это сложно отследить, поэтому лучше просто сразу запрашивать пермишен.

Media Content, SAF и Datasets относятся к Shared Storage (ShS). При удалении приложения расшаренные данные не удаляются. Это полезно, если не хочется потерять нужный контент.

Хотя даже при наличии SS можно дать доступ к своим файлам по определённой технологии через FileProvider можно указать возможность получения доступа к своим файлам из другого приложения. Это нормально, потому что файлы расшаривает сам разработчик.

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

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

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

Перейдём к практике.

Переход на новую версию

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

Для этого выделили в общий интерфейс работу с файлами, реализация которого зависела от версии API.

interface FilesManipulator {    fun createVideoFile(fileName: String, copy: Copier): Uri    fun createImageFile(fileName: String, copy: Copier): Uri    fun createFile(fileName: String, copy: Copier): Uri    fun getPath(uri: Uri): String    fun deleteFile(uri: Uri)}

FilesManipulator представляет собой интерфейс, который знает, как работать с файлами и предоставляет разработчику API для записи информации в файл. Copier это интерфейс, который разработчик должен реализовать, и в который передаётся поток вывода. Грубо говоря, мы не заботимся о том, как создаются файлы, мы работаем только с потоком вывода. Под капотом до 10-й версии Android в FilesManipulator происходит работа с File API, после 10-й (и включая её) MediaStore API.

Рассмотрим на примере сохранения картинки.

fun getContentValuesForImageCreating(fileName: String): ContentValues {    return ContentValues().apply {        put(MediaStore.Images.Media.DISPLAY_NAME, fileName)        put(MediaStore.Images.Media.IS_PENDING, FILE_WRITING_IN_PENDING)        put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + appFolderName)    }}fun createImageFile(fileName: String, copy: Copier): Uri {    val contentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)    val contentValues = getContentValuesForImageCreating(fileName)    val uri = contentResolver.insert(contentUri, contentValues)         ?: throw IllegalStateException("New image file insert error")    downloadContent(uri, copy)    return uri}fun downloadContent(uri: Uri, copy: Copier) {    try {        contentResolver.openFileDescriptor(uri, FILE_WRITE_MODE)                .use { pfd ->                    if (pfd == null) {                        throw IllegalStateException("Got nullable file descriptor")                    }                    copy.copyTo(FileOutputStream(pfd.fileDescriptor))                }        contentResolver.update(uri, getWriteDoneContentValues(), null, null)    } catch (e: Throwable) {        deleteFile(uri)        throw e    }}fun getWriteDoneContentValues(): ContentValues {    return ContentValues().apply {        put(MediaStore.Images.Media.IS_PENDING, FILE_WRITING_DONE)    }}

Так как операция сохранения медиафайлов достаточно длительная, то целесообразно использовать MediaStore.Images.Media.IS_PENDING, которая при установлении значения 0 не дает видеть файл приложениям, отличного от текущего.

По сути, вся работа с файлами реализована через эти классы. Шаринг в другие приложения автоматически сохраняют медиа в память устройства и последующая работа с URI уже происходит по новому пути. Но есть такие SDK, которые ещё не успели перестроиться под новые реалии и до сих пор используют File API для проверки медиа. В этом случае используем кеш из External Storage и при необходимости провайдим доступ к файлу через FileProvider API.

Помимо ограничений с памятью в приложениях, таргетированных на 30-ю версию API, появилось ограничение на видимость приложения. Так как iFunny использует шаринг во множество приложений, то данная функциональность была сломана полностью. К счастью, достаточно добавить в манифест query, открывающую область видимости к приложению, и можно будет также полноценно использовать SDK.

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

<manifest  ><queries><intent>    <action android:name="android.intent.action.SENDTO" />    <data android:scheme="smsto, mailto" /></intent>    <package android:name="com.twitter.android" />    <package android:name="com.snapchat.android" />    <package android:name="com.whatsapp" />    <package android:name="com.facebook.katana" />    <package android:name="com.instagram.android" />    <package android:name="com.facebook.orca" />    <package android:name="com.discord" />    <package android:name="com.linkedin.android" /></queries></manifest>

После проверок запуска UI-тестов на девайсах с версиями API 29-30 было выявлено, что они также перестали корректно отрабатываться.

Первоначально в LogCat обнаружил, что приложение не может приконнектиться к процессу Orchestrator и выдает ошибку java.lang.RuntimeException: Cannot connect to androidx.test.orchestrator.OrchestratorService.

Эта проблема из разряда видимости других приложений, поэтому достаточно было добавить строку <package android:name="androidx.test.orchestrator" /> .

Тест удачно запустился, но возникла другая ошибка Allure не может сохранить отчёт в память устройства, падает с ошибкой.

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

Так как нам нужно использовать этот пермишен только для тестов, то нам условия подходят. Поэтому я быстренько написал свой ShellCommandExecutor, который выполняет команду adb shell appops set --uid PACKAGE_NAME MANAGE_EXTERNAL_STORAGE allow на создании раннера тестов.

На Android 11 тесты удачно запустились и стали проходить без ошибок.

После попытки запуска на 10-й версии Android обнаружил, что отчет Allure также перестал сохраняться в память девайса. Посмотрев issue Allure, обнаружил, что проблема известная, как и с 11-й версией. Достаточно выполнить команду adb shell appops set --uid PACKAGE_NAME LEGACY_STORAGE allow. Сказано, сделано.

Запустил тесты всё еще не происходит сохранения в память отчёта. Тогда я обнаружил, что в манифесте WRITE_EXTERNAL_STORAGE ограничен верхней планкой до 28 версии API, то есть запрашивая работу памятью мы не предоставили все разрешения. После изменения верхней планки (конечно, для варианта debug) и запроса пермишена на запись тесты удачно запустились и отчёт Allure сохранился в память устройства.

Добавлены следующие определения пермишенов для debug-сборки.

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /><uses-permission    android:name="android.permission.WRITE_EXTERNAL_STORAGE"    android:maxSdkVersion="29"    tools:node="replace" />

После всех вышеописанных манипуляций с приложением, можно спокойно устанавливать targetSdkVersion 30, загружать в Google Play и не беспокоиться про дедлайн, после которого загружать приложения версией ниже станет невозможно.

Подробнее..

Перевод 20 инструментов Android-разработчика, о которых вы могли не знать

14.09.2020 18:23:30 | Автор: admin

Набор полезных, но не очень известных инструментов и библиотек Android.

Работая над статьями о 30 лучших библиотеках и проектах Android 2019 г. и 25 лучших библиотеках и проектах Android 2020 г., я наткнулся на множество замечательных инструментов и проектов, которые могут пригодиться в разработке приложений для Android ниже они приведены в случайном порядке. Пользуйтесь.

1. AinD Android (Anbox) в Докере

AinD запускает приложения Android, помещая контейнеры Anbox в Докер.

В отличие от аналогичных проектов на основе виртуальных машин, AinD может выполняться на экземплярах IaaS без поддержки вложенной виртуализации. Docker Hub: aind/aind.

Предназначение:

2. Booster

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

Booster это набор модулей для оценки производительности, оптимизации многопоточности, встроенного индекса ресурсов, сокращения числа избыточных ресурсов, сжатия ресурсов, исправления системных ошибок и т. д. Booster позволяет повысить стабильность приложения на 1525% и снизить размер пакета на 110 МБ.

Документация очень хорошая, лицензия Apache 2.0.

3. Shake

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

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

Устанавливается эта штука просто, в документации всё отлично описано. Кроме того, есть интерактивная демонстрация, которая показывает работу инструмента изнутри.

4. Scabbard

Scabbard помогает с визуализацией и анализом графика зависимостей Dagger 2.

Scabbard визуализирует точки входа, схемы зависимостей, взаимосвязи компонентов и области действия. Добавить этот инструмент в проект очень легко: он хорошо интегрирован с Gradle, а также с Android Studio и IntelliJ (нажав значок на левом поле в редакторе, можно просмотреть схему для @Component или @Subcomponent).

Документирован проект отлично: есть множество примеров и подсказок.

Лицензия Apache 2.0.

5. Can I Drop Jetifier?

Как известно, иногда перейти со старой библиотеки поддержки на AndroidX не так просто, особенно в крупных проектах с большим количеством устаревшего кода. Успех перехода во многом зависит от использования инструмента Jetifier (преобразует зависимости, которым для работы с классами AndroidX всё еще требуются старые артефакты), который замедляет сборку.

Всё больше и больше библиотек переходят на AndroidX, поэтому в какой-то момент необходимость включать этот инструмент отпадает. Этот плагин определяет, какие из используемых библиотек нужно перенести на AndroidX или избавиться от них, если уже вышла новая версия, Can I Drop Jetifier?

Документация понятная, проект выпущен под лицензией Apache 2.0. Очень рекомендую!

6. ADB Event Mirror

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

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

Инструмент дает возможность протестировать приложение одновременно на эмуляторах различных типов.

7. Android Emulator Container Scripts

Android Emulator Container Scripts набор небольших сценариев для запуска эмулятора в контейнере для различных систем (например, для Докера) с целью внешнего использования. Сценарии совместимы с Python версий 2 и 3. Этот репозиторий довольно популярен и пригодится, если нужно запускать много эмуляторов на удаленных машинах.

Проект выпущен под лицензией Apache 2.0 и хорошо документирован.

8. Autoplay

Autoplay это плагин для Gradle, предназначенный для публикации артефактов Android в Google Play.

Его можно считать очень простой альтернативой Gradle Play Publisher или Fastlane. Опубликовать приложение можно как apk или набор App Bundle.

Особенности Autoplay:

  • Оптимизирован для использования в CI/CD.

  • Удобен для разработчиков.

  • Надежен и перспективен.

У проекта хорошая документация, версия на момент написания статьи 1.3.0, лицензия Apache 2.0.

9. Плагин Gradle для статического анализа

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

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

10. AndroidUtilCode

AndroidUtilCode функциональная и простая в использовании библиотека для Android, которая инкапсулирует функции, обычно используемые при разработке Android с демонстрационными версиями и модульными тестами. Инкапсулированные API позволяют значительно повысить эффективность разработки.

Проект состоит в основном из двух модулей: utilcode (используется в разработке часто) и subutil (используется редко, но позволяет упростить основной модуль).

Версия проекта 1.29.0, лицензия Apache 2.0.

11. Hijckr

Hijckr вмешивается в инфляцию макета Android и перенаправляет названные элементы в другие классы.

Это довольно интересный инструмент. Например, если файл макета содержит TextView, Android обычно загружает android.widget.TextView, но вместо этого можно перехватить xml-теги и загрузить com.myapp.TextView.

Описание проекта довольно подробное и позволяет быстро начать работу с инструментом (который полностью написан на Java).

12. Roomigrant

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

Проект выпущен под лицензией MIT, версия 0.1.7.

13. RoomExplorer

После переноса базы данных на Room неплохо бы просмотреть ее: RoomExplorer позволяет просматривать все данные таблиц в табличном формате, удалять таблицы, вставлять, изменять и удалять строки и т. д.

Инструмент хорошо документирован, лицензия Apache 2.0.

14. Android Framer

Инструмент android-framer добавляет рамки и заголовки к скриншотам в Google Play. Источник вдохновения fastlane frameit.

Инструмент написан на Python и использует ImageMagick. Настроить рамки (фоны) можно, например, с помощью Facebook Design. Также можно менять шрифт, кегль, размер рамки и т. д.

Лицензия Apache 2.0.

15. Dependency Tree Diff

Dependency Tree Diff это интеллектуальный инструмент сравнения для вывода задачи dependencies Gradle, который всегда показывает путь к корневой зависимости.

Можно установить инструмент через brew или просто использовать jar-файл.

Лицензия Apache 2.0.

16. Gradle Doctor

Gradle Doctor это плагин для сканирования сборки Gradle. Функциональность: настраиваемые предупреждения о проблемах со скоростью сборки, измерение временных затрат на инструменты обработки аннотаций Dagger, установка переменной JAVA_HOME и проверка ее соответствия JAVA_HOME в IDE, простое отключение кеширования тестов, остановка сборки в случае, если найдены пустые каталоги src (поскольку это может быть причиной несовпадений в кеше), и многое другое.

У инструмента отличная документация, проект выпущен под лицензией Apache 2.0.

17. GloballyDynamic

GloballyDynamic это набор инструментов, направленных на обеспечение всеобщей доступности Dynamic Delivery, независимо от магазина приложений или платформы распространения, которые также предоставляют единый унифицированный клиентский API для Android и простой интерфейс для разработчиков.

Поддерживаются:

Рекомендую прочитать README и подробнее ознакомиться с этим инструментом.

Лицензия Apache 2.0.

18. Dagger Browser

Dagger Browser еще один инструмент (прогрессивное веб-приложение) для удобной навигации по схеме Dagger в проекте.

Данные схемы заполняются с помощью SPI-плагина Dagger, а средство просмотра написано с помощью CRA (create-react-app) и TypeScript, Dagger Browser

Есть и пример приложения, в котором показано, как инструмент работает. Всё это выпущено под трехпунктовой лицензией BSD.

19. Wormhole

Wormhole путешествующий во времени инструмент преобразования байт-кода, добавляющий в android.jar будущие API-интерфейсы, которые можно десахаризовать на все уровни API с помощью D8 и R8.

Wormhole обеспечивает обратную совместимость с более новыми API. Приведу пример.

В Android R есть новые методы из Java 9 например, List.of. Благодаря D8 и R8 они не являются эксклюзивными для API 30 и мгновенно превращаются в совместимые с API 1. В D8 и R8 есть набор методов десахаризации для API, которых еще нет в android.jar. И можно не ждать, пока они появятся этот проект дает возможность использовать их сразу же.

20. MNML

MNML (произносится как minimal минимальный) простое бесплатное приложение для записи экрана в Android.

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

Лицензия Apache 2.0.

Заключение

Вот и всё. Надеюсь, список вам понравился и какие-то инструменты смогли вас вдохновить. До встречи!

О переводчике

Перевод статьи выполнен в Alconost.

Alconost занимается локализацией игр, приложений и сайтов на 70 языков. Переводчики-носители языка, лингвистическое тестирование, облачная платформа с API, непрерывная локализация, менеджеры проектов 24/7, любые форматы строковых ресурсов.

Мы также делаем рекламные и обучающие видеоролики для сайтов, продающие, имиджевые, рекламные, обучающие, тизеры, эксплейнеры, трейлеры для Google Play и App Store.

Подробнее..

Из песочницы Избегайте внедрения внешних библиотек в свой проект

14.10.2020 12:12:47 | Автор: admin
Часто можно услышать фразу: Зачем писать свой велосипед? Возьми готовую либу и пользуйся! За тебя уже все написали. Особенно часто подобные выражения слышат начинающие разработчики. При решении любой задачи они начинают смотреть готовые либы и бездумно тянуть их в свой проект. В этой статье Вы узнаете к каким последствиям может привести бездумное внедрение сторонних библиотек.

Потенциальные проблемы


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

Размер приложения


Большинство библиотек не сильно увеличивают размер Вашего приложения. Но существуют такие либы, после добавления которых Ваше приложение увеличится в разы! Например, библиотека Realm может увеличить размер APK с 4MB до 12MB. В среднем приложение весит 100MB-200MB и лишние 8MB могут быть не заметны. Но не забывайте, что Realm не единственная библиотека, которая негативно влияет на размер APK. А можно ли уменьшить размер APK, используя эту зависимость? Да, можно сделать split apk по архитектурам процессора. Но это приводит к следующей проблеме (пункт 2).

Поддерживаемость кода


Проекты разрастаются, покрываются все большей логикой и разобраться порой в них становится все сложнее. Чтобы спустя нескольких лет развития проекта любой новый разработчик мог легко освоиться в проекте нужна четкая архитектура проекта. Однако некоторые библиотеки могут свести поддерживаемость проекта к нулю. Например, некорректная работа с EventBus может сильно запутать логику Вашего приложения. Тут важно четко разграничить: где и в каких кейсах Вы используете эту либу. А также быть уверенным, что никто и никогда не будет отклоняться от этих правил. Но что происходит на практике? Почти каждый разработчик, начиная работать с EventBus, использует его повсюду. В итоге проект становится абсолютно нечитаемым.

Время на изучение библиотеки


При добавлении новой библиотеки необходимо изучить как с ней взаимодействовать. Существуют библиотеки, которые могут очень негативно повлиять на скорость разработки в будущем.
Например, PagingLibrary от Google требует немалых усилий, чтобы понять как с ней взаимодействовать. Чтобы разобраться с этой библиотекой каждому новому разработчику потребуется от 12 до 20 часов. Вы теряете почти 3 дня! Как раз за эти 3 дня Вы можете запилить свою пагинацию и быть независимым от сторонних решений.

Скорость сборки


Скорость сборки современных приложений оставляет желать лучшего. Однако, если Вы хотите ещё больше увеличить время сборки используйте Dagger! Не знаю почему библиотекой до сих пор активно пользуются после появления Kotlin. В большинстве случаев Dagger содержит все 4 проблемы, которые были описаны выше.

Баги, баги, баги


На своем опыте в одном лишь проекте я выпиливал 5 библиотек из-за наличия в них багов. Помните, что в библиотеках почти всегда есть баги. Например:

  • AndroidPdfViewer оставляет утечки памяти, некорректно обрабатывает некоторые кейсы с null, из-за чего вылетает NullPointerException
  • Android Navigation Component некорректно работает с анимациями Shared Elemant'ов в некоторых кейсах
  • Cicerone иногда крашит приложение из-за executePendingTransactions()

Значит ли это, что библиотеки не стоит использовать? Нет, библиотеки использовать можно и нужно, но важно как минимум убедиться, что в списке issue нет критических багов для Вашего проекта.

Наличие уязвимостей в библиотеке


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

Поддержка библиотеки


Если библиотека не обновлялась уже год задайте вопрос, стоит ли её использовать. Дело в том, что в библиотеках регулярно находятся баги. Если эти баги не исправляются, то стоит ли использовать эту библиотеку? Вполне возможно, что в ближайшем будущем вы наткнетесь на баг библиотеки и вам придется искать альтернативные пути реализации фичи. Есть некоторые библиотеки, которые должны адаптироваться под возможности текущей версии Android API. Например, если появился новый элемент в Android, то нужно добавить его поддержку. К таким библиотекам можно отнести Anko, который больше не поддерживается. Теперь использовать эту библиотеку в крупных проектах нет смысла.

Библиотека присутствует во всех слоях проекта


Такие библиотеки как RxJava или PagingLibrary заставляют разработчика использовать их API на каждом слое приложения. Если вы уверены, что библиотека всегда будет в проекте, то проблем нет. Но если вам по каким-то причинам придется выпиливать библиотеку, то вы затратите колоссальные усилия! Вам придется переписывать полпроекта.

Ограничения библиотеки


Каждая либа предоставляет API, который ограничен как наличием открытыми методами, так и внутренней реализацией. Убедитесь, что возможностей библиотеки вам полностью хватает. Например, популярная библиотека Android Navigation Component очень сильно связывает руки разработчика. Она не предоставляет методы show, hide, add (которые есть в FragmentTransaction). Помимо этого библиотека усложняет работу с BottomNavigationView при необходимости хранить историю табов.

Огромный Gradle файл с зависимостями


Когда я прихожу на новый проект, я первым делом смотрю зависимости в Gradle файл. Он даёт понять, что умеет делать приложение и как решаются те или иные задачи. Но вот было моё удивление, когда увидел, что для работы с сетью используются и OkHttp, и Retrofit, и Volley (форк). И это только работа с сетью. Сам Gradle файл состоит из огромного числа библиотек, поддержка которых уже давно закончилась. Когда разработчик один, он может держать весь проект в голове, но когда приходят новые разработчики разобраться в таком проекте становится крайне сложно.

Чек-лист вопросов перед внедрением библиотеки


  1. Какие issue есть у данной библиотеки? Критичны ли они для моего проекта?
  2. На сколько эта библиотека/технология протестирована сообществом разработчиков? Сколько у нее звездочек на GitHub?
  3. Как часто разработчик отвечает на issue?
  4. Как часто разработчик обновляет библиотеку?
  5. Сколько времени потратят новые разработчики на изучение используемой технологии?
  6. Как библиотека повлияет на размеры приложения?
  7. Как библиотека повлияет скорость работы приложения?
  8. Как библиотека повлияет на скорость сборки? Помогает ли она сэкономить время разработки?
  9. Есть ли у библиотеки уязвимости?
  10. Будет ли библиотека присутствовать на каждом слое проекта? На сколько это критично?
  11. Каким образом библиотека ограничивает возможности разработчика (она почти всегда ограничивает). Хорошо это или плохо?
  12. Смогу ли я сам написать решение, которое будет заточено под мой проект за допустимые сроки?

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

Как написать простое Android ToDo-приложение на Java

15.03.2021 22:22:10 | Автор: admin

Предисловие

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

Я расскажу вам как написать простенькое ToDo-приложение на Android с тремя активностями (рабочими экранами).

Ссылка на проект на Github будет в конце данной статьи.

Установка и первичная настройка

Для разработки приложения я рассмотрю использование бесплатной IDEIntellij от разработчиков JetBrains - Android Studio, у меня версия 4.1.1.

После успешной установки IDE и запуска нажимаем на самую первую кнопкуStart a new Android Studio Project. Далее появится мастер первичной подготовки проекта:

  • выберем подходящий шаблон, в моем случае это Empty Activity - он самый простой для новичков, так как при первом запуске будет всего 1 XML файл с версткой и один java файл MainActivity.

  • На следующем экране придумываем имя приложению; помните, что package name, после публикации на Google Play изменить нельзя (иначе Google Play посчитает это другим приложением (поправьте меня, если я ошибаюсь). Выбираем язык Java, так как по нему данная статья, а также, по нему больше информации в Интернете, чем по Kotlin.

  • Минимальный SDK выбираем под Android 5.0, так как данного API будет предостаточно для наших задач, заодно мы получим большой охват, в том числе старых устройств: планшеты, смартфоны, встроенные системы.

Скриншоты: установка и первичная настройка

Далее раскрываем вкладку Project и находим в каталоге Java><Ваш_Проект> файл MainActivity.java, в котором мы будем описывать все происходящее на главном экране.

Подготовка макетов (layouts) - внешний облик приложения

После рассмотрим файл MainActivity.xml, для этого нам нужно найти каталог res>layout>. Откроем MainActivity.xml для создания облика первой - главной страницы и перетягивая спанели Palette необходимые нам типы объектов.

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

Кстати, вместо px, тут используется другая величина - dp, позволяющая на разных экранах видеть одинаковый и желаемый результат.

Кстати, также, советую названия Текст полей переназначать в String значения, чтобы в дальнейшем было проще делать перевод интерфейса - подобный функционал уже встроен в Android Studio. Для этого нажимаем на объект, далее в меню Свойств объекта находим поле text и нажимаем на маленькую плашку-кнопку справа от текста. В открывшимся окне, нажимаем на плюсик слева сверху и создаем название String-переменной и ее значение по умолчанию:

Создание String-переменнойСоздание String-переменной

Для перевода интерфейса, необходимо сохранить изменения и над нашим конструктором Layout нажать на кнопку Default (en-us) и выбрать Edit Translations, далее найти слева сверху значок глобуса и нажать на него для добавления нового языка:

Переводы для интерфейсовПереводы для интерфейсов

Таким образом создадим дополнительные макеты (layouts) для оставшихся двух окон:

Скриншоты: еще два макета
Макет Activity_Settings.xmlМакет Activity_Settings.xmlМакет Activity_Advanced.xmlМакет Activity_Advanced.xml

Программируем на Java под Android

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

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

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

  • На переключателях должен отображаться тот текст, который пользователь настраивает из окна с макетом Activity_Settings.xml

  • Количество переключателей должно соответствовать заданному числу из окна макета Activity_Advanced.xml

  • После выхода из приложения и повторного запуска все переключатели должны оставаться в том же положении, в котором пользователь их оставил

  • Сброс переключателей возможен только, если переключатель Уверен/-а? включен.

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


    Пишем следующее:

Код под спойлером: 156 строчек
package com.bb.myapplication;import androidx.appcompat.app.AppCompatActivity;import androidx.appcompat.widget.SwitchCompat;import android.content.Intent;import android.content.SharedPreferences;import android.graphics.Color;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    //Создаем 9 переключателей с помощью массива    SwitchCompat[] switcharray = new SwitchCompat[9];    Boolean Reset; //Булев для выключения переключателя    Button NextButton;    public int[] list_of_switches = {            R.id.switch_compat1,            R.id.switch_compat2,            R.id.switch_compat3,            R.id.switch_compat4,            R.id.switch_compat5,            R.id.switch_compat6,            R.id.switch_compat7,              R.id.switch_compat8,             R.id.switch_compat10, //переключатель "Вы уверены?" //8    };    //Нажатие кнопки Сброс    public void ResetButtonClick (View view) throws IllegalAccessException {        Reset =false;        if (switcharray[8].isChecked()) { //Если переключатель "Вы уверены?" нажат, то разрешаем переключить в false остальные переключатели            SharedPreferences.Editor editor = getSharedPreferences("save"                    ,MODE_PRIVATE).edit();            //Сохраняем в Intent значения всех переключателей в False            for (int k=0; k<10; k++) {                editor.putBoolean("value"+k, false);            }            editor.apply();            //Устанавливаем все переключатели в значение False            for (int i=0;i<9;i++){                switcharray[i].setChecked(false);            }            //Reset background color of checked SwitchCompats            for (int i = 0; i < 9; i++) {                findViewById(list_of_switches[i]).setBackgroundColor(Color.TRANSPARENT);            }        }    }    //Создание формы / открытие приложения    @Override    protected void onCreate(final Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //Назначаем полям значения по умолчанию и сохраняем их в Intent        String[] tsfield = new String[8];        SharedPreferences prefs = getSharedPreferences("MY_DATA", MODE_PRIVATE);        tsfield[0] = prefs.getString("KEY_F0", "Выключил газ");        tsfield[1] = prefs.getString("KEY_F1", "Выключил воду");        tsfield[2] = prefs.getString("KEY_F2", "Покормил кошек");        tsfield[3] = prefs.getString("KEY_F3", "Закрыл окна");        tsfield[4] = prefs.getString("KEY_F4", "Выключил Интернет");        tsfield[5] = prefs.getString("KEY_F5", "Закрыл дверь");        tsfield[6] = prefs.getString("KEY_F6", "Выключил везде свет");        tsfield[7] = prefs.getString("KEY_F7", "Вынес мусор");        //Получаем настройки текста заголовка        String hellotext = prefs.getString("hellotitletext", "");        switcharray[6] = findViewById(list_of_switches[6]);        switcharray[7] = findViewById(list_of_switches[7]);        //Получаем настройки количества полей        String sixfields = prefs.getString("sixfields", "true");        String sevenfields = prefs.getString("sevenfields", "false");        String eightfields = prefs.getString("eightfields", "false");        if (sixfields.equals("true")){            switcharray[6].setVisibility(View.GONE);            switcharray[7].setVisibility(View.GONE);        }        else if (sevenfields.equals("true")) {            switcharray[6].setVisibility(View.VISIBLE);            switcharray[7].setVisibility(View.GONE);        }        else if (eightfields.equals("true")) {            switcharray[6].setVisibility(View.VISIBLE);            switcharray[7].setVisibility(View.VISIBLE);        }        //Создаем массив из TextView        TextView[] textarr = new TextView[8];        //Каждому переключателю назначаем текст из итерации поля tsfield        for (int i=0; i<8;i++){            textarr[i] = (TextView) findViewById(list_of_switches[i]);            textarr[i].setText(tsfield[i]);        }        //Назначаем текст заголовка            TextView textView5 = (TextView) findViewById(R.id.textView5);            textView5.setText(hellotext);        //Отображать заголовок, если соотв. поле заполнено        if(!hellotext.matches(""))        {            textView5.setVisibility(View.VISIBLE);        }        //Создаем связь каждого элемента переключателя по id из XML с соответствующей переменной типа SwitchCompat        for (int i=0;i<9;i++) {            switcharray[i] = findViewById(list_of_switches[i]);        }        //Создаем связь кнопки по id bt_next из xml переменной NextButton        NextButton = findViewById(R.id.bt_next);        //Используем SharedPreferences = "save"        SharedPreferences sharedPreferences = getSharedPreferences("save"                , MODE_PRIVATE);        //При первом запуске - все переключатели в False        for (int k=0; k<9; k++) {        switcharray[k].setChecked(sharedPreferences.getBoolean("value"+k, false));        }        //При переключении переключателей сохраняем данные, а также, проверяем их при повторном запуске        for (int k=0; k<9; k++) {            final int finalK = k;            switcharray[k].setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View view) {                    if (switcharray[finalK].isChecked()) {                        //когда переключатель включен                        Reset = true; //                        switcharray[finalK].setBackgroundColor(Color.parseColor("#c8a2c6"));                        SharedPreferences.Editor editor = getSharedPreferences("save"                                , MODE_PRIVATE).edit();                        editor.putBoolean("value" + finalK, true);                        editor.apply();                        switcharray[finalK].setChecked(true);                    } else {                        //когда переключатель выключен                        SharedPreferences.Editor editor = getSharedPreferences("save"                                , MODE_PRIVATE).edit();                        editor.putBoolean("value" + finalK, false);                        Reset = false;                        switcharray[finalK].setBackgroundColor(Color.TRANSPARENT);                        editor.apply();                        switcharray[finalK].setChecked(false);                    }                }            });        }        //Кнопка открытия страницы настроек        NextButton.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                //Go to next activity                Intent intent2 = new Intent(MainActivity.this, Activity_settings.class);                startActivity(intent2);            }        });    }}

Следующим этапом будет написание кода для корректной работы макета Activity_Settings.XML, а логика его такова:

  • Введенные пользователь записи сохраняются даже после перезапуска приложения

  • Количество полей соответствуют числу, заданному в настройках из макета Activity_Advanced.xml

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

Код по спойлером: 124 строчки
package com.bb.myapplication;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.content.SharedPreferences;import android.net.Uri;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.TextView;public class Activity_settings extends AppCompatActivity {    //Initialize Variable    Button btBack;    Button fcSubmit;    Button btAdvanced;    //Ассоциируем поля ввода с переменными с помощью массива    EditText[] InputFields = new EditText[8];    //Назначаем полям значения по умолчанию и сохраняем их в Intent    String[] tsfield = new String[8];    //Создаем массив элементов из XML по id    public int[] list_of_fields = {            R.id.inputField0,            R.id.inputField1,            R.id.inputField2,            R.id.inputField3,            R.id.inputField4,            R.id.inputField5,            R.id.inputField6,            R.id.inputField7,    };    private SharedPreferences prefs;    //Создание формы / открытие приложения    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_settings);        //Назначаем полям значения по умолчанию и сохраняем их в Intent        prefs = getSharedPreferences("MY_DATA", MODE_PRIVATE);        //Получаем данные по количеству используемых полей        String sixfields = prefs.getString("sixfields", "true");        String sevenfields = prefs.getString("sevenfields", "false");        String eightfields = prefs.getString("eightfields", "false");        EditText inputField71var = (EditText) findViewById(list_of_fields[6]);        EditText inputField81var = (EditText) findViewById(list_of_fields[7]);        if (sixfields.equals("true")){            inputField71var.setVisibility(View.INVISIBLE);            inputField81var.setVisibility(View.INVISIBLE);        }        else if (sevenfields.equals("true")) {            inputField71var.setVisibility(View.VISIBLE);            inputField81var.setVisibility(View.INVISIBLE);        }        else if (eightfields.equals("true")) {            inputField71var.setVisibility(View.VISIBLE);            inputField81var.setVisibility(View.VISIBLE);        }        tsfield[0] = prefs.getString("KEY_F0", "Выключил газ");        tsfield[1] = prefs.getString("KEY_F1", "Выключил воду");        tsfield[2] = prefs.getString("KEY_F2", "Покормил кошек");        tsfield[3] = prefs.getString("KEY_F3", "Закрыл окна");        tsfield[4] = prefs.getString("KEY_F4", "Выключил Интернет");        tsfield[5] = prefs.getString("KEY_F5", "Закрыл дверь");        tsfield[6] = prefs.getString("KEY_F6", "Выключил везде свет");        tsfield[7] = prefs.getString("KEY_F7", "Вынес мусор");        //Назначаем полям ввода текст из SharedPreferences        for (int i=0; i<8; i++) {            InputFields[i] = (EditText) findViewById(list_of_fields[i]);            InputFields[i].setText(tsfield[i]);        }        //Создаем переменные для кнопок        btBack = findViewById(R.id.bt_back);        fcSubmit = findViewById(R.id.submit_fc);        btAdvanced = findViewById(R.id.btAdvanced);        //Кнопка Назад        btBack.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                //Go back                Intent intent = new Intent (                        Activity_settings.this,MainActivity.class                );                startActivity(intent);            }        });        //Кнопка Расширенные настройки/Дополнительно        btAdvanced.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                //Open Advanced Settings                Intent intent = new Intent (                        Activity_settings.this,Activity_advanced.class                );                startActivity(intent);            }        });    }    //Ссылка-значок на внешний ресурс - ссылка на мой телеграм    public void tglink(View view){        Intent myWebLink = new Intent(android.content.Intent.ACTION_VIEW);        myWebLink.setData(Uri.parse("https://t.me/EndlessNights"));            startActivity(myWebLink);    }    //Кнопка Сохранить данные    public void SaveData(View view)    {        for (int i=0; i<8;i++) {            tsfield[i] = InputFields[i].getText().toString();        SharedPreferences.Editor editor = prefs.edit();        editor.putString("KEY_F"+i, tsfield[i]);            editor.apply();        }        // Открываем главную страницу        startActivity(new Intent(getApplicationContext(), MainActivity.class));    }}

И наконец опишем логику работы последнего окна в приложении - с Дополнительными настройками:

  • Количество полей для отображения - в данном случае выбор с помощью радиокнопок - 6, 7 или 8 полей.

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

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

  • И наконец должны работать оставшиеся кнопки меню.

Код под спойлером: 134 строчки
package com.bb.myapplication;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;import android.content.SharedPreferences;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.CompoundButton;import android.widget.EditText;import android.widget.RadioButton;import android.widget.RadioGroup;import android.widget.Switch;import android.widget.TextView;public class Activity_advanced extends AppCompatActivity {    Button btBack;    //Назначаем радиокнопкам значения по умолчанию    Boolean sixbool = true;    Boolean sevenbool = false;    Boolean eightbool = false;    private SharedPreferences prefsadv;    //Поле ввода текста для заголовка    private EditText hellotitletext;    RadioGroup rdGroup;    //Переменные для радиокнопок    public RadioButton r1, r2, r3;    //Переменные для передачи состояния из boolean в sharedPrefs    String sixdata;    String sevendata;    String eightdata;    Switch bgswitchvar;    private SharedPreferences prefs;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_advanced);        bgswitchvar = findViewById(R.id.bgswitch);        prefsadv = getSharedPreferences("MY_DATA", MODE_PRIVATE);        rdGroup = (RadioGroup)findViewById(R.id.radioGroup);        //Поле заголовка        String hellotitletext1 = prefsadv.getString("hellotitletext","");        hellotitletext = (EditText) findViewById(R.id.hellotitletext);        hellotitletext.setText(hellotitletext1);        //Ассоциируем переменные с полями по id из xml        r1 = findViewById(R.id.sixfields);        r2 = findViewById(R.id.sevenfields);        r3 = findViewById(R.id.eightfields);        //При нажатии на радиокнопку, вызываем функцию Update с заданным ключом        r1.setChecked(Update("rbsix"));        r2.setChecked(Update("rbseven"));        r3.setChecked(Update("rbeight"));        //При нажатии первой кнопки добавляем True с ключом rbsix в RBDATA        r1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {            @Override            public void onCheckedChanged(CompoundButton compoundButton, boolean r1_isChecked) {                SaveIntoSharedPrefs("rbsix", r1_isChecked);            }        });        //При нажатии второй кнопки добавляем True с ключом rbsix в RBDATA        r2.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {            @Override            public void onCheckedChanged(CompoundButton compoundButton, boolean r2_isChecked) {                SaveIntoSharedPrefs("rbseven", r2_isChecked);            }        });        //При нажатии третьей кнопки добавляем True с ключом rbsix в RBDATA        r3.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {            @Override            public void onCheckedChanged(CompoundButton compoundButton, boolean r3_isChecked) {                SaveIntoSharedPrefs("rbeight", r3_isChecked);            }        });        //Back button        btBack = findViewById(R.id.btBackadvanced);        btBack.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                //Go back                Intent intent = new Intent (                        Activity_advanced.this,Activity_settings.class                );                startActivity(intent);            }        });    }    //Сохранение данных в SharedPreferences - ожидая ключ и значение булева типа    private void SaveIntoSharedPrefs(String key, boolean value){        SharedPreferences sp = getSharedPreferences("RBDATA",MODE_PRIVATE);        SharedPreferences.Editor editor = sp.edit();        editor.putBoolean(key,value);        editor.apply();    }    //Функция обновления значения в SharedPreferences    private boolean Update(String key){        SharedPreferences sp = getSharedPreferences("RBDATA",MODE_PRIVATE);        return sp.getBoolean(key, false);    }    //Сохраняем данные по количеству полей    public void SaveDataAdvanced(View view)    {        int checkedId = rdGroup.getCheckedRadioButtonId();        if(checkedId == R.id.sixfields) {            sixbool = true;            sevenbool = Boolean.FALSE;            eightbool = Boolean.FALSE;        }        else if (checkedId == R.id.sevenfields){            sevenbool = true;            sixbool = Boolean.FALSE;            eightbool = Boolean.FALSE;        }        else if (checkedId == R.id.eightfields){            eightbool = true;            sevenbool = Boolean.FALSE;            sixbool = Boolean.FALSE;        }        sixdata = String.valueOf(sixbool);        sevendata = String.valueOf(sevenbool);        eightdata = String.valueOf(eightbool);        String hellofield = hellotitletext.getText().toString();        SharedPreferences.Editor editor = prefsadv.edit();        editor.putString("sixfields", sixdata);        editor.putString("sevenfields", sevendata);        editor.putString("eightfields", eightdata);        editor.putString("hellotitletext", hellofield);        editor.apply();        startActivity(new Intent(getApplicationContext(), MainActivity.class));    }}

Подготовка приложения к публикации

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

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

Регистрация в Google Play

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

Далее вам предстоит оплатить пошлину в $35 за возможность публиковать приложения, это почти в 3 раза дешевле, чем в Steam, при том, что Steam просит $100 за каждое публикуемое приложение/игру, даже бесплатное, а с аккаунтом разработка, в Google Play вы можете публиковать несчётное множество приложений.

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

После создания приложения в консоли разработчика Google Play, необходимо перейти в раздел Рабочая версия и нажать на кнопку Создать новый выпуск. Вам предложат получить электронную подпись для вашего приложения с расширением *.jks, с помощью которой вам предстоит подписать свое первое приложение, а также, все дальнейшие выпуски с обновлениями.

Возвращаемся в Android Studio и необходимо заполнить немного информации о нашем приложении, для этого нажимаем File>Project Structure и заполняем поля Version Code и Version Name - без них Google Play Google Play не допустит ваше приложение до публикации:

Наконец, переходим в следующий раздел: пункт меню Build>Generate Signed Bundle / APK

В открывшимся окне выбираем APK. В подразделе Key Store Path выбираем Create new, далее заполняем все поля (прямая ссылка на официальную инструкцию), далее данный ключ потребуется загрузить в консоль Google Play. Затем вернемся в Android Studio и после ввода всех необходимых данных, нажимаем Next

В следующем окне отмечаем все чекбоксы, выбираем release и нажимаем Finish - Android Studio скомпилирует подписанное приложение, которое можно опубликовать в Google Play.

Итог

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

Наконец отправляем приложение в публикацию. Сотрудники Google Play будут проверять ваше приложение в течении 2 недель, судя по официальным данным. Данное приложение рассматривали в течении 5 суток. Также, стоит учесть, что каждое обновление, также, будут проверять, но на обновления уходит не более 2-3 суток.

Ссылка на GitHub, как обещано. Ссылка на приложение в Google Play.

Подробнее..

Удобное отображение пустого списка

31.01.2021 18:12:32 | Автор: admin

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

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

Каждая подсказа представляет из себя отдельный фрагмент, который может содержать все угодно. Чтобы управлять ими, объекту нужен FragmentManager и Id контейнера, в котором должны находится фрагменты. Больше зависимостей нет.

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

Теперь это надо сделать в реалиях Андроида. Кроме самой активности (или фрагмента), которой нужен объект, на него никто больше ссылаться не будет. Есть начальное, Дефолтное, состояние. Оно будет отображаться при первом появлении, пока не появится новое значение. Сами значения хранятся в LiveData (которая во ViewModel), на которую подписывается активность и передает каждое новое объекту. Это позволяет переживать пересоздание активности и сохранять состояние.

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

Нюансы

Если, например, не очистить список от данных, то подсказка может быть отображена поверх (или под) этим списком, что будет некрасиво. Для этого нужно предварительно очищать список (или скрывать его видимость, что лучше?).

Реализация

CodeSwitcher.

CodeSwitcher. Я не смог придумать адекватного названия.

//код самого объектаpublic class CodeSwitcher {    //перечисление всех ситуаций    public enum Code {        DEFAULT,        HTTP_OK,        HTTP_CREATED,        HTTP_BAD_REQUEST,        HTTP_NOT_FOUND,        NO_DATA    }    //зависимости    private FragmentManager fragmentManager;    private int fragmentHostId;    public CodeSwitcher(FragmentManager fragmentManager, int fragmentHostId) {        this.fragmentManager = fragmentManager;        this.fragmentHostId = fragmentHostId;    }    //метод, который будет заменять фрагменты    public void switchFragments(Code code) {        FragmentTransaction transaction = fragmentManager.beginTransaction();        switch (code) {            case HTTP_OK:                transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_OK"));                break;            case HTTP_CREATED:                transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_CREATED"));                break;            case HTTP_BAD_REQUEST:                transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_BAD_REQUEST"));                break;            case HTTP_NOT_FOUND:                transaction.replace(fragmentHostId, CodeFragment.newInstance("HTTP_NOT_FOUND"));                break;            case NO_DATA:                transaction.replace(fragmentHostId, CodeFragment.newInstance("NO_DATA"));                break;            default:                transaction.replace(fragmentHostId, CodeFragment.newInstance("Default"));                break;        }        transaction.commit();    }}
//код ViewModel//в конструкторе происходит установка дефолтного состоянияpublic CodeShowActivityViewModel() {    listCode = new MutableLiveData<>();    listCode.setValue(CodeSwitcher.Code.DEFAULT);}//методы только для примера, в реальности таких нет, нужны для изменения состоянияpublic void httpOk() {  listCode.setValue(CodeSwitcher.Code.HTTP_OK);  clearList();}public void httpBadRequest() {  listCode.setValue(CodeSwitcher.Code.HTTP_BAD_REQUEST);  clearList();}
//код активности, необходимый для использованияprivate CodeSwitcher switcher;//в onCreate()switcher = new CodeSwitcher(getSupportFragmentManager(), R.id.айди_контейнера);//подписка на LiveData, передача значения на обработкуcodeActVM.getListCode().observe(this, code -> {  switcher.switchFragments(code);});
Гифка (4мб) с описанием

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

Что думаете об этом способе?

P.S.

Беды с названиями...

Подробнее..

Проекты в Gradle 7 как не зависеть от зависимостей

03.06.2021 16:16:28 | Автор: admin

Привет! Меня зовут Ксения Кайшева, я пишу приложения под Android в компании 65apps. Сегодня расскажу о новой возможности, которая позволяет централизованно описывать зависимости на проектах с системой сборки Gradle.

На текущий момент существует множество вариантов описания зависимостей в проектах, использующих Gradle. Рекомендуемого стандарта нет, поэтому используются самые разные подходы: кто-то просто перечисляет зависимости в блоке dependencies, кто-то выносит зависимости в отдельный файл, блок ext и т.д. И для новых разработчиков не всегда очевидно, что, где и как используется в большом и многомодульном проекте.

В 7й версии Gradle представлена новая функция, позволяющая описывать все зависимости централизованно. Эта функция находится на стадии превью, и чтобы воспользоваться ей в файле settings.gradle(.kts) необходимо добавить строку:

enableFeaturePreview("VERSION_CATALOGS")

Так выглядит использование (описанных в централизованном каталоге) зависимостей в любом build.gradle скрипте:

dependencies {
implementation libs.lifecycle.runtime
implementation libs.lifecycle.viewmodel.ktx
implementation libs.lifecycle.extentions
implementation libs.lifecycle.livedata.ktx
}

Здесь:

libs это сам каталог
lifecycle.runtime это зависимость в этом каталоге.

Каталог описывается в settings.gradle(.kts) файле:

dependencyResolutionManagement {
versionCatalogs {
libs {
alias('lifecycle-runtime')
.to(androidx.lifecycle:lifecycle-runtime:2.2.0')
alias('lifecycle-viewmodel-ktx').to(androidx.lifecycle', 'lifecycle-viewmodel-ktx').version {
strictly '[2.2.0, 2.3.0['
prefer '2.3.1'
}
}
}
}

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

Разделение через тире является рекомендованным.

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

Например,

lifecycle-runtime
lifecycle_runtime
lifecycle.runtime
junit5-test-core
spek-runner-junit5

Недопустимо иметь псевдоним для зависимости, которая также принадлежит вложенной группе. Например, lifecycle-viewmodel и lifecycle-viewmodel-ktx.

Gradle рекомендует в таком случае использовать регистр для различения.
Например, lifecycleViewmodel и lifecycleViewmodelKtx.

Версии можно объявлять отдельно и затем ссылаться на них в описаниях самих зависимостей:

dependencyResolutionManagement {
versionCatalogs {
libs {
version('lifecycle', '2.3.1')
alias('lifecycle-viewmodel-ktx').to('androidx.lifecycle', 'lifecycle-viewmodel-ktx').versionRef('lifecycle')
}
}
}

Объявленные таким образом версии также доступны из любогоbuild.gradle файла:

version = libs.versions.lifecycle.get()

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

dependencyResolutionManagement {
versionCatalogs {
libs {
version('lifecycle', '2.3.1')
alias('lifecycle-runtime').to('androidx.lifecycle, 'lifecycle-runtime').versionRef('lifecycle')
alias('lifecycle-viewmodel-ktx').to('androidx.lifecycle, 'lifecycle-viewmodel-ktx').versionRef('lifecycle')
bundle('lifecycle',
['lifecycle-runtime', 'lifecycle-viewmodel-ktx'])
}
}
}

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

dependencies {
implementation libs.bundles.lifecycle
}

Добавление одного пакета эквивалентно добавлению всех зависимостей из пакета по отдельности.

Помимо описания каталога в settings.gradle(.kts) файле, есть более простая возможность собрать все зависимости вместе использовать toml-файл каталоге gradle: libs.versions.toml.

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

По умолчанию libs.versions.toml файл будет входом в libs каталог. Можно также изменить имя каталога по умолчанию, например:

dependencyResolutionManagement {
defaultLibrariesExtensionName.set('deps')
}

Toml-файл состоит из 3 основных разделов:

[versions] - раздел для объявления версий
[libraries] - раздел для объявления зависимостей
[bundles] - раздел для объявления пакетов зависимостей

Например,

[versions]
lifecycle = "2.3.1"

[libraries]
lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" }
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" }

[bundles]
dagger = ["lifecycle-runtime", "lifecycle-viewmodel-ktx"]

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

[versions]
any-lib1 = 1.0
any-lib2 = { strictly = "[1.0, 2.0[", prefer = "1.2" }

Более подробно о расширенном варианте версии по ссылке

Семантика объявления номера версии по ссылке

Саму зависимость можно прописать, указав полное имя или отделив версию от группы и имени:

[libraries]
any-lib = "com.company:anylib:1.4"
any-other-lib = { module = "com.company:other", version="1.4" }
any-other-lib2 = { group = "com.company", name="alternate", version="1.4" }
anylib-full-format = { group = "com.company", name="alternate", version={ require = "1.4" } }

Если необходимо сослаться на версию, объявленную в [versions] разделе, то следует использовать свойство version.ref:

[versions]
some = "1.4"

[libraries]
any-lib = { group = "com.company", name="anylib", version.ref="some" }

Можно использовать несколько toml-файлов.Для этого нужно указать, как импортировать соответствующий файл:

dependencyResolutionManagement {
versionCatalogs {
testLibs {
from(files('gradle/test-libs.versions.toml'))
}
}
}

Также есть возможность создать каталог зависимостей и использовать его между разными проектами в рамках одной компании, например.

Подробнее по ссылке

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

При использовании Groovy не работает автоподстановка при указании зависимости в build.gradle файле и, соответственно, нет возможности провалиться в описание зависимости при нажатии на нее. Исправлять это не планируют. Решение для автоподстановки использовать Kotlin DSL.

Подробнее..

Vivaldi 4.0 Первое приближение

09.06.2021 10:16:43 | Автор: admin
image

Привет, Хабр!

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

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

Мир без барьеров


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


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


Также в настройках браузера, в разделе Общие Язык, вы можете отключить автоматическое предложение перевода в дальнейшем вы сможете делать это вручную только там, где захотите:


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


А теперь немного о самом переводчике, как о компоненте.

Движок переводчика разработан компанией Lingvanex, которая стала нашим партнёром. Принцип работы переводчика вполне стандартный: текст из браузера отправляется на сервер, где осуществляется перевод и результаты возвращаются в браузер. И вот здесь нужно обратить внимание на важный момент: движок переводчика работает на наших собственных серверах, установленных в Исландии. Другими словами, вы можете пользоваться данным переводчиком не опасаясь, что посещаемые вами веб-сайты и, как следствие, ваши интересы станут известны неким крупным сервисам, которые смогут использовать полученные данные по назначению. Короче продать рекламщикам. Мы, как вы знаете, данными пользователей не торгуем и принципиально не интересуемся, что вы посещаете, переводите, пишете или думаете.

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

Почта, которая всегда под рукой


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

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


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


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


Вы выбираете новости, а не наоборот


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


Визуально новостные компоненты отделены от почты, но и по интерфейсу, и по настройкам они сходны с почтовиком:


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

В общем и целом, новостной клиент позволяет контролировать поток получаемой вами информации вы подписываетесь только на те новости, которые вам действительно интересны и важны, а весь мусорный контент идёт лесом. И да, клиент новостей тоже пока в бета-режиме. Как и следующая важная функция

Календарь для тех, кто управляет своим временем


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


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


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


Выбор, как встроенная функция


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


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

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


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

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

Get it on Google Play

Get it on Uptodown

Всем спасибо за помощь в подготовке и тестировании новых версий браузера Vivaldi!
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru