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

Разработка под android

Виды биометрии в мобильном приложении

22.11.2020 10:20:58 | Автор: admin
Для идентификации пользователя в приложении можно использовать биометрию например, сканеры радужной оболочки глаза, геометрии лица или отпечатка пальца. Хотя эти технологии известны и популярны, у начинающих разработчиков из-за недостатка информации до сих пор возникают те или иные вопросы.

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



Основные виды биометрии


Идентификация пользователей необходима во многих приложениях, которые обрабатывают личные данные, например, в онлайн-банках. Так, в России с 2018 года действует Единая биометрическая система (ЕБС), с помощью которой клиенты могут пользоваться услугами банков удаленно. По рекомендациям Банка России, в 2020 году все банки должны иметь возможность собирать биометрические данные пользователей.

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

Популярные способы биометрической идентификации, по данным исследования:

  • Сканер отпечатка пальца (fingerprint) 57%
  • Сканер геометрии лица (face ID) 14%
  • Прочие методы: сканеры радужной оболочки глаза (IRIS) и геометрии руки (3-5%).

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

Рассмотрим особенности нескольких перечисленных способов.

1) Сканер отпечатка пальца (fingerprint)


Для того, чтобы узнать пользователя по отпечатку пальца и безопасно хранить его данные, каждый производитель мобильных устройств предлагает свои возможности. Так, на устройствах Apple образец отпечатка пальца проводится через хеш-функцию перед сохранением в защищенный вычислительном модуль. Все процессы, связанные с Touch ID, происходят именно в этом модуле, и извлечь такие данные невозможно.

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



Freepik.com

2) Сканер геометрии лица (face ID)


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

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



Adobe Stock

При этом не все устройства предоставляют полноценные возможности для распознавания лиц. Бывают случаи, когда производители ограничиваются 2D-сканированием с помощью обычной камеры. Как правило, при этом на картинке выделяется лицо, которое можно сравнить с другими изображениями в базе как в игре Найди 10 отличий. Если приложению не удастся найти отличия, то пользователь может быть распознан как владелец. В этом случае есть риск, что потенциальному злоумышленнику удастся разблокировать приложение, просто просканировав фотографию владельца.

3) Сканер радужной оболочки глаза (IRIS)


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

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

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



Adobe Stock

Как работает распознавание


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

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



Блок-схема с симметричными криптографическими ключами (источник)

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

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

Рассмотрим, как будет выглядеть процесс регистрации:

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

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


Блок-схема с асимметричными криптографическими ключами (источник)

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

Подводя итоги


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

Kotlin FP моноиды и сортировки

26.11.2020 08:23:40 | Автор: admin
В данной статье мы рассмотрим понятие моноид и узнаем, как он может помочь нам при сортировке данных.

Интересующихся функциональным программированием на Kotlin также приглашаю заглянуть на мой youtube-канал, где я разбираю разные интересные моменты из Kotlin FP.




Теория


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

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

Ассоциативность это свойство, при котором результат последовательного применения операций не зависит от расстановки скобок:

$a + b + c == a + (b + c) == (a + b) + c$


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

Нейтральный элемент ноль, поскольку:

$0 + a == a == a + 0$


Многие примитивные типы являются моноидом.

Например:

Натуральные числа с операцией умножения и нейтральным элементом 1

$a * b * c == a * (b * c) == (a * b) * c$


$1 * a == a == a * 1 $


Строки с операцией конкатенации (склеивания) и нейтральным элементом (пустой строкой)

$a + b + c == a + (b + c) == (a + b) + c$


$$display$$ "" + a == a == a + ""$$display$$


Теперь перейдем к функциям.


Возьмем функцию, у которой принимаемый тип равен возвращаемому:

$(A) -> A$


Эта функция с операцией композиции является моноидом.

Прежде чем рассматривать моноид, предлагаю вспомнить, что такое композиция функций.
Допустим, у нас есть две функции.
Первая принимает аргумент A и возвращает значения типа B:

$(A) -> B$


Вторая принимает аргумент B и возвращает значения типа С:

$(B) -> C$


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

$(A) -> C$


Это преобразование и есть композиция функций.

Реализация композиции функций:

infix fun <A, B, C> ((A) -> B).andThen(g: (B) -> C): (A) -> C = { a: A ->    g(this(a))}

Ассоциативность композиции довольно очевидна:

val f: (A) -> Bval g: (B) -> Cval h: (C) -> Df andThen g andThen h == f andThen (g andThen h) == (f andThen g) andThen h

Нейтральным элементом для операции композиции будет являться функция identity:

fun <T> identity(x: T): T = x

То есть такая функция, которая возвращает значение, принятое на входе.

Доказательство того, что identity является нейтральным элементом так же очевидно:

f andThen ::identity = f = ::identity andThen f

Теперь вновь вернемся к функции

$(A) -> A$


Так как композиция подобных функций является бинарной операцией,
а композиция ассоциативна и имеет нейтральный элемент, то функция
(A) -> A является моноидом.

Далее рассмотрим функцию:

$(A) -> B$


где B это моноид (т.е. область значения функции является моноидом).

Такая функция с бинарной операцией

$((A) -> B), (A) -> B)) -> (A) -> B$


будет также являться моноидом.

Для примера рассмотрим функцию:

$(A) -> Int$


Ранее мы рассмотрели, что множество значений типа Int с бинарной операцией сложения это моноид.

А для функции (A) -> Int мы можем написать бинарную операцию:

operator fun <A> ((A) -> Int).plus(g: (A) -> Int) = { a: A -> this(a) + g(a) }

Ассоциативность которой прослеживается из Int и нейтральным элементом является функция, возвращающая нейтральный элемент для Int, то есть:

val f: (A) -> Int = { 0 }

Функции моноиды нам пригодятся на практике, к которой мы сейчас перейдем.

Практика


Создадим интерфейс моноида:

interface Monoid<A> {   operator fun plus(rh: A): A   fun empty(): A}

Он содержит две функции:
  • plus ассоциативная бинарная операция
  • empty нейтральный элемент для функции plus


Создадим enum-класс, который содержит в себе значения, соответствующие типу отношений между объектами (больше, меньше, либо равно):

enum class Order(val compareValue: Int) {   LT(-1),   EQ(0),   GT(1)}

Допустим, у нас имеется класс пользователя User с полями name и age.
Перед нами стоит задача сравнить двух пользователей user1 и user2.
Мы можем сравнить имена пользователей и получить значение Order, показывающее тип отношений между ними.
Также мы можем сравнить возраст пользователей и получить соответствующее значение Order.

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

fun plus(nameOrder: Order, ageOrder: Order) = when(nameOrder) {       LT -> LT       EQ -> ageOrder       GT -> GT   }

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

enum class Order(val compareValue: Int) : Monoid<Order> {  LT(-1),  EQ(0),  GT(1);   override fun plus(rh: Order) = when (this) {       LT -> LT       EQ -> rh       GT -> GT   }   override fun empty() = EQ}

Ассоциативность в данном случае хорошо прослеживается:

$order1 + order2 + order3 == order1 + (order2 + order3) == (order1 + order2) + order3$


Так как не важен порядок скобок, наибольший приоритет будет иметь order, стоящий в операции левее остальных.
И нейтральный элемент EQ, который соответствует правилу:

$EQ + order == order + EQ == order$


Следующим шагом рассмотрим функцию:

$(A, A) -> Order$


Она также является моноидом, так как область значения этой функции моноид.

Реализация:

fun interface ComparatorMonoid<A> : Monoid<ComparatorMonoid<A>> {   fun compare(a: A, other: A): Order   override fun plus(rh: ComparatorMonoid<A>) =       ComparatorMonoid<A> { a, other -> compare(a, other) + rh.compare(a, other) }   override fun empty() = ComparatorMonoid<A> { _, _ -> Order.EQ }}

В Kotlin мы можем сравнивать основные типы (String, Int, Float, ...), потому что они наследуются от интерфейса Comparable. Из этого следует, что мы можем написать функцию получения Order для основных типов Kotlin:

fun <A : Comparable<A>> A.compare(other: A) = when {   this > other -> Order.GT   this == other -> Order.EQ   else -> Order.LT}

Также мы сможем получать наш ComparatorMonoid из функции, возвращающей значение типа Comparable:

val <A, B : Comparable<B>> ((A) -> B).comparator    get():ComparatorMonoid<A> = ComparatorMonoid<A> { a, b ->        invoke(a).compare(invoke(b))    }

Теперь научим списки работать с ComparatorMonoid:

fun <A> Iterable<A>.sort(comparatorMonoid: ComparatorMonoid<A>) =   sortedWith { a, b -> comparatorMonoid.compare(a, b).compareValue }

В итоге мы получили следующее:
  • Списки, которые умеют сортироваться на основе ComparatorMonoid;
  • ComparatorMonoid, который умеет комбинироваться на основании того, что он является моноидом;
  • ComparatorMonoid можно получить из Kotlin Comparable.


Теперь давайте воспользуемся этим.

Создадим класс User и необходимый для него класс Address:

data class User(val name: String, val age: Int, val gender: String, val address: Address)data class Address(val city: String, val number: Int)

Создадим тестовые данные:

val users: List<User>

Теперь мы можем сортировать этот список по любым полям и в любой последовательности, в которой нам нужно.
К примеру, у нас стоит задача произвести сортировку сначала по возрасту, затем по имени. Решается она следующим образом:

users.sort(User::age.comparator + User::name.comparator)

Если нам необходима сортировка пользователей по названию города, которое содержится в поле city класса Address, а затем по полу (gender), а после него ещё и по имени (name), то функция будет выглядеть следующим образом:

users.sort(   User::address.andThen(Address::city).comparator +   User::gender.comparator +   User::name.comparator)

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

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

users.sort(   User.address.city.comparator +   User.gender.comparator +   User.name.comparator)




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

Магическая шаблонизация для Android-проектов

26.11.2020 10:08:34 | Автор: admin


Начиная с Android Studio 4.1, Google прекратил поддержку кастомных FreeMarker-ных шаблонов. Теперь вы не можете просто взять и написать свои ftl-файлы и сложить их в определённую папку, чтобы Android Studio самостоятельно добавила их в меню New Other. В качестве альтернативы нам предлагают разбираться в плагиностроении и создавать шаблоны изнутри плагинов IDEA. Нас в hh такая ситуация не очень устраивает, так как есть несколько полезных FreeMarker-ных шаблонов, которые мы постоянно используем и которые иногда нуждаются в обновлениях. Лезть в плагины, чтобы поправить какой-то шаблон? Нет уж, увольте.


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


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


*Geminio заклинание удвоения предметов во вселенной Гарри Поттера


Немного терминологии


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


Я буду называть шаблоном набор метаданных, который необходим в построении диалога для ввода пользовательских параметров. Рецептом назовём набор инструкций для исполнения, который отработает после того, как пользователь введёт данные. Когда я буду говорить про шаблонный текст генерируемого кода, я буду называть это ftl-шаблонами или FreeMarker-ными шаблонами.


Чем заменили FreeMarker?


Google уже давно объявил Kotlin предпочитаемым языком для разработки под Android. Все новые библиотеки, новые приложения в Google постепенно переписываются именно на Kotlin. И плагин android-а в Android Studio не стал исключением.


Как механизм шаблонов работал до Android Studio 4.1? Вы создавали папку для описания шаблона, заводили в нём несколько файлов globals.xml.ftl, template.xml, recipe.xml.ftl для описания параметров и инструкций выполнения шаблона, а ещё вы помещали туда ftl-шаблоны, служившие каркасом генерируемого кода. Затем все эти файлы перемещали в папку Android Studio/plugins/android/lib/templates/<category>. После запуска проекта Android Studio парсила содержимое папки /templates, добавляла в интерфейс меню New > дополнительные action-ы, а при вызове action-а читала содержимое template.xml, строила UI и так далее.


В целом понятно, почему в Google отказались от этого механизма. Создание нового шаблона на основе FreeMarker-ных recipe-ов раньше напоминало русскую рулетку: до запуска ты никогда не мог точно сказать, правильно ли его описал, все ли требуемые параметры заполнил. А потом, по реакции Android Studio, ты пытался определить, в какой конкретной букве ошибся. Находил ошибку, менял шаблон, и всё шло на новый круг. А число шаблонов растёт, растёт и количество мест в интерфейсе, куда хочется добавлять эти шаблоны. Раньше для добавления одного и того же шаблона в несколько мест интерфейса приходилось создавать дополнительные action-ы плагины. Нужно было упрощать.


Вот так и появился удобный Kotlin DSL для описания шаблонов. Сравните два подхода:


FreeMarker-ный подход

Вот так выглядел файл template.xml:


<?xml version="1.0"?><template    format="4"    revision="1"    name="HeadHunter BaseFragment"    description="Creates HeadHunter BaseFragment"    minApi="7"    minBuildApi="8">    <category value="HeadHunter" />    <!-- параметры фрагмента -->    <parameter        id="className"        name="Fragment Name"        type="string"        constraints="class|nonempty|unique"        default="BlankFragment"        help="The name of the fragment class to create" />    <parameter        id="fragmentName"        name="Fragment Layout Name"        type="string"        constraints="layout|nonempty|unique"        default="fragment_blank"        suggest="fragment_${classToResource(className)}"        help="The name of the layout to create" />    <parameter        id="includeFactory"        name="Include fragment factory method?"        type="boolean"        default="true"        help="Generate static fragment factory method for easy instantiation" />    <!-- доп параметры  -->    <parameter        id="includeModule"        name="Include Toothpick Module class?"        type="boolean"        default="true"        help="Generate fragment Toothpick Module for easy instantiation" />    <parameter        id="moduleName"        name="Fragment Toothpick Module"        type="string"        constraints="class|nonempty|unique"        default="BlankModule"        visibility="includeModule"        suggest="${underscoreToCamelCase(classToResource(className))}Module"        help="The name of the Fragment Toothpick Module to create" />    <thumbs>        <thumb>template_base_fragment.png</thumb>    </thumbs>    <globals file="globals.xml.ftl" />    <execute file="recipe.xml.ftl" /></template>

А ещё был файл recipe.xml.ftl:


<?xml version="1.0"?><recipe>    <#if useSupport>    <dependency mavenUrl="com.android.support:support-v4:19.+"/>    </#if>    <instantiate        from="res/layout/fragment_blank.xml.ftl"        to="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />    <open file="${escapeXmlAttribute(resOut)}/layout/${escapeXmlAttribute(fragmentName)}.xml" />    <instantiate        from="src/app_package/BlankFragment.kt.ftl"        to="${srcOutRRR}/${className}.kt" />    <open file="${srcOutRRR}/${className}.kt" />    <#if includeModule>        <instantiate            from="src/app_package/BlankModule.kt.ftl"            to="${srcOutRRR}/di/${moduleName}.kt" />        <open file="${srcOutRRR}/di/${moduleName}.kt" />    </#if></recipe>

То же самое, но в Kotlin DSL

Сначала мы создаём описание шаблона с помощью специального TemplateBuilder-а:


val baseFragmentTemplate: Template    get() = template {        revision = 1        name = "HeadHunter BaseFragment"        description = "Creates HeadHunter BaseFragment"        minApi = 7        minBuildApi = 8        formFactor = FormFactor.Mobile        category = Category.Fragment        screens = listOf(            WizardUiContext.FragmentGallery,            WizardUiContext.MenuEntry        )        // параметры        val className = stringParameter {            name = "Fragment Name"            constraints = listOf(                Constraint.CLASS,                Constraint.NONEMPTY,                Constraint.UNIQUE            )            default = "BlankFragment"            help = "The name of the fragment class to create"        }        val fragmentName = stringParameter {            name = "Fragment Layout Name"            constraints = listOf(                Constraint.LAYOUT,                Constraint.NONEMPTY,                Constraint.UNIQUE            )            default = "fragment_blank"            suggest = { "fragment_${classToResource(className.value)}" }            help = "The name of the layout to create"        }        val includeFactory = booleanParameter {            name = "Include fragment factory method?"            default = true            help = "Generate static fragment factory method for easy instantiation"        }        // доп. параметры        val includeModule = booleanParameter {            name = "Include Toothpick Module class?"            default = true            help = "Generate fragment Toothpick Module for easy instantiation"        }        val moduleName = stringParameter {            name = "Fragment Toothpick Module"            constraints = listOf(                Constraint.CLASS,                Constraint.NONEMPTY,                Constraint.UNIQUE            )            visible = { includeModule.value }            suggest = { "${underscoreToCamelCase(classToResource(className.value))}Module" }            help = "The name of the Fragment Toothpick Module to create"            default = "BlankFragmentModule"        }        thumb { File("template_base_fragment.png") }        recipe = { templateData ->            baseFragmentRecipe(                moduleData = templateData as ModuleTemplateData,                className = className.value,                fragmentName = fragmentName.value,                includeFactory = includeFactory.value,                includeModule = includeModule.value,                moduleName = moduleName.value            )        }    }

Затем описываем рецепт в отдельной функции:


fun RecipeExecutor.baseFragmentRecipe(    moduleData: ModuleTemplateData,    className: String,    fragmentName: String,    includeFactory: Boolean,    includeModule: Boolean,    moduleName: String) {    val (projectData, srcOut, resOut, _) = moduleData    if (projectData.androidXSupport.not()) {        addDependency("com.android.support:support-v4:19.+")    }    save(getFragmentBlankLayoutText(), resOut.resolve("/layout/${fragmentName}.xml"))    open(resOut.resolve("/layout/${fragmentName}.xml"))    save(getFragmentBlankClassText(className, includeFactory), srcOut.resolve("${className}.kt"))    open(srcOut.resolve("${className}.kt"))    if (includeModule) {        save(getFragmentModuleClassText(moduleName), srcOut.resolve("/di/${moduleName}.kt"))        open(srcOut.resolve("/di/${moduleName}.kt"))    }}private fun getFragmentBlankClassText(className: String, includeFactory: Boolean): String {    return "..."}private fun getFragmentBlankLayoutText(): String {    return "..."}private fun getFragmentModuleClassText(moduleName: String): String {    return "..."}

Текст шаблонов перекочевал из FreeMarker-ных ftl-файлов в Kotlin-овские строчки.


По количеству кода получается примерно то же самое, но вот наличие подсказок IDE при описании шаблона помогает не ошибаться в значениях enum-ов и функциях. Добавьте к этому валидацию при создании объекта шаблона (например, покажется исключение, если вы забыли указать один из необходимых параметров), возможность вызова шаблона из разных меню в Android Studio и, кажется, у нас есть победитель.


Добавление шаблона через extension point


Чтобы новые шаблоны попали в существующие галереи новых объектов в Android Studio, нужно добавить созданный с помощью DSL шаблон в новую точку расширения (extension point) WizardTemplateProvider.


Для этого мы сначала создаём класс provider-а, наследуясь от абстрактного класса WizardTemplateProvider:


class MyWizardTemplateProvider : WizardTemplateProvider() {    override fun getTemplates(): List<Template> {        return listOf(            baseFragmentTemplate        )    }}

А затем добавляем созданный provider в качестве extension-а в plugin.xml файле:


<extensions defaultExtensionNs="com.android.tools.idea.wizard.template">    <wizardTemplateProvider implementation="ru.hh.plugins.geminio.actions.MyWizardTemplateProvider" /></extensions>

Запустив Android Studio, мы увидим шаблон baseFragmentTemplate в меню New->Fragment и в галерее нового фрагмента.


Покажи картинки!

Вот наш шаблон в меню New -> Fragments:



А вот он же в галерее нового фрагмента:



Если вы захотите самостоятельно пройти весь этот путь по добавлению нового шаблона из кода плагина, можете, во-первых, посмотреть на актуальный список готовых шаблонов в исходном коде Android Studio (который совсем недавно наконец-то добавили в cs.android.com), а во-вторых почитать вот эту статью на Medium (там хорошо описана последовательность действий по созданию нового шаблона, но показан не очень правильный хак с получением инстанса Project-а так лучше не делать).


А чем ещё можно заменить FreeMarker?


Кроме того, добавить шаблоны кода из плагинов можно с помощью File templates. Это очень просто: добавляете его в папку resources/fileTemplates и Вы восхитительны!


А можно поподробнее?

В папку /resources/fileTemplates вашего плагина нужно добавить шаблон нужного вам кода, например, /resources/fileTemplates/Toothpick Module.kt.ft .


package ${PACKAGE_NAME}.diimport toothpick.config.Moduleinternal class ${NAME}: Module() {    init {            // TODO    }}

Шаблоны кода работают на движке Velocity, поэтому можно добавлять в код шаблона условия и циклы. File template-ы имеют ряд встроенных параметров, например, PACKAGE_NAME (подставит package name, в зависимости от выбранного в Project View файла), MONTH (текущий месяц) и так далее. Каждый "неизвестный" параметр будет преобразован в поле ввода для пользователя.


После запуска Android Studio в меню New вы увидите новый пункт с названием вашего шаблона:



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



Примеры таких шаблонов вы можете подсмотреть в репозитории MviCore коллег из Badoo.


В чём минус таких шаблонов они не позволяют вам одновременно добавить несколько файлов. Поэтому мы в hh их обычно не создаём.


Что не так с новым механизмом


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


Мы же хотим оперативно обновлять содержимое ftl-файлов, добавлять новые шаблоны и желательно без вмешательства в плагин, потому что отладка шаблонов из плагина тот ещё квест =) А ещё мы очень не хотим выбрасывать готовые шаблоны, которые заточены под использование FreeMarker-а.


Механизм рендеринга шаблонов


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


Разобрались. Делимся.


Чтобы заставить Android Studio построить UI и сгенерировать код на основе нужного шаблона, придётся написать довольно много кода. Допустим, вы уже создалисобственный плагин, объявили зависимости от android-плагина, который лежит в Android Studio 4.1, добавили новый action, который будет отвечать за рендеринг. Тогда метод actionPerformed будет выглядеть вот так:


Обработка actionPerformed
override fun actionPerformed(e: AnActionEvent) {    val dataContext = e.dataContext    val module = LangDataKeys.MODULE.getData(dataContext)!!    var targetDirectory = CommonDataKeys.VIRTUAL_FILE.getData(dataContext)    if (targetDirectory != null && targetDirectory.isDirectory.not()) {       // If the user selected a simulated folder entry (eg "Manifests"), there will be no target directory        targetDirectory = targetDirectory.parent    }    targetDirectory!!    val facet = AndroidFacet.getInstance(module)    val moduleTemplates = facet.getModuleTemplates(targetDirectory)    assert(moduleTemplates.isNotEmpty())    val initialPackageSuggestion = facet.getPackageForPath(moduleTemplates, targetDirectory).orEmpty()    val renderModel = RenderTemplateModel.fromFacet(        facet,        initialPackageSuggestion,        moduleTemplates[0],        "MyActionCommandName",        ProjectSyncInvoker.DefaultProjectSyncInvoker(),        true,    ).apply {        newTemplate = template { ... } // build your template     }     val configureTemplateStep = ConfigureTemplateParametersStep(         model = renderModel,         title = "Template name",         templates = moduleTemplates     )     val wizard = ModelWizard.Builder()                    .addStep(configureTemplateStep).build().apply {          val resultListener = object : ModelWizard.WizardListener {          override fun onWizardFinished(result: ModelWizard.WizardResult) {              super.onWizardFinished(result)              if (result.isFinished) {                  // TODO do some stuff after creating files                  //   (renderTemplateModel.createdFiles)              }          }       }    }     val dialog = StudioWizardDialogBuilder(wizard, "Template wizard")            .setProject(e.project!!)            .build()     dialog.show()}

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


По логике программы, пользователь плагина нажимает Cmd + N на каком-то файле или package-е внутри какого-то модуля. Именно там мы и создадим пачку файлов, которые нам нужны. Поэтому необходимо определить, внутри какого же модуля и какой папки работаем.


Чтобы это сделать, воспользуемся возможностями AnActionEvent-а.


val dataContext = e.dataContextval module = LangDataKeys.MODULE.getData(dataContext)!!var targetDirectory = CommonDataKeys.VIRTUAL_FILE.getData(dataContext)if (targetDirectory != null && targetDirectory.isDirectory.not()) {    // If the user selected a simulated folder entry (eg "Manifests"), there will be no target directory    targetDirectory = targetDirectory.parent}targetDirectory!!

Как я уже рассказывал в своей статье с теорией плагиностроения, AnActionEvent представляет собой контекст исполнения вашего Action-а. Внутри этого класса есть свойство dataContext, из которого при помощи специальных ключей мы можем доставать нужные данные. Чтобы посмотреть, какие ещё ключи есть, обратите внимание на классы PlatformDataKeys, LangDataKeys и другие. Ключ LangDataKeys.MODULE возвращает нам текущий модуль, а CommonDataKeys.VIRTUAL_FILE выбранный пользователем в Project View файл. Немного преобразований и мы получаем директорию, внутрь которой нужно добавлять файлы.


val facet = AndroidFacet.getInstance(module)

Чтобы двигаться дальше, нам требуется объект AndroidFacet. Facet это, по сути, свойства модуля, которые специфичны для того или иного фреймворка. В данном случае мы получаем специфичное для Android описание нашего модуля. Из facet-а можно достать, например, package name, указанный в AndroidManifest.xml вашего android-модуля.


val moduleTemplates = facet.getModuleTemplates(targetDirectory)assert(moduleTemplates.isNotEmpty())val initialPackageSuggestion = facet.getPackageForPath(moduleTemplates, targetDirectory).orEmpty()

Из facet-а мы достаём объект NamedModuleTemplate контейнер для основных путей android-модуля: путь до папки с исходным кодом, папки с ресурсами, тестами и т.д. Благодаря этому объекту можно найти и package name для подстановки в будущие шаблоны кода.


val renderModel = RenderTemplateModel.fromFacet(    facet,    initialPackageSuggestion,    moduleTemplates[0],    "MyActionCommandName",    ProjectSyncInvoker.DefaultProjectSyncInvoker(),    true,).apply {    newTemplate = template { ... } // build your template}

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


  • AndroidFacet модуля, в котором мы создаем файлы;
  • первый предлагаемый пользователю package name (его можно будет использовать в параметрах шаблона);
  • объект, хранящий пути к основным папкам модуля, NamedModuleTemplate;
  • строковую константу для идентификации WriteCommandAction (внутренний объект IDEA, предназначенный для операций модификации кода) она нужна для того, чтобы у вас сработал Undo;
  • объект, отвечающий за синхронизацию проекта после создания файлов, ProjectSyncInvoker;
  • и, наконец, флаг true или false, который отвечает за то, можно ли открывать все созданные файлы в редакторе кода или нет.

val configureTemplateStep = ConfigureTemplateParametersStep(    model = renderModel,    title = "Template name",    templates = moduleTemplates)val wizard = ModelWizard.Builder()    .addStep(configureTemplateStep)    .build().apply {        val resultListener = object : ModelWizard.WizardListener {               override fun onWizardFinished(result: ModelWizard.WizardResult) {                       super.onWizardFinished(result)                       if (result.isFinished) {                               // TODO do some stuff after creating files                   //   (renderTemplateModel.createdFiles)                       }               }     }}val dialog = StudioWizardDialogBuilder(wizard, "Template wizard")            .setProject(e.project!!)            .build()dialog.show()

Финал!


Для начала создаем ConfigureTemplateParametersStep, который прочитает переданный объект template-а и сформирует UI страницы wizard-диалога, потом пробрасываем step в модель Wizard-диалога и наконец-то показываем сам диалог.


А ещё мы добавили специальный listener на событие завершения диалога, так что после создания файлов можем ещё и как-то их модифицировать. Достучаться до созданных файлов можно через renderTemplateModel.createdFiles.


Самое сложное позади! Мы показали диалог, который взял на себя работу по построению UI из модели шаблона и обработку рецепта внутри шаблона.


Остаётся только откуда-то получить сам шаблон. И рецепт.


Откуда взять модель шаблона


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


Мне показалось, что самый простой формат это yaml-конфиг. Почему именно yaml? Потому что: а) выглядит проще XML, и б) внутри IDEA уже есть подключенная библиотечка для его парсинга SnakeYaml, позволяющая в одну строчку прочитать весь файл в Map<String, Any>, который можно дальше крутить как угодно.


В данный момент конфиг шаблона выглядит так:


yaml-конфиг шаблона
requiredParams:  name: HeadHunter BaseFragment  description: Creates HeadHunter BaseFragmentoptionalParams:  revision: 1  category: fragment  formFactor: mobile  constraints:    - kotlin  screens:    - fragment_gallery    - menu_entry  minApi: 7  minBuildApi: 8widgets:  - stringParameter:      id: className      name: Fragment Name      help: The name of the fragment class to create      constraints:        - class        - nonempty        - unique      default: BlankFragment  - stringParameter:      id: fragmentName      name: Fragment Layout Name      help: The name of the layout to create      constraints:        - layout        - nonempty        - unique      default: fragment_blank      suggest: fragment_${className.classToResource()}  - booleanParameter:      id: includeFactory      name: Include fragment factory method?      help: Generate static fragment factory method for easy instantiation      default: true  - booleanParameter:      id: includeModule      name: Include Toothpick Module class?      help: Generate fragment Toothpick Module for easy instantiation      default: true  - stringParameter:      id: moduleName      name: Fragment Toothpick Module      help: The name of the Fragment Toothpick Module to create      constraints:        - class        - nonempty        - unique      default: BlankModule      visibility: ${includeModule}      suggest: ${className.classToResource().underlinesToCamelCase()}Modulerecipe:  - instantiateAndOpen:      from: root/src/app_package/BlankFragment.kt.ftl      to: ${srcOut}/${className}.kt  - instantiateAndOpen:      from: root/res/layout/fragment_blank.xml.ftl      to: ${resOut}/layout/${fragmentName}.xml  - predicate:      validIf: ${includeModule}      commands:        - instantiateAndOpen:            from: root/src/app_package/BlankModule.kt.ftl            to: ${srcOut}/di/${moduleName}.kt

Вся конфигурация шаблона делится на 4 секции:


  • requiredParams параметры, обязательные для каждого шаблона;
  • optionalParams параметры, которые можно спокойно опустить при описании шаблона. В данный момент эти параметры ни на что не влияют, потому что мы не подключаем созданный на основе конфига шаблон через extension point.
  • widgets набор параметров шаблона, которые зависят от пользовательского ввода. Каждый из этих параметров в конечном итоге превратится в виджет на UI диалога (textField-ы, checkbox-ы и т.п.);
  • recipe набор инструкций, которые выполняются после того, как пользователь заполнит все параметры шаблона.

Написанный мною плагин парсит этот конфиг, конвертирует его в объект шаблона Android Studio и пробрасывает в RenderTemplateModel.


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


suggest: ${className.classToResource().underlinesToCamelCase()}Module

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


sealed class Command {    data class Fixed(        val value: String    ) : Command()    data class Dynamic(        val parameterId: String,        val modifiers: List<GeminioRecipeExpressionModifier>    ) : Command()    data class SrcOut(        val modifiers: List<GeminioRecipeExpressionModifier>    ) : Command()    data class ResOut(        val modifiers: List<GeminioRecipeExpressionModifier>    ) : Command()    object ReturnTrue : Command()    object ReturnFalse : Command()}

Каждая команда знает, как себя вычислить, какой она внесёт вклад в итоговый результат, требуемый в том или ином параметре. Над парсингом выражений пришлось немного посидеть: сначала я хотел выцепить отдельные кусочки ${...} с помощью регулярок, но вы же знаете, если вы хотите решить какую-то проблему с помощью регулярных выражений, то у вас появляется ещё одна проблема. В итоге я распарсил строчку посимвольно.


Что ещё хорошо в своём собственном формате конфига можно добавлять новые ключи и строить на них свою дополнительную логику. Так, например, появилась новая команда для рецептов instantiateAndOpen, которая сначала создаёт файл из текста ftl-шаблона, а потом открывает созданный файл в редакторе кода. Да-да, в FreeMarker-ных шаблонах уже были команды instantiate и open, но это были отдельные команды.


recipe:  # Можно писать вот так  - instantiate:      from: root/src/app_package/BlankFragment.kt.ftl      to: ${srcOut}/${className}.kt  - open:      file: ${srcOut}/${className}.kt  # А можно одной командой:  - instantiateAndOpen:      from: root/src/app_package/BlankFragment.kt.ftl      to: ${srcOut}/${className}.kt

Какие ещё есть плюсы в Geminio


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


Если бы вы создавали шаблон из плагина, то вы бы не избежали этой проблемы с перезапуском IDE в случае ошибки ваш шаблон бы просто не работал.


Roadmap


Я был бы рад сказать, что уже сейчас плагин поддерживает все возможности, которые были у FreeMarker-ных шаблонов, но нет. Далеко не все возможности нужны прямо сейчас, а до некоторых мы обязательно доберёмся в рамках улучшения других плагинов. Например:


  • нет поддержки enum-параметров, которые бы отображались на UI в виде combobox-ов;
  • не все команды из FreeMarker-ных шаблонов поддерживаются в рецептах например, нет автоматического добавления зависимостей в build.gradle, merge-а XML-ресурсов;
  • новые шаблоны страдают от той же проблемы, что и FreeMarker-ные шаблоны нет адекватной валидации, которая бы точно сказала, где именно случилась ошибка;
  • и нет никаких подсказок IDE при описании шаблона.

Заключение


Заканчивать нужно на позитивной ноте. Поэтому вот немного позитива:


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

Всем успешной автоматизации.


Полезные ссылки


Подробнее..

Перевод Koin библиотека для внедрения зависимостей, написанная на чистом Kotlin

26.11.2020 16:14:32 | Автор: admin

Как управлять внедрением зависимостей с помощью механизма временной области (scope)

Для будущих студентов курса "Android Developer. Professional" подготовили перевод полезной статьи.

Также приглашаем принять участие в открытом вебинаре на тему "Пишем Gradle plugin"


О чем эта статья

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

Введение

Разработчики ОС Android не рекомендуют использовать внедрение зависимостей (Dependency Injection, DI (англ.)), если в вашем приложении три экрана или меньше. Но если их больше, лучше применить DI.

Популярный способ реализации DI в Android-приложениях основан на фреймворке Dagger. Но он требует глубокого изучения. Одна из лучших альтернатив этому фреймворку Koin, библиотека, написанная на чистом Kotlin.

Если вы уже пользовались Dagger или любой другой библиотекой для DI, то, скорее всего, знаете, насколько важен в этом процессе механизм временной области (scope). Он позволяет определять, в каких случаях нужно получать один и тот же зависимый объект, а в каких новый. Он также помогает освобождать невостребованные ресурсы и память.

Области в Koin

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

Как правило, в Koin три вида временных областей.

  • single (одиночный объект)создается объект, который сохраняется в течение всего периода существования контейнера (аналогично синглтону);

  • factory (фабрика объектов) каждый раз создается новый объект, без сохранения в контейнере (совместное использование невозможно);

  • scoped (объект в области) создается объект, который сохраняется в рамках периода существования связанной временной области.

Одиночный объект(single). Фабрика объектов(factory)Одиночный объект(single). Фабрика объектов(factory)

Область вида single при каждом запросе возвращает один и тот же экземпляр, а factory каждый раз возвращает новый экземпляр.

Настраиваемая область

Стандартные области single и factory в Koin живут в течение жизненного цикла модулей Koin. Однако в реальных сценариях использования требования к внедрению зависимостей будут отличаться.

Зависимости обычно нужны только на определенный период времени. Например, репозиторий OnBoardRepository в Android-приложении требуется только при регистрации пользователя. Как только пользователь авторизуется, удержание этого репозитория в памяти станет лишней тратой ресурсов.

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

Шаг 1

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

creating custom koin scope

Шаг 2

Следующим шагом объявим необходимые зависимости с использованием областей single и factory в соответствии с требованиями проекта. Ключевой момент заключается в присвоении областям уникальных квалификаторов. Вот так:

dependencies inside custom scopes

Шаг 3

Мы закончили настройку в модуле Koin. На этом шаге нам нужно создать область из того компонента, из которого мы импортируем нужные зависимости. Обычно области создаются из Android-компонентов, например Activity,Fragment и т.п.

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

val stringQualifiedScope = getKoin().createScope(    "ScopeNameID", named("CustomeScope"))

Получив CustomScope как значение параметра имени, Koin будет искать область, которую мы объявили под этим именем в модулях Koin. ScopeNameID это идентификатор, который мы применяем, чтобы отличать одну область от другой. Он используется на внутреннем уровне в качестве ключа для поиска этой области.

Если вы обращаетесь к областям или создаете их из нескольких Android-компонентов, то вместо функции createScope рекомендуется использовать функцию getOrCreateScope. Из названий этих функций очевидно, что они делают.

Шаг 4

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

val sampleClass = stringQualifiedScope.get<SampleClass>(    qualifier = named("scopedName"))

scopedName и factoryName это квалификаторы, которые мы объявили внутри модуля Koin на шаге2.

Шаг 5

Чтобы избавиться от зависимостей, созданных посредством stringQualifiedScope, вчастности sampleclass, необходимо вызвать функцию close. Например, если вы хотите избавиться от созданных в этой области зависимостей при уничтожении активности, то нужно вызвать функцию close в рамках соответствующего метода onDestroy. Вот так:

override fun onDestroy() {    super.onDestroy()    stringQualifiedScope.close()}

Koin-Android

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

Для этого необходимо импортировать библиотеки Koin-Android. Добавьте следующие строки в узел dependencies файла build.gradle уровня приложения:

// Koin for Androidimplementation "org.koin:koin-android:$koin_version"// Koin Android Scope featuresimplementation "org.koin:koin-android-scope:$koin_version"

Модули Koin-Android

Теперь с целью сократить шаблонный код мы хотим, например, автоматически закрывать область в рамках метода onDestroy компонента Android. Это можно сделать путем привязки Koin к импорту зависимостей посредством lifecyclescope.

Для начала необходимо создать в модуле Koin область для зависимостей с компонентами Android. Как это сделать:

val androidModule = module {    scope<SampleActivity> {        scoped { SampleClass() }    }  }

scoping dependency with android activity

Затем нужно выполнить внедрение зависимости в активности при помощи lifecyclescope:

val sampleClass : SampleClass by lifecycleScope.inject()

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

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)    fun onDestroy() {    if (event == Lifecycle.Event.ON_DESTROY) {               scope.close()        }    }}

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

Дополнительные материалы

На этом все. Надеюсь, вы узнали что-то полезное для себя. Спасибо за внимание!


Подробнее о курсе "Android Developer. Professional". Записаться на открытый урок "Пишем Gradle plugin" можно здесь.

Подробнее..

Как сделать цветные тени в Android с градиентом и анимацией

28.11.2020 18:12:01 | Автор: admin

На презентации новых макбуков обратил внимание на картинку процессора:

Переливающиеся цветные тени на темном фоне, выглядит классно.

Вот дошли руки, решил попробовать нарисовать на андроиде так же. Вот что получилось:

Сразу оговорюсь, что стандартным способом это сделать нельзя, до api 28 есть поддержка только черных elevation, после api 28 добавили поддержку цветных теней, но градиент сделать не получится. Поэтому мы будет рисовать drawable, устанавливать его в виде background и применять padding на целевой вьюхе, чтобы контент был внутри тени.

Напишем функцию создания Drawable с тенью:

/** * Создание drawable с градиентом-тенью */private fun createShadowDrawable(    @ColorInt colors: IntArray,    cornerRadius: Float,    elevation: Float,    centerX: Float,    centerY: Float): ShapeDrawable {    val shadowDrawable = ShapeDrawable()    // Устанавливаем черную тень по умолчанию    shadowDrawable.paint.setShadowLayer(        elevation, // размер тени        0f, // смещение тени по оси Х        0f, // по У        Color.BLACK // цвет тени    )    /**     * Применяем покраску градиентом     *     * @param centerX - Центр SweepGradient по оси Х. Берем центр вьюхи     * @param centerY - Центр по оси У     * @param colors - Цвета градиента. Последний цвет должен быть равен первому,     * иначе между ними не будет плавного перехода     * @param position - позиции смещения градиента одного цвета относительно другого от 0 до 1.     * В нашем случае null т.к. нам нужен равномерный градиент     */    shadowDrawable.paint.shader = SweepGradient(        centerX,        centerY,        colors,        null    )    // Делаем закугление углов    val outerRadius = FloatArray(8) { cornerRadius }    shadowDrawable.shape = RoundRectShape(outerRadius, null, null)    return shadowDrawable}

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

/** * Создание цветного drawable с закругленными углами * Это будет основной цвет нашего контейнера */private fun createColorDrawable(    @ColorInt backgroundColor: Int,    cornerRadius: Float) = GradientDrawable().apply {        setColor(backgroundColor)        setCornerRadius(cornerRadius)    }

Функция установки бэкграунда на вьюху-контейнер. У нас будет LayerDrawable с двумя слоями. 1 - тень, 2 - просто цвет с закругленными углами.

/** * Устанавливаем бэкграунд с тенью на вьюху, учитывая padding */private fun View.setColorShadowBackground(    shadowDrawable: ShapeDrawable,    colorDrawable: Drawable,    padding: Int) {    val drawable = LayerDrawable(arrayOf(shadowDrawable, colorDrawable))    drawable.setLayerInset(0, padding, padding, padding, padding)    drawable.setLayerInset(1, padding, padding, padding, padding)    setPadding(padding, padding, padding, padding)    background = drawable}

Применяем на вьюхе:

// ждем когда вьюха отрисуется чтобы узнать ее размерыtargetView.doOnNextLayout {    val colors = intArrayOf(        Color.WHITE,        Color.RED,        Color.WHITE    )    val cornerRadius = 16f.dp    val padding = 30.dp    val centerX = it.width.toFloat() / 2 - padding    val centerY = it.height.toFloat() / 2 - padding    val shadowDrawable = createShadowDrawable(        colors = colors,        cornerRadius = cornerRadius,        elevation = padding / 2f,        centerX = centerX,        centerY = centerY    )    val colorDrawable = createColorDrawable(        backgroundColor = Color.DKGRAY,        cornerRadius = cornerRadius    )    it.setColorShadowBackground(        shadowDrawable = shadowDrawable,        colorDrawable = colorDrawable,        padding = 30.dp    )}

Теперь проанимируем изменение с одного набора цветов на другие. Зациклим.

/** * Анимация drawable-градиента */private fun animateShadow(    shapeDrawable: ShapeDrawable,    @ColorInt startColors: IntArray,    @ColorInt endColors: IntArray,    duration: Long,    centerX: Float,    centerY: Float) {    /**     * Меняем значение с 0f до 1f для применения плавного изменения     * цвета с помощью [ColorUtils.blendARGB]     */    ValueAnimator.ofFloat(0f, 1f).apply {        // Задержка перерисовки тени. Грубо говоря, фпс анимации        val invalidateDelay = 100        var deltaTime = System.currentTimeMillis()        // Новый массив со смешанными цветами        val mixedColors = IntArray(startColors.size)        addUpdateListener { animation ->            if (System.currentTimeMillis() - deltaTime > invalidateDelay) {                val animatedFraction = animation.animatedValue as Float                deltaTime = System.currentTimeMillis()                // Смешиваем цвета                for (i in 0..mixedColors.lastIndex) {                    mixedColors[i] = ColorUtils.blendARGB(startColors[i], endColors[i], animatedFraction)                }                // Устанавливаем новую тень                shapeDrawable.paint.shader = SweepGradient(                    centerX,                    centerY,                    mixedColors,                    null                )                shapeDrawable.invalidateSelf()            }        }        repeatMode = ValueAnimator.REVERSE        repeatCount = Animation.INFINITE        setDuration(duration)        start()    }}

Применим:

// Второй массив с цветами. Размер массивов должен быть одинаковый.val endColors = intArrayOf(Color.RED,Color.WHITE,Color.RED)animateShadow(shapeDrawable = shadowDrawable,  startColors = colors,  endColors = endColors,  duration = 2000,  centerX = centerX,  centerY = centerY)

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

Подробнее..

Перевод Меняем стандартный диалог сбоя приложения в Android на собственный экран

01.12.2020 18:05:13 | Автор: admin

Как вместо стандартного диалогового окна о сбое использовать собственный экран с сообщением об ошибке

Будущих учащихся на курсе "Android Developer. Basic" приглашаем посетить открытый вебинар на тему "Приложение под Android на Kotlin за 1,5 часа"


А пока делимся традиционным полезным переводом.


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

Живи так, как будто ты умрешь завтра. Учись так, как будто ты будешь жить вечно.

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

Давайте разберемся, как это сделать.

Что нам потребуется?

  1. Kotlin

  2. Android Studio

  3. Та самая библиотека.

После создания нового проекта добавьте в файл build.gradle(:app) следующую зависимость:

implementation 'cat.ereza:customactivityoncrash:2.3.0'

Создадим макет собственного экрана сбоя. Например, такой:

Макет собственного экрана сбояМакет собственного экрана сбоя

Эту картинку, изображающую сбой, я скачал отсюда.

<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android"    xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto"    xmlns:tools="http://personeltest.ru/away/schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent">    <com.google.android.material.textview.MaterialTextView        android:id="@+id/errorText"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true"        android:layout_marginTop="30dp"        android:fontFamily="sans-serif"        android:text="@string/ouch_something_went_wrong"        android:textAppearance="@style/TextAppearance.AppCompat.Large"        android:textStyle="bold" />    <androidx.appcompat.widget.AppCompatImageView        android:layout_width="match_parent"        android:layout_height="match_parent"        android:src="@drawable/ic_error_img" />    <com.google.android.material.button.MaterialButton        android:id="@+id/restartApp"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true"        android:layout_centerHorizontal="true"        android:layout_marginBottom="50dp"        android:text="Restart App"        android:textAllCaps="false" /></RelativeLayout>

Как стать успешным разработчиком для Android

Следуйте этому плану, если хотите стать профессиональным разработчиком для Android

medium.com

Давайте добавим код, который отслеживает событие сбоя и отображает собственный экран сбоя вместо стандартного диалогового окна Android.

package com.example.customerrorscreenimport android.os.Bundleimport android.view.Viewimport androidx.appcompat.app.AppCompatActivityimport cat.ereza.customactivityoncrash.CustomActivityOnCrashimport kotlinx.android.synthetic.main.activity_error.*class ErrorActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_error)        /** you can log the reason of crash by adding this line of code         *  CustomActivityOnCrash.getStackTraceFromIntent(intent)         */        //getting crashing intent        val config = CustomActivityOnCrash.getConfigFromIntent(intent)        /**         * If config is null or not getting an intent simply finish the app         */        if (config == null) {            finish()            return        }                if (config.isShowRestartButton && config.restartActivityClass != null) {            restartApp.text = "Restart App"            restartApp.setOnClickListener {                CustomActivityOnCrash.restartApplication(                    this,                    config                )            }        } else {            restartApp.text = "Close App"            restartApp.setOnClickListener {                CustomActivityOnCrash.closeApplication(                    this,                    config                )            }        }    }}

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

package com.example.customerrorscreenimport android.app.Applicationimport cat.ereza.customactivityoncrash.config.CaocConfig/** * Created by  Mustufa on 04/11/2020. * Email : mustufaayub82@gmail.com */class MyApp : Application() {    override fun onCreate() {        super.onCreate()        CaocConfig.Builder.create()            .trackActivities(true)            .errorActivity(ErrorActivity::class.java)            .apply()    }}

Добавим этот класс в файл AndroidManifest.xml, используя атрибут name.

AndroidManifest.xmlAndroidManifest.xml

Теперь перейдем к файлу MainActivity.kt и поэкспериментируем с нашей новой возможностью. Мы должны вызвать сбой в приложении, чтобы увидеть наш экран сбоя. Я добавлю код, вызывающий сбой приложения, в метод onCreate.

var array = mutableListOf<String>()array[0] = "Hello"findViewById<TextView>(R.id.textView).text = array[1]

При выполнении этого кода будет сгенерировано исключение IndexOutOfBoundException и в приложении возникнет сбой.

Запустим приложение.

Исходный код можно скачать отсюда.


Узнать подробнее о курсе "Android Developer. Basic".

Записаться на открытый урок на тему "Приложение под Android на Kotlin за 1,5 часа".

Подробнее..

DIP vs IoC vs DI в мире Android

17.11.2020 20:16:29 | Автор: admin
Недавно меня спросили на собеседовании: В чём разница между Dependency injection и Dependency inversion principle из SOLID. Я знал определение каждого из них, но провести четкую границу между этими понятиями у меня не получилось. В этой статье я хочу кратко описать различие между понятиями Dependency inversion principle (DIP), Inversion of control (IoC) и Dependency injection (DI) на примере чистой архитектуры и Android фреймворка. Итак, поехали.

Dependency inversion principle


Определение
A. Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.
B. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

модуль логически взаимосвязанная совокупность функциональных элементов

Чтобы не было недопонимания, рассмотрим определения на примере. Допустим, нам надо получить список прочитанных книг и вывести его пользователю. Для этого будем использовать классы BooksInteractor и BooksRepository. Каждый из этих классов положим в свой модуль BI и BR соответственно. Модуль класса BooksInteractor зависит от класса BooksRepository.



BooksInteractory нужно получить список книг из BooksRepository. Интерактору совершенно не важно, как репозиторий получит эти данные, а значит в данном случае BooksInteractor является абстракцией для BooksRepository. Согласно второму определению BooksInteractor (абстракиция) НЕ должен зависеть от BooksRepository (реализация). А вот репозиторий наоборот должен знать о модуле BI. Что ж это получается: интерактор должен лежать внутри репозитория? Нет, чтобы инвертировать зависимости, нам достаточно покрыть BooksRepository интерфейсом IBooksRepository, а этот интерфейс положить в модуль класса BooksInteractor. Теперь вернемся к 1-ому определению DIP и взглянем на диаграмму.



Смотрите-ка, модули верхних уровней (модуль BI) не зависят от модулей нижних уровней (модуль BR). А оба модуля зависят от абстракции (от интерфейса IBooksRepository). Если вы уловили магию инвертирования зависимости с помощью покрытия репозитория интерфейсом, вы поняли принцип инверсии зависимостей. Поздравляю! Самое сложное вы поняли. Подробнее почитать про DIP можете в этой статье на хабре.

Inversion of Control


Мы изучили понятие принцип инверсии зависимостей. Теперь перейдем к ещё одной инверсии инверсии контроля. Само понятие очень обширно и в программировании может означать одно из трех:
1) Инверсия интерфейсов делегирование взаимоотношения между модулями посреднику-интерфейсу. Где это применяется? Например, в DIP, который мы изучили ранее.
2) Инверсия создания делегирование создания и связывания зависимостей посреднику (фабрики, DI/IOC контейнеры)
3) Инверсия процесса делегирование контроля над своей программой фреймворку, который сам управляет последовательностью вызовов функций. Первое, что приходит на ум в качестве примера, Android фреймворк. Ты расширяешь класс Activity и Fragment, отдавая контроль над их жизненным циклом фреймворку.

Dependency Injection


Dependency injection (внедрение зависимостей) это механизм передачи классу его зависимостей. С этим вы всегда встречаетесь, когда нужно передать зависимость в другой класс. Существует несколько способов внедрения зависимостей: через конструктор (Constructor Injection), через метод (Method Injection) и через свойство (Property Injection). Каждый из этих методов используется для своих целей. Но тут важно понять, что Dependency Injection это всего лишь передача зависимости в конструктор, метод или свойство

Рассматриваем edge cases


  • Может ли быть DI без IoC и DIP? Да, может. Создаем экземпляр конкретного класса A и передаем его в объект класса B через конструктор, метод или свойство.
  • Может ли быть DIP без IoC? Нет, DIP является одним из способов реализации Инверсии интерфейсов в IoC.
  • Может ли быть DIP без DI? Да, мы можем завязать нижние модули на интерфейс. Классы верхнего модуля будут работать с интерфейсом-абстракцией, но конкретная реализация класса нижнего модуля будет создаваться в конструкторе верхнего модуля.


Узнать отличия DIP, DI и IoC от других авторов можно тут и тут.

Буду рад вашим комментариям и отзывам!
Подробнее..

Переходим В OFFLINE FIRST с использованием Core Data и Managed Document(s)

19.11.2020 12:06:35 | Автор: admin
Придя в компанию Мегафон как iOS-разработчик, Валентин Чернов попал в сегодняшний основной тренд переход приложений в оффлайн. Основная работа Валентина разработка главного приложения Мегафона, мобильного личного кабинета. Это в нем вы можете смотреть баланс и вообще управлять им, подключать/отключать услуги и сервисы, смотреть сторисы, участвовать в конкурсах и видеть персональные предложения от партнеров Мегафона.

Даже несмотря на то, что оно недавно получило две премии Рунета, с таким количеством сервисов приложение, конечно, будет тяжелым и весомым. И перевод его в оффлайн большая длинная задача. Сейчас Мегафон выбрал возможность работать при нестабильной связи как одну из важных точек роста. Хотя уже двадцать первый век, 4G-5G сети наступают по всем фронтам, тем не менее для приложений федерального масштаба в России найдутся места, где связь временно отключается или пропадает надолго. И нужно, чтобы даже в этом случае приложение работало без сбоев.

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



Задача


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

Стек технологий


Помимо стандартной архитектуры MVC мы используем:

Swift + Objective-C


Большая часть кода (80% нашего проекта) написан на Objective-C. А уже новый код мы пишем на Swift.

Модульная архитектура


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

Submodules (библиотеки)


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

Core Data для локального хранения информации


При выборе для нас главным критерием были нативность и интеграция с iOS фреймворками. А эти преимущества Core Data стали решающими при его выборе:

  • Автосохранение стека и данных, которые получаем;
  • Удобная работа с моделью данных, достаточно удобная работа с графическим редактором для составления сущностей (и возможности их править, передавать новым разработчикам и т.д.)
  • Поддержка миграции и версионирования;
  • Ленивая загрузка объектов;
  • Работа в многопоточном режиме;
  • Отслеживание изменений;
  • Интеграция с UI (FRC);
  • Работа с запросами в БД на более высоком уровне (NSPredicates).

UIManaged document


У UI kit есть встроенный класс, называемый UIManagedDocument, и который является подклассом UIDocument. Его основное отличие при инициализации управляемого документа указывается URL-адрес для расположения документа в локальном или удаленном хранилище. Затем объект документа полностью создает стек Core Data прямо из коробки, который используется для доступа к постоянному хранилищу документа с использованием объектной модели (.xcdatamodeld) из основного пакета приложения. Это удобно и имеет смысл, даже несмотря на то, что мы живем уже в 21 веке:

  • UIDocument автосохраняет текущее состояние сам, с определенной частотой. Для особо критичных секций мы можем вручную вызывать сохранение.
  • Можно отслеживать состояния документа. Если документ открыт для работы или находится в каких-то конфликтных ситуациях например, мы осуществляем сохранение из разных точек, и где-то вдруг мы вызвали конфликт, мы можем это отследить, обработать, поправить и уведомить пользователя правильной понятной ошибкой.
  • UIDocument позволяет читать и записывать документ асинхронно.
  • Он может создать стек Core data из коробки.
  • Есть встроенная функция хранения в iCloud и синхронизации с облаком. Это как раз то, к чему мы в будущем стремимся.
  • Поддержка версионности.
  • Используется Document based app парадигма представление модели данных как контейнер для хранения этих данных. Если посмотреть на классическую модель MVC в документации Apple, можем увидеть, что Core data создана как раз для того, чтобы управлять этой моделью и помогать нам на более высоком уровне абстракции работать с данными. На уровне модели работаем, подключая UIManagedDocument со всем созданным стеком. А сам документ рассматриваем как контейнер, который хранит Core data и все данные из кэша (от экранов, пользователей). Плюс это могут быть картинки, видео, тексты любая информация.
  • Мы же рассматриваем наше приложение, его запуск, авторизацию пользователей и все его данные как некий большой документ (файл), в котором хранится история нашего пользователя. И которую мы можем передавать, очищать и владеть ею на уровне приложения:



Процесс


Как мы проектировали архитектуру


Процесс проектирования у нас проходит в несколько этапов:

  1. Анализ технического задания.
  2. Отрисовка диаграммы UML-диаграмм. Мы используем в основном три типа UML-диаграмм: class diagram (диаграмма классов), flow chart (блок-схема), sequence diagram (диаграмма последовательностей). Это прямая обязанность senior-разрабочиков, но могут делать и разработчики с меньшим опытом. Это даже приветствуется, так как позволяет хорошо погрузиться в задачу и изучить все ее тонкости. Что помогает найти в ТЗ какие-то недоработки, а также структурировать всю информацию по задаче. И мы стараемся учитывать кросс-платформенность нашего приложения мы тесно работаем с Android-командой, рисуя одну схему на две платформы и стараясь использовать основные общепринятые паттерны проектирования от банды четырёх.
  3. Ревью архитектуры. Как правило, ревью и оценку проводит коллега из смежной команды.
  4. Реализация и тестирование на примере одного UI модуля.
  5. Масштабирование. Если тестирование проходит успешно, мы масштабируем архитектуру на все приложение.
  6. Рефакторинг. Чтобы проверить, не упустили ли мы что-нибудь.

Сейчас, после 5 месяцев разработки этого проекта, я могу показать весь наш процесс в три этапа: что было, как менялось и что получилось в результате.

Что было


Нашей отправной точкой была стандартная MVC архитектура это связанные между собой слои:

  • UI слой, полностью программно сверстанный с использованием Objective C;
  • Класс презентации (модель);
  • Сервисный слой, где мы работаем с сетью.

Activity indicator был расположен в том месте схемы, где мы держим пользователя в режиме ожидания он хочет быстрого результата, но вынужден смотреть на какие-то лоадеры, индикаторы и прочие сигналы. Это было нашим слабым местом в user experience:



Переходный период


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

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

В правой части схемы мы разбили получение данных на команды паттерн Command нацелен выполнить какую-то базовую команду и получить результат. В случае iOS мы используем наследников NSOperation:



Каждая команда, которую вы здесь видите это операция, в которой есть логическая единица ожидаемого действия. Это получение данных из БД (или сети) и сохранение этих данных в Core data. Или, например, главная задача AcquireCommand это не только вернуть фасаду источник данных, но и дать нам возможность разрабатывать код таким образом, чтобы получать данные через фасад. То есть взаимодействие с операциями идет через данный фасад.

А основная задача операций передать данные DataSource для DataSourceFacade. Конечно, мы выстраиваем логику так, чтобы как можно быстрее показать данные пользователю. Как правило, внутри DataSourceFacade у нас есть операционная очередь, где мы запускаем наши NSOperations. В зависимости от настроенных условий мы можем принять решение, когда показывать данные из кэша, а когда получать из сети. При первом запросе источника данных в фасаде мы идем в БД Core data, достаем оттуда через FetchCommand данные (если они там есть) и моментально возвращаем их пользователю.

Одновременно запускаем параллельный запрос данных через сеть, и когда этот запрос выполняется, то результат приходит в базу данных, сохраняется в ней, и после мы получаем update нашего DataSource. Этот update попадает уже в UI. Так мы минимизируем время ожидания данных, а пользователь, получая их мгновенно, не замечает разницы. А обновленные данные он получит сразу, как база данных получит ответ от сети.

Как стало


К такой более лаконичной схеме мы идем (и придем в итоге):



Сейчас из этого у нас есть:

  • UI слой,
  • фасад, через который мы предоставляем наш DataSource,
  • команда, которая этот DataSource вместе с updates возвращает.

Что такое DataSource и почему мы о нем так много говорим


DataSource это объект, который предоставляет данные для слоя презентации и соответствует заранее определенному протоколу. А протокол должен быть подстроен под наш UI и предоставлять данные для нашего UI (неважно, для конкретного экрана или для группы экранов).

У DataSource, как правило, две основных обязанности:

  1. Предоставление данных для отображения в UI слое;
  2. Уведомление UI слоя об изменениях в данных и досылка необходимой пачки изменений для экрана, когда мы получаем уже обновление.

Мы у себя используем несколько вариантов DataSource, потому что у нас много Objective C legacy кода то есть, мы не везде можем легко наш Swiftовый DataSource воткнуть. Еще мы пока не везде используем коллекции, но в будущем перепишем код именно для использования CollectionView экранов.

Пример одного из наших DataSource:



Это DataSource для коллекции (он так и называется CollectionDataSource) и это достаточно несложный класс с точки зрения интерфейса. Он принимает в себя коллекцию, настроенный fetchedResultsController и CellDequeueBlock. Где CellDequeueBlock type alias, в котором мы описываем стратегию по созданию ячеек.

То есть мы создали DataSource и присвоили его коллекции, вызвав у fetchedResultsController performFetch, и дальше вся магия возложена на взаимодействие нашего класса DataSource, fetchedResultsController и возможность у делегата получать обновления из базы данных:



FetchedResultsController сердце нашего DataSource. В документации Apple вы найдете много информации по работе с ним. Как правило, мы получаем все данные с его помощью и новые данные, и данные, которые были обновлены или удалены. При этом мы параллельно запрашиваем данные из сети. Как только данные были получены и сохранились в БД, мы получили update у DataSource, и update пришел к нам в UI. То есть одним запросом мы и получаем данные, и показываем их в разных местах классно, удобно, нативно!

И везде, где можно использовать уже готовые DataSource с таблицами или с коллекциями, мы это делаем:



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

Как только мы будем готовы перевести экран полностью в Swift-реализацию, достаточно будет убрать Objective C-обертку и можно работать напрямую со Swiftовым протоколом, благодаря кастомному DataSource.

Сейчас мы используем три основных варианта DataSources:

  • TableViewDatasource + cell strategy (стратегия по созданию ячеек);
    CollectionViewDatasource + cell strategy (вариант с коллекциями);
    CustomDataSource кастомный вариант, его мы сейчас используем больше всего.

    Результаты


    После всех шагов по проектированию, реализации и взаимодействию с legacy кодом бизнес получил:

    • Существенно повысилась скорость доставки данных до пользователя за счет кэширования это, наверное, очевидный и логичный результат.
    • Мы теперь на шаг ближе к парадигме offline first.
    • Настроили процессы архитектурного кроссплатформенного ревью внутри iOS & Android команд все причастные к этому проекту разработчики владеют информацией и легко обмениваются опытом между командами.
    • Как бонус получили хорошую документацию к проекту за счет схем и описаний. Мы ее показываем нашим новым разработчикам, чтобы им было проще понять, как у нас проброшен мостик между legacy и новым кодом, и как работает сам процесс кэширования.
    • Мы уложились в сжатые спортивные сроки, и это на живом проекте. У нас получилось, условно говоря, провести ремонт, никого не выселяя из офиса, но все продолжали работать, и даже не дышали строительной пылью.

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

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

    Ссылки


    • Document-based programming guide. Это довольно старый документ, Apple его уже не рекомендует использовать. Но я бы порекомендовал посмотреть хотя бы для дополнительного развития. Там очень много полезной информации.
    • Document-based WWDC: первый и второй
    • DataSources
Подробнее..

Я месяц провел в MIT и понял даже софтверным инженерам не стоит забывать про паяльник

24.11.2020 18:14:36 | Автор: admin


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

Что ты ожидаешь от одного из самых крутых университетов мира? Выматывающие лекции и бесконечные формулы на белесых от мела досках? Конечно. Умудренная семи пядей во лбу профессура со всего мира? Обязательно. Но престижный Massachusetts Institute of Technology знаменит не только этим. MIT ценят за близость к индустрии. Буквально в нескольких кварталах от университетских корпусов в Бостоне расположены офисы техногигантов, таких как Google и Microsoft. Студентов учат не только инженерному делу, но и практическому его применению, а также умению хорошо продать свою идею, или найти человека, который продвинет твою инновацию на рынок.

Знаменитый Гарвард находится в двух станциях метро от MIT. Умники гарвардские выпускники, разбирающиеся в бизнесе, просто приходят к тру инженерам и создают новые компании. Это место генерирует новые стартапы каждый день. И я совсем не удивился, когда услышал очередное задание от преподавателя воркшопа. Каждому из нас надо было во что бы то ни стало получить скидку в местных магазинах. Один парень так старался на кассе в сетевом Best buy, что продавщица черкнула ему свой номерок и имя в чеке.

Что ж, это тоже успех.

Санкт-Петербург, весна 2013 года за моими плечами ИТМО и несколько разрабовских работ.


К тому времени совсем молодой российский Сколтех (Сколковский институт науки и технологий) набирает студентов в магистратуру. Обучение на английском, все в лучших традициях MIT, и более того, Сколтех и создан при поддержке Массачусетского технологического института. На должность ректора Сколтеха приглашен профессор MIT Эдвард Кроули. Программу магистратуры предваряет поездка в США месячный Innovation Workshop. Не раздумывая я подаю документы и принимаюсь готовиться к вступительным испытаниям.

Процесс поступления я запомнил следующим образом: собираешь и отправляешь документы, тебя приглашают в Москву, где проводят трехдневный хакатон и собеседования. Там же сдаешь TOEFL. Все получилось так быстро, что американской визой я занимался уже в Питере. Отпечатки пальцев, несколько вопросов за жизнь, подтверждающие документы от Сколтеха и выписка со счета с кругленькой суммой. Зачисление в магистратуру и одобрение визы я не знаю, чему я больше радовался!

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

Перелет в Бостон


занял порядка 10-12 часов. Нас встретили, посадили в типично американские огромные черные джипы и отвезли в общежитие, где нас ждала пицца. Боже, о чем еще может мечтать человек после долгого перелета?

Для изучения города нам устроили scavenger hunt по Бостону. С помощью подсказок мы искали специальные монеты, которые были раскиданы по улицам. Я думаю, это выглядело забавно: группки русских и не только студентов носятся по бостонским закоулкам, ищут монеты и донимают местных бесконечными вопросами. Но нам было весело! В выходные занятий не было, за несколько уикендов мы успели изучить окрестности, покататься на яхтах, выехать на океан и даже смотаться в Нью-Йорк.

Рядом с MIT и Гарвардом есть магазины, где можно купить футболки, кружки и другие вещи с символикой университетов. И все это не для туристов. Студенты искренне гордятся тем, где они учатся. На футболках то и дело мелькают надписи Nerd pride. Бостонцы знают и уважают людей из MIT. В один из вечеров я пробрался на концерт в Бостоне.

На пути обратно я встретил много людей, которые махали мне и кричали: MIT rules и engineers forever.


После такого тебя просто переполняет мотивация учиться дальше!



В будни каждый день был расписан по часам. Мы начинали в 9 утра и заканчивали ближе к семи вечера. Нередко, чтобы успеть всё, преподаватели продолжали занятия за обедом, на лужайке или в кафе. Однажды мы немного устали и закапризничали. Кто-то бурчал мол либо ужин, либо учеба. Тогда кто-то предложил: Давайте совмещать, но только если будет пюрешка.. Лекции во время еды не нравились, но с пюрешкой так уж и быть были согласны. Все горячо поддержали эту идею. И вы знаете, преподаватель на следующий день выкатил нам тазик с пюрешкой! Ох, уж эти странные русские, черт с вами, ешьте свою mashed potato, рассмеялся он, и продолжил занятие. Мы были в восторге.

И в этом весь дух отношений между студентами и преподавателями. Это история про уважение и страсть. Все вокруг пашут, фигачат, и ты тоже начинаешь ускоряться. Когда приходишь к человеку, который помогает тебе составить твой индивидуальный план обучения в MIT и говоришь: Я хочу вот эти курсы и еще вот эти. Он смотрит на тебя с облегчением и выдает: Наконец-то человек, который знает, чего хочет.

В погружении в процесс помогают и стены. Все аудитории доступны, нет никаких охранников. По всей территории MIT прекрасный бесплатный вай-фай. (Честно, я сначала подумал, что во всей Америке так. Каково было мое огорчение, когда я вышел в Бостон!).

Вот неполный список того, что мы изучали за 3 недели:


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



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

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

Там я снова взял в руки паяльник и увидел вживую всякие роборуки


На одном из занятий Innovation Workshop преподаватель вручил мне паяльник и сказал что-то в духе: Паяй, сынок. Один преподаватель с помощью света передавал звук. Есть проблема с передачей информации со спутников на землю. Провод не бросить, увы. А что если можно пустить луч, огромная штука его примет и все будет работать! Примерно так он объяснил следующие действия: Смотрите, что могу! Он дает что-то типа приемника одному из нас, а сам берет небольшую указку, которая светит еле видимым светом. Наводит на приемник луч и мы слышим музыку Убирает музыка замолкает. Как ты это делаешь, маг?

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



После той поездки кто-то из ребят углубился в науку и учится на PhD в Англии, кто-то ушел в data science, а кто-то открыл свою компанию и запускает спутники в космос. Я свое вдохновение конвертировал в страсть: я детально изучал все что давали в Сколтехе, а что не давали отбирал и изучал. Лаборатория робототехники в Сколтехе сделана по образу и подобию соответствующей лаборатории в MIT. Я еще много провел в ней времени, получив доступ к квадракоптерам, 3d-принтерам, лазерным резакам и другому крутому оборудованию, которое я раньше не видел.

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

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

Перевод Рисование собственных представлений (View) в Android

01.12.2020 18:05:13 | Автор: admin

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

В преддверии старта курса "Android Developer. Professional" приглашаем всех желающих принять участие в открытом вебинаре на тему "Пишем gradle plugin".

А пока делимся переводом полезного материала.


Введение

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

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

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

Зачем создавать собственные представления?

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

  1. Производительность: в вашем макете много представлений и вы хотите оптимизировать их, нарисовав одно, более легкое собственное представление.

  2. Имеется сложная иерархия представлений, которую трудно использовать и поддерживать.

  3. Необходимо создать специализированное представление, требующее рисования вручную.

Общий подход

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

  1. Создать класс, расширяющий базовый класс или подкласс представления.

  2. Реализовать конструкторы, использующие атрибуты из XML-файла.

  3. Переопределить некоторые методы родительского класса (onDraw(), onMeasure() и т.д.) в соответствии с нашими требованиями.

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

Пример

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

Шаг 1. Создадим класс с именем CircularTextView.

Шаг 2. Расширим класс виджета TextView. Здесь под TextView в IDE выдается ошибка, в которой сообщается, что у этого типа есть конструктор и он должен быть инициализирован.

Шаг 3. Добавим конструкторы в класс.

Это можно сделать двумя способами.

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

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

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

View(Context context)

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

View(Context context, @Nullable AttributeSet attrs)

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

Шаг 4. Самый важный шаг в отрисовке собственного представления это переопределение метода onDraw() и реализация необходимой логики отрисовки внутри этого метода.

Метод OnDraw(canvas: Canvas?) имеет параметр Canvas (холст), с помощью которого компонент представления может отрисовывать себя. Для рисования на холсте необходимо создать объект Paint.

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

  • что рисовать (определяется объектом Canvas);

  • как рисовать (определяется объектом Paint).

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

Давайте займемся кодом. Мы создаем объект Paint и присваиваем ему некоторые свойства, а затем рисуем фигуру на холсте (объект Canvas), используя наш объект Paint. Метод onDraw() будет выглядеть так:

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

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

Шаг 5. Мы закончили с рисованием. Теперь давайте внесем этот класс представления в XML.

Добавьте этот XML-макет в вашу активность (Activity) и запустите приложение. Вот что будет на экране.

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

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

fun setSolidColor(color: String) {    solidColor = Color.parseColor(color)    circlePaint?.color = solidColor}

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

circularTextView.setSolidColor("#FF0000")

Неплохо, правда? Теперь давайте добавим контур к кружку. Контур будет задаваться двумя входными параметрами: шириной линии контура и ее цветом. Чтобы задать цвет линии контура, нам нужно создать объект Paint точно так же, как мы это делали для кружка. Чтобы задать ширину линии контура, мы создадим переменную, установим для нее нужное значение и используем его в методе onDraw(). Полный код будет выглядеть так:

Теперь в активности можно динамически настраивать эти атрибуты нужным образом.

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

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

Для начала создадим файл с именем attrs.xml в папке values. Этот файл будет содержать все атрибуты для различных представлений, которые мы создаем сами. В приведенном ниже примере у нашего представления под названием CircularTextView имеется атрибут ct_circle_fill_color, который принимает значение цвета. Аналогичным образом мы можем добавить и другие атрибуты.

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

val typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircularTextView)circlePaint?.color = typedArray.getColor(R.styleable.CircularTextView_ct_circle_fill_color,Color.BLUE)typedArray.recycle()

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

app:ct_circle_fill_color="@color/green"

В моем случае результат был таким:

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

Обновление представления

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

invalidate()

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

requestLayout()

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

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

Благодарю за внимание!


- Узнать подробнее о курсе "Android Developer. Professional".


- Записаться на открытый вебинар "Пишем gradle plugin".

Подробнее..

Разработка приложения с использованием Python и OpenCV на Android устройстве

18.11.2020 22:19:29 | Автор: admin

В это статье я хочу показать пример того, как андроид устройство можно использовать для разработки на таких языках программирования как python с библиотекой opencv в среде VSCode (будет использован code-server). В конце статьи приведено небольшое сравнение производительности Termux на моем Android устройстве и Raspberry Pi 3B.

Все действия описанные статье выполнялись на:
Huawei MediaPad M5 10.8
4GB ОЗУ, Hisilicon Kirin 960s, EMUI 9, без root

Для начала понадобится установить Termux (эмулятор терминала, предоставляющий возможности среды Linux), о придожении уже писали на habr.

Далее установим необходимые пакеты, а так же, для более быстрой настройки в дальнейшем, установим ssh сервер:
$ pkg update -y pkg install curl openssh autossh termux-services screen$ sv-enable sshd$ sv up sshd

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

По умолчанию openssh прослушивает порт 8022, узнать ip адрес устройства можно с помощью команды ifconfig:
$ ifconfigwlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500         inet 192.168.1.88  netmask 255.255.255.0  broadcast 192.168.1.255

Подключаемся к Termux:
$ ssh u0_a137@192.168.1.88 -p 8022 

Установить opencv-python в собственном окружении Termux мне не удалось, поэтому воспользуемся трудами Andronix и запустим в Termux Ubuntu 18.04.
$ curl https://raw.githubusercontent.com/AndronixApp/AndronixOrigin/master/Installer/Ubuntu/ubuntu.sh | bash

В официальном приложением Andronix можно найти команды для установки других дистрибутивов таких как Kali, Manjaro и т.д.
Если все выполнилось успешно на экране появится
You can now launch Ubuntu with the ./start-ubuntu.sh script

Запускаем Ubuntu
$ ./start-ubuntu.sh 

Установим пакеты необходимые для разработки на python3 с использованием opencv:
$ apt update$ apt install curl git net-tools unzip yarn nano nodejs python3-dev python3-pip python3-opencv -y

Установка занимает довольно много времени.
Теперь установим code-server
$ curl -fsSL https://code-server.dev/install.sh | sh

После установки code-server необходимо отредактировать файл конфигурации
$ nano ~/.config/code-server/config.yamlbind-addr: 127.0.0.1:8080auth: passwordpassword: 4a40bd9973dae545b3b4c037cert: false

По умолчанию code-server прослушивает адрес 127.0.0.1:8080, для обращения к code-server с других устройств необходимо поменять bind-addr на 0.0.0.0:8080. Присутствует возможность авторизации по паролю. Для задания пароля необходимо изменить значение password. Для отключения авторизации необходимо указать auth: none.

Все действия в статье выполняются в Ubuntu без графического интерфейса в результате чего нет возможности использовать imshow, но есть способ транслировать видео в браузер используя библиотеку flask и специальный MIME тип multipart.

Чтобы не отвлекаться на написание кода, используемого в статье для примера, его можно взять в моем github репозитории.
$ git clone https://github.com/guinmoon/flask_opencv_sample$ cd flask_opencv_sample && pip3 install flask

Открываем проект в code-server

При первом открытии будет предложено установить расширение Python соглашаемся.
Установим приложение для потоковой передачи с камеры устройства, например IP Webcam. Главное требование к приложению возможность транслировать с камеры поток понятный opencv, например rtsp. Разрешение видео потока настраиваем в зависимости от производительности устройства. На моем самое оптимальное 1280x720.


Запускаем трансляцию видео в IP Webcam и проект:


В заключение хочу отметить, что при наличии современного Android устройства его можно использовать как альтернативу raspberry pi. Так, например, сняв ограничения энергопотребления домашний планшет можно использовать как полноценный arm64 мини пк, работающий в фоне постоянно. При этом производительность у Termux вполне высокая.
Сравнение запуска того же кода на Raspberry pi 3
Разрешение видео 1280x720


Далее несколько дополнений которые не вошли в основную статью

Память устройства
Для того чтобы иметь возможность обмениваться файлами между termux и android необходимо в выполнить команду
$ termux-setup-storage

Теперь локальная память устройства примонтирована в ~/storage

.Net Core
Присутствует возможность компилировать приложения .net-core, но к сожалению без возможности отладки так как нет версии OmniSharp скомпилированной под arm.
в start_ubuntu.sh
ищем строчку
command+=" -b /data/data/com.termux/files/home:/root"
и исправляем ее на
command+=" -b /data/data/com.termux/files/home"
curl -SL -o dotnet-sdk-3.1.403-linux-arm64.tar.gz https://download.visualstudio.microsoft.com/download/pr/7a027d45-b442-4cc5-91e5-e5ea210ffc75/68c891aaae18468a25803ff7c105cf18/dotnet-sdk-3.1.403-linux-arm64.tar

если ссылка не работает то руками качаем от сюда

mkdir -p /usr/share/dotnettar -zxf dotnet-sdk-3.1.403-linux-arm64.tar.gz -C /usr/share/dotnetln -s /usr/share/dotnet/dotnet /usr/bin/dotnetdotnet new console -o appcd appdotnet run


Тестируем производительность с помощью sysbench
В sourses.list добавляем
deb ftp.debian.org/debian buster-backports main
apt-get updateapt install sysbenchsysbench --test=cpu --cpu-max-prime=20000 --num-threads=4 run

Подробнее..

Разработка мобильных приложений на Python. Создание анимаций в Kivy. Part 2

23.11.2020 08:20:36 | Автор: admin

Приветствую всех любителей и знатоков языка программирования Python!

Сегодня продолжим разбираться с темой анимаций в кроссплатформенном фреймворке для с поддержкой мультитач Kivy в связке с библиотекой компонентов Google Material Design KivyMD. В прошлой статье мы уже разбирали пример тестового приложения на Python/Kivy/KivyMD, в этой пройдемся по теме анимаций более подробно. В конце статьи я приведу ссылку на репозиторий проекта, в котором вы сможете скачать и сами пощупать, демонстрационное Kivy/KivyMD приложение. Как и предыдущая, эта статья будет содержать не маленькое количество GIF анимаций и видео, а поэтому наливайте кофе и погнали!

Kivy работает на Linux, Windows, OS X, Android, iOS и Raspberry Pi. Вы можете запустить один и тот же код на всех поддерживаемых платформах без внесения дополнительных изменений в кодовую базу. Kivy поддерживает большое количество устройств ввода, включая WM_Touch, WM_Pen, Mac OS X Trackpad и Magic Mouse, Mtdev, Linux Kernel HID, TUIO и так же как и Flutter, не задействует нативные элементы управления. Все его виджеты настраиваются. Это значит, что приложения Kivy будут выглядеть одинаково на всех платформах. Но благодаря тому, что виджеты Kivy могут быть кастомизированы как угодно, вы можете создавать свои собственные виджеты. Например, так появилась библиотека KivyMD. Прежде чем продолжить, давайте посмотрим небольшой обзор возможностей Kivy:

Демонстрационные ролики Kivy приложений






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

from kivy.animation import Animationfrom kivy.lang import Builderfrom kivymd.app import MDAppKV = """<CommonLabel@MDLabel>    opacity: 0    adaptive_height: True    halign: "center"    y: -self.heightMDScreen:    on_touch_down: app.start_animation()    CommonLabel:        id: lbl_1        font_size: "32sp"        text: "M A R S"    CommonLabel:        id: lbl_2        font_size: "12sp"        text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit""""class TestAnimation(MDApp):    def build(self):        return Builder.load_string(KV)    def start_animation(self):        lbl_1 = self.root.ids.lbl_1        lbl_2 = self.root.ids.lbl_2        Animation(            opacity=1, y=lbl_1.height * 2, d=0.9, t="in_out_back"        ).start(lbl_1)        Animation(            opacity=1, y=lbl_2.height + ids.lbl_1.height, d=1, t="in_out_back"        ).start(lbl_2)TestAnimation().run()

Это уже готовое приложение. Мы будем его лишь слегка редактировать. Правило CommonLabel в KV строке аналогично созданию класса в Python коде. Сравните:


Код в Kivy Language всегда короче и читабельнее. Поэтому в Python коде у нас будет только логика. Мы создали две метки с общими свойствами, описанными в правиле CommonLabel: прозрачность (opacity), размер текстуры метки (adaptive_height), горизонтальное выравнивание (halign), положение по оси Y (y ) и дали этим меткам id-шники (lbl_1, lbl_2), чтобы иметь возможность обращаться к свойствам объектов меток и манипулировать ими из Python кода. Далее мы привязали к событию on_touch_down (сработает при прикосновении к экрану в любом месте) вызов метода start_animation, в котором будем анимировать наши две метки.

Animation


Для анимарования объектов в Kivy используется класс Animation. Использовать его очень просто: при инициализации класса Animation вы должны передать в качестве аргументов имена свойств с целевыми значениями, которые будут достигнуты в конце анимации. Например:

    def start_animation(self):        # Получаем объекты меток из KV разметки        lbl_1 = self.root.ids.lbl_1        lbl_2 = self.root.ids.lbl_2        # Анимация первой метки        Animation(            opacity=1,  # анимация прозрачности до значения 1            y=lbl_1.height * 2,  # анимация положения виджета по оси Y            d=0.9,  # время выполнения анимация            t="in_out_back"  # тип анимации        ).start(lbl_1)  # в метод start передаем объект, который нужно анимаровать        # Анимация второй метки        Animation(            opacity=1, y=lbl_2.height + lbl_1.height, d=1, t="in_out_back"        ).start(lbl_2)

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

  1. in_out_back
  2. out_elastic
  3. linear


Давайте немного усложним задачу и попробуем анимировать вращение меток на плоскости. Для этого будем использовать матричные манипуляции (PushMatrix, PopMatrix, Rotate, Translate, Scale). Добавим к общей метке инструкции canvas:

<CommonLabel@MDLabel>    angle: 180  # значение вращения    [...]    canvas.before:        PushMatrix        Rotate:            angle: self.angle            origin: self.center    canvas.after:        PopMatrix

А в Python коде в класс Animation передадим новое свойство angle для анимации:

    def start_animation(self):        lbl_1 = self.root.ids.lbl_1        lbl_2 = self.root.ids.lbl_2        Animation(angle=0, [...]).start(lbl_1)        Animation(angle=0, [...]).start(lbl_2)

Результат:

Добавим анимирование масштаба меток:

<CommonLabel@MDLabel>    scale: 5  # значение масшбирования    [...]    canvas.before:        PushMatrix        [...]        Scale:            # масштабирование по трем осям            x: self.scale            y: self.scale            z: self.scale            origin: self.center    canvas.after:        PopMatrix

В Python коде в класс Animation передадим новое свойство scale для анимации:

    def start_animation(self):        lbl_1 = self.root.ids.lbl_1        lbl_2 = self.root.ids.lbl_2        Animation(scale=1, [...]).start(lbl_1)        Animation(scale=1, [...]).start(lbl_2)

Результат:

Класс Animation имеет ряд событий для отслеживания процесса анимации: on_start, on_progress, on_complete. Рассмотрим последний. on_complete вызывается в момент завершения процесса анимации. Привяжем это событие к методу complete_animation, который мы сейчас создадим:

[...]class TestAnimation(MDApp):    [...]    def complete_animation(self, animation, animated_instance):        """        :type animation: <kivy.animation.Animation object>        :type animated_instance: <WeakProxy to <kivy.factory.CommonLabel object>>        """        # Анимируем масштаб и цвет первой метки.        Animation(scale=1.4, d=1, t="in_out_back").start(animated_instance)        Animation(color=(1, 0, 1, 1), d=1).start(animated_instance)

Привязываем событие:

    def start_animation(self):        [...]        animation = Animation(            angle=0, scale=1, opacity=1, y=lbl_1.height * 2, d=0.9, t="in_out_back"        )        animation.bind(on_complete=self.complete_animation)        animation.start(lbl_1)        [....]

Результат:

На этом пока все. Просили:

Ниже прикрепляю превью Kivy/KivyMD проекта и ссылку на репозиторий, где можно скачать APK и пощупать:

Репозиторий github.com/HeaTTheatR/Articles
APK можно найти в директории репозитория StarTest/bin
Подробнее..

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

22.11.2020 00:21:34 | Автор: admin

Всем здравствуйте!


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


Должен сразу оговориться, что больше рассказ коснётся KnownReader (он же KR), который мы разрабатываем, а также близкого семейства, построенного на одном движке (CoolReader, он же CR; KOReader). Но кое-какие ссылки и на другие приложения будут даны, если потребуется. Хотелось не столько поделиться собственными ноу-хау, сколько рассказать, как оно работает изнутри, с какими проблемами пришлось столкнуться и как они были решены, а заодно и познакомить читателя с интерфейсом и возможностями приложения. Потому что читалка это целая философия, без понимания которой можно не заметить целый океан возможностей, скрытых от взгляда обывателя.


Выделяем, переводим


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


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


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


Направление перевода
Направление перевода Перевод (Lingvo)

Про элементы управления


Должен сделать небольшое отступление о том, как устроены элементы управления в программах для чтения. Читалка вещь уникальная тем, что она должна быть похожа на книгу. Поэтому на всём своём пространстве она рисует текст. А если везде текст, как же ей управлять? И вот что обычно предлагают читалки:
Тап-зоны это зоны экрана (в KR их 9 3х3), на нажатие которых можно назначить действия. Очевидно, должны быть как минимум следующая/предыдущая страницы, но не только. Разнообразие действий очень велико, а потому тут каждый развлекается как может. Буквально. В настройках KR можно настроить тап-зоны под себя;


Тап зоны


Жесты. Всякие смахивания, проведения слева направо, сверху вниз и т.п.;
Панель с кнопками. Её не все любят, считая её визуальным мусором, но не все и не любят.


Про ноу-хау


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


  1. В качестве словаря в KR можно указать любую программу, которая может принимать в себя текст (хоть СМС отправляйте). Это удобно, если у вас есть какой-то словарь, про который KR не знает. Отмечу также, что и переключение словарей также можно вынести на тап-зону;
  2. Сделали несколько настроек словарей, между которыми можно быстро переключаться (режимы переключить на 2 словарь, переключить на 2 словарь на один следующий перевод, потом вернуться обратно);

Словари и режимы выделения
Настройки словарей Настройки различных видов выделения в разных режимах: режим чтения, выделения, инспектора

  1. Добавили уникальное действие длинный двойной тап. Это когда вы делаете двойной тап, но не отпускаете палец после второго тапа. На это уникальное действие можно настроить что душе угодно. Например, вызов другого словаря. Если вы знаете другую читалку, которая умеет длинный двойной тап, напишите об этом в комментариях или в личку. Мне правда интересно;
  2. К жестам (это не про словари, но все равно напишу) добавили возможность менять размер шрифта диагональным щипком и размеры полей горизонтальным и вертикальными щипками. Тут следует отметить, что аудитория читалки отреагировала хорошо. Но не вся аудитория. А потому без правок не обошлось. В следующем же релизе пришлось вынести в настройки возможность отключения управления жестами;
  3. Разнообразили панель с кнопками её теперь можно очень гибко настраивать: изменять список и функции кнопок, регулировать её размер (больше, меньше, средний) и цвет. Вообще, гибкость настраивания это про KR. Возможно, кого-то это может запутать поначалу, но было замечено, что к такой свободе люди быстро привыкают и неохотно от неё отказываются.

Несколько словарей


Одна из моих любимых тем.


Минутка заботы от автора материала

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


  1. Элемент управления или пункт меню в читалке;
  2. Приложение для отображения словарей (Abbyy, GoldenDict и т.д.);
  3. Конкретный словарь (например Толковый словарь Ушакова), отображаемый в пункте выше.
    Как правило, из контекста понятно о чём идет речь. Если непонятно, спрашивайте, отвечу.

Как правило, хватает двух словарей. Но в KR их больше. И вот что это даёт:


  1. Разнообразие. В одной программе могут быть одни словарные статьи, в другой другие. Где-то есть транскрипция, где-то нет. Какие-то умеют переводить фразы (Яндекс, Гугл, Промт), какие-то могут оперировать только словами.
  2. Словари языковые и толковые. Может быть интересно посмотреть как перевод слова, так и его толкование (википедия, энциклопедии и т.д.).

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


  • Lingvo. Очень достойное приложение от уважаемой фирмы ABBYY. Всё на уровне, даже с проговаривателем. Умеем показывать его в карточке (т.е. на часть экрана). Есть хороший такой минус это платно. Как сама программа, так и словари. Я покупал английский словарь за 20 долларов (а для России ещё и с НДС). Другой значимый минус в том, что нельзя повлиять на развитие. Например, я просил поддержку добавить кнопку скопировать словарную статью уж столько воды утекло, а её до сих пор не добавили.
  • GoldenDict. Очень хорошая вещь, практически не имеющая аналогов, у которой есть куча словарей. Их очень много. Также GoldenDict умеет найти словари из Lingvo и использовать их. Есть форк на форуме 4pda, который привносит некоторую функциональность и осовременивает интерфейс. От минусов совсем грустно становится развитие остановилось, исходников программы нет. Да и платный он, этот GoldenDict.
  • Aard 2. Совершенно уникальная вещь. Программа создана для обработки больших (даже не больших, а огромных) словарей. На сайте Aardа выкладываются дампы википедий. Представляете, оффлайновая википедия в телефоне? Ух...

Любопытный вариант использования Aard

Аард один раз лично мне очень помог в работе (в настоящей, за которую деньги платят). У него есть утилита Slob (на питоне), которая позволяет выполнять поиск словарных статей и выгружать их в html формат. Нам (моей команде на работе) нужно было обогатить информацию о населённых пунктах РФ данными из википедии (численность населения по годам и т.д.). Мы разработали свою утилиту, которая запускала Slob и давала ей на вход название населенного пункта (например Пермь). Далее находились все статьи (с околопермской тематикой), после чего мы пакетно обрабатывали файлы и легко вычисляли нужный (через jsoup) по наличию определённых атрибутов коды ОКАТО, ОКТМО и т.д., затем забирали из него нужные данные (страницы википедии хорошо шаблонизированы и структурированы). Так задача была решена в автоматическом режиме по всем населённым пунктам РФ.


  • Яндекс Переводчик / Гугл Переводчик. Это уже новое веяние, они в представлении не нуждаются. Работают, развиваются, очень хороши. Недавно, правда, Яндекс немного расстроили, переведя свой API переводчика на платные рельсы. Мы об этом писали на форуме и даже инструкцию оформили о том, как его теперь готовить для использования в KR. Поэтому здесь повторяться не буду. Если кому интересно станет, заходите, в нашей ветке на форуме уютно.
  • Если знаете другие хорошие словари добавляйте в комментарии, обсудим.

Пользовательский словарь


Немного многословно, но до сих пор мы с вами обсудили только базовую функциональность выделили слово, перевели слово (возможно, даже в нескольких словарях и с множеством хитрых настроек). Хорошо, но мало. Ведь есть нерешённая проблема. При чтении нам иногда встречаются трудные слова, которые мы переводим. Эти слова могут встречаться и на следующих страницах, а мозг устроен таким образом, что сразу слово можно и не запомнить. А потому одно и то же действие мы вынуждены повторить несколько раз. Чтобы этого не делать мы добавили то, что называем пользовательским словарём. Это наше нововведение и в какой-то мере отличительная особенность.
Сейчас расскажу, как оно работает. Мы завели небольшую панель под книгой, в которую помещаем слова из пользовательского словаря (до 10 слов), встретившиеся на текущей (читаемой) странице. Можно быстро нажать и подсмотреть перевод.


Онлайн-словарь
Перевод в онлайн-словаре Перевод в онлайн-словаре

Пользовательский словарь
Сохранённые слова в пользовательском словаре Быстрый просмотр слова из пользовательского словаря

Цитаты, история поиска
Диалог пользовательский словарь / цитаты / история поиска в словаре Диалог пользовательский словарь / цитаты / история поиска в словаре

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


  1. При выделении слова оно запоминается внутри KR как последнее выделенное слово.
  2. Дальше оно уходит во внешнюю программу-словарь.
  3. Мы в свою очередь прочитали, посмотрели, дальше ВДЕЛИЛИ словарную статью и СКОПИРОВАЛИ её в буфер обмена, затем вернулись в KR и нажали действие (мы же помним, что его можно назначить на тап-зону) создать элемент пользовательского словаря по последнему выделению и содержимому буфера обмена (знаем, что надо поработать над формулировкой, но как есть), после чего осталось только нажать OK. Можно даже без буфера есть возможность отправить текст в KR через всплывающее меню андроида функциональность PROCESS_TEXT.
    Что здесь сложно? Копирование словарной статьи. Это снова подержать, подвигать ползунки (не у нас, а в программе-словаре). Хорошо, мы упорные. Поговорили с коллегами из GoldenDict mod, сделали для них доработку добавили кнопку в GoldenDict, чтобы можно было хотя бы выделить словарную статью. Коллеги обещали нашу доработку включить в свой билд и выпустить. Ждём-с.

Онлайн словари


Пользовательский словарь это не только нижняя панелька. Это полноценное окно пользовательский словарь / цитаты / история поиска в словаре. С поиском и т.д. Вызывается также по действию, либо можно нажать на ссылку слов: Х.
Вроде бы и звучит хорошо, но мало. Мы живем в современном мире. Современный мир живет в онлайне. Поэтому тут мы добавили онлайновые словари Яндекс, Lingvo, Wikipedia (пока еще в ранней реализации).:


  1. Яндекс хорош тем, что переводит несколько слов (добавили действие перевести всю страницу). Лингво расширенными словарными статьями и транскрипциями. Википедия хороша сама по себе.
  2. Технически онлайновый словарь вызывается не выходя из KR, т.е. вызывается в собственном интерфейсе KR. Таким образом мы получили главную ценность у нас нет необходимости копировать перевод, вставлять в пользовательский словарь и т.д. Перевод в онлайновом словаре автоматически заносит слово в историю с переводом. И мы сразу видим его на следующих страницах. Профит.
  3. Минусы онлайновых словарей тоже очевидны зависимость от внешних сервисов с их ограничениями (которые могут меняться), а также необходимость быть онлайн.
    Ну, это, в целом, более чем приемлемо.

Режимы


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


  1. Режим выделения.
    Включается режим выделения (страница текста обводится сплошной рамочкой), в котором слово можно выделить в один тап, а несколько слов тапнули и провели. Что мы поменяли. В режиме выделения можно настроить другие словари на действия. Действий три: выделение слова, выделение нескольких слов, выделение слова по длинному двойному тапу. Это расширяет возможности использования выделения. Допустим в обычном режиме перевод в англо-русском словаре, в режиме выделения в википедии.
  2. Режим инспектора.
    Страница текста выделяется штрихпунктирной рамочкой. Режим выделения после перевода отключается. Режим инспектора это такой залипающий режим выделения, который включен, пока не выключен. Удобно, например, если хочется протыкать много слов на странице друг за другом и получить быстрый перевод по каждому. Также в режиме инспектора тоже можно настроить свои словари (на три варианта выделения).

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

Википедия
Поиск в Википедии список статей Википедия выбранная статья

Что дальше?


Много всего сказано, но хочется большего:


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

Отображение двух страниц


  • Пользовательский словарь. Сейчас найденные слова на странице отрисовываются на нижней панельке. Есть желание просто подсвечивать их в тексте в виде примерно как гиперссылка.
  • Лингво-онлайн умеет отдать wav-файл с озвучкой. У нас пока не реализовано, но можно сделать проговариватель.
  • Википедия-онлайн у нас выводится без форматирования. Можно развиваться в эту сторону.
    К чему это я? Да к тому, что приложение живёт, пока оно кому-нибудь нужно. Желательно, чтобы оно было нужно не только его автору и паре-тройке его друзей. Нет, это, конечно, тоже уже кое-что, но всегда хочется большего. Если вы посетите нашу ветку на 4pda, то, возможно, заметите, что у нас ведётся достаточно активное обсуждение новых фич и старых багов. У нас есть своя аудитория, мы ценим её мнение и дорожим ей. Но взгляд со стороны довольно часто бывает полезным. А потому приглашаю вас, хабровчане, к обсуждению.
Подробнее..

Перевод Студийные приложения Netflix на Android и iOS теперь с Kotlin Multiplatform

09.11.2020 20:17:29 | Автор: admin
Примечание от переводчика: при словах мобильные приложения Netflix вспоминаются их основные приложения для просмотра видеоконтента, но компания ведёт и киностудийную разработку для тех, кто этот контент создаёт. И их пост посвящён использованию Kotlin Multiplatform во втором случае.

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


Поскольку сетевое соединение часто оказывается ненадёжным, мы обратились к мобильным решениям для персистентности на клиентской стороне и поддержки офлайна. А потребность выпускать быстро привела к экспериментам с мультиплатформенной архитектурой. И теперь мы зашли тут ещё на шаг дальше, использовав Kotlin Multiplatform, чтобы писать платформонезависимую бизнес-логику один раз на Kotlin и компилировать её в Kotlin-библиотеку для Android и нативный Universal Framework для iOS с помощью Kotlin/Native.



Kotlin Multiplatform


Kotlin Multiplatform позволяет вам делать единую кодовую базу для бизнес-логики iOS- и Android-приложений. Вам требуется писать код для конкретной платформы только там, где это необходимо: например, для реализации нативного UI или при работе с платформоспецифичными API.

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


Этот подход хорошо работает для нас по нескольким причинам:


  1. У наших приложений для Android и iOS общая архитектура со схожей, а порой и идентичной бизнес-логикой на обеих платформах.
  2. Почти 50% нашего продакшн-кода в наших Android- и iOS-приложениях не связано с платформой.
  3. Это никак не мешает нам изучать новые технологии от самих этих платформ (Jetpack Compose, Swift UI и так далее).

Итак, что мы с этим делаем?


Управление опытом (experience management)


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


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


По своей сути Hendrix это простой интерпретируемый язык, который выражает, как должны вычисляться значения конфигурации. Эти выражения оцениваются в контексте текущей сессии приложения, и могут обращаться к данным вроде местонахождения устройства, его атрибутам, значениям A/B-тестов. В нашем случае мы конфигурируем наборы функций приложения, зависящие от региона, версии и типа проекта.


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


Это привело нас к решению сделать легковесный мобильный SDK для Hendrix и он был отличным кандидатом для Kotlin Multiplatform, так как требует значимой бизнес-логики и полностью платформонезависим.


Реализация


Для краткости мы опустим конкретные детали о Hendrix и затронем отличия в использовании Kotlin Multiplatform от Kotlin/Swift.


Сборка


На Android всё как обычно. Hendrix Multiplaform SDK подключается с помощью Gradle в качестве Android-библиотеки как любая другая зависимость. В случае с iOS нативный бинарь включается в проект Xcode как универсальный фреймворк.


Эргономика разработки


В случае с Kotlin Multiplatorm исходный код можно редактировать, перекомпилировать и добавлять к нему отладчик с брейкпойнтами хоть в Android Studio, хоть в Xcode (включая поддержку lldb). Android Studio работает из коробки, поддержка Xcode достигается с помощью плагина xcode-kotlin от TouchLabs.



Отлаживаем Kotlin-исходники в Xcode


Работа с сетью


Hendrix интерпретирует набор правил удалённо конфигурируемые файлы, которые оказываются скачаны на устройство. Мы используем Multiplatform HttpClient из фреймворка Ktor, чтобы добавить наш код работы с сетью в SDK.


Дисковый кэш


Конечно, сеть может быть недоступна, поэтому скачанные наборы правил нужно закэшировать. Для этого мы используем SQLDelight с его Android и Native Database-драйверами, чтобы получить персистентность на обеих платформах.


Завершающие мысли


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


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


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


Мы обратили внимание на пост Netflix, потому что перекликающийся с ним доклад скоро представят на нашей конференции Mobius: там тоже расскажут об опыте внедрения Kotlin Multiplatform в продакшн крупной компанией. Только речь пойдёт не о малоизвестном нишевом приложении, а о суперпопулярных Яндекс.Картах. Если два таких гиганта почти одновременно заговорили о продакшн-опыте, значит ли это, что вот теперь время Kotlin Multiplatform пришло?
Подробнее..

Монетизация рекламного трафика в мобильной экосистеме Huawei

13.11.2020 16:21:32 | Автор: admin

Привет, Хабр! Работа со встроенной рекламой в приложениях на платформе Huawei Mobile Services ведётся с помощью сервиса Ads Kit. Сервис предоставляет пользователям персонализированную рекламу, позволяет разработчикам анализировать результаты промокампаний и работать с основными рекламными форматами. В статье я расскажу, что включает в себя этот сервис, какие рекламные форматы поддерживает и какие даёт возможности для аналитики. Кому интересно прошу под кат.

Что такое Ads Kit

Ads Kit включает в себя две службы: Ads Publisher Service и Identifier Service. Ads Publisher Service предоставляет инструменты для интеграции рекламных объявлений и получения отчётов (количества запрошенных и просмотренных объявлений, кликов). Identifier Service обеспечивает работу с рекламным идентификатором пользователя Open Advertising ID (OAID) и персонализацией объявлений.

Пока рекламная платформа доступна только для корпоративных аккаунтов, поэтому стоит помнить, что при работе с платёжными сервисами экосистема Huawei требует обязательную регистрацию юридического лица. Но скоро Ads Kit смогут внедрить в свои приложения все разработчики. Также необходимо учитывать, что Ads Kit работает с HMS Core (APK) 4.0.0, так как на предыдущих версиях возможны ошибки.

Форматы рекламы

Сейчас в Ads Kit доступно 5 разных типов рекламы:

  • Баннерная реклама. По размерам баннеры делятся на фиксированные и адаптивные. У фиксированных 5 размеров: 320 x 50 dp, 320 x 100 dp, 300 x 250 dp, 360 x 57 dp для изображений 1 080 x 170 пикселей и 360 x 144 dp для изображений 1 080 x 432 пикселя. Адаптивные рекламные баннеры автоматически регулируют ширину в зависимости от соотношения сторон устройств. Также есть возможность использовать смарт-баннеры при загрузке рекламы Huawei Ads SDK создаёт рекламный экран той же ширины, что и экран, в зависимости от его ориентации на устройстве.

  • Нативная реклама. Этот тип рекламы вписывается в окружающий контент, чтобы соответствовать дизайну приложения. Такие объявления можно настраивать по мере необходимости. В нативную рекламу можно встроить не только картинки или текст, но и видео.Huawei предоставляет набор шаблонов нативной рекламы, которые доступны в двух размерах малом и среднем, и их макеты и стили можно настроить под себя. Также есть возможность дизлайкнуть нативную рекламу со стороны пользователя, при этом разработчики могут кастомизировать дальнейшее отображение рекламы после Dislike. Например, спросить, почему эта реклама нерелевантна.

  • Rewarded Ads. Реклама с вознаграждением это полноэкранные видеообъявления, которые награждают пользователей за просмотр. При просмотре рекламы сервер отправляет уникальный URL со специфичными параметрами в Ads Kit, чтобы уведомить медиасервер о том, что пользователь должен быть вознаграждён за взаимодействие с видеообъявлением. При принятии решения о том, когда вознаграждать пользователя, необходимо сбалансировать проверку пользователя и скорость подтверждения вознаграждения, чтобы, с одной стороны, защититься от спуфинга, а с другой не заставлять пользователя ждать своей награды.Разработчики Huawei рекомендуют сразу вознаграждать пользователя, обрабатывая запрос на стороне клиента, а потом проверять все вознаграждения при получении обратных вызовов на стороне сервера. Такой подход гарантирует действительность предоставленных вознаграждений, обеспечивая при этом хорошее взаимодействие с пользователем.

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

  • Splash Ads. Рекламные заставки отображаются сразу после запуска приложения, даже до того, как отобразится главный экран приложения. Для таких заставок может быть установлен таймер показа и реагирование на конкретные события, например когда пользователь перешёл по рекламной ссылке.

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

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

Рекламная аналитика

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

Это можно использовать для анализа трафика и конверсии, разделения групп пользователей и корректировки рекламных кампаний для этого Ads Kit может интегрироваться со сторонними API рекламных платформ. В этом случае необходимо к Ads Kit добавить Analytics Kit, с помощью которого также можно собирать данные по более чем 500 параметрам, включая разные сценарии поведения пользователей, их реакцию на рекламу и ключевые точки, которые влияют на дальнейшее взаимодействие.

Для анализа источников трафика применяется функция Install Referrer, которая считывает информацию из реферальной ссылки, использованной пользователем для установки приложения. Чтобы использовать Install Referrer, требуется интегрировать API Ads Kit и после этого опубликовать приложение в AppGallery.

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

Подробнее..

Дайджест интересных материалов для мобильного разработчика 370 (9 15 ноября)

15.11.2020 14:11:45 | Автор: admin
В этом дайджесте обсуждаем ужасную документацию Apple, запуск Android Studio на любом устройстве, переезд на Kotlin (в том числе и Multiplatform), создание бэкенда без серверных разработчиков, успех Among Us и UX-игры и многое другое.

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

Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

(+6) Интеграция CI/CD для нескольких сред с Jenkins и Fastlane. Часть 2
(+5) Разница между @StateObject, @EnvironmentObject и @ObservedObject в SwiftUI
(+5) SPM: модуляризация проекта для увеличения скорости сборки
Apple выпускает macOS Big Sur
Apple показала первые компьютеры на Apple Silicon
iOS 14.3 предлагает установку сторонних приложений при настройке системы
В новой версии TestFlight появилось автоматическое обновление приложений
Многие издатели отказались от публикации iOS-приложений в Mac App Store
На пути к изучению SwiftUI
3 подхода к созданию эффекта размытия в iOS
SwiftUI против реактивной MVVM-архитектуры
Анимированная круговая диаграмма на SwiftUI 2 с использованием Combine
Перенос существующего проекта Xcode на Kotlin Multiplatform Mobile
Почему новые Mac кардинально меняют правила игры для продуктовых дизайнеров?
Использование кривых Безье в SwiftUI
iPhoneNumberField: поле ввода телефона на SwiftUI
MarqueeText: бегущая строка

Android

(+19) Студийные приложения Netflix на Android и iOS теперь с Kotlin Multiplatform
(+18) Переезд из Java в Kotlin: как забрать коллекции с собой
(+9) Jetpack Compose Desktop
(+3) Как исправить баг с Drawable.setTint в API 21 Android SDK
Как запустить Android Studio на ЛЮБОМ устройстве с помощью JetBrains Projector
Видео Chicago Roboto 2020
Android Broadcast: Jetpack Compose на Desktop
Как сделать компилятор умнее
Разработка приложений для Android: полезные инструменты для разработчиков
Android Navigation с помощью уже существующего обратного стека
Разработка для разработчиков: работа над Android-библиотекой
Android RecyclerView: часть 1 Создание простого списка
Как упростить делегирование Android View Binding
Android TV: лучшие практики увлекательных приложений
Ускорение сборки с помощью Dagger Reflect
Классы данных отличный способ хранения данных
iiVisu: визуализатор звука для Android
Brackeys IDE: редактор кода для Android
ForgetMeNot: запоминание с помощью карточек

Разработка

(+24) Как приручить Charles Proxy?
(+22) Meta Gameplay Framework, или бэкенд без серверных разработчиков
(+13) Дополнительные компоненты для кроссплатформеннной библиотеки материального дизайна KivyMD
(+11) Лучшие выпускные работы весеннего семестра Технопроектов
(+9) Собеседование для QA: резюме, вопросы на интервью, переговоры о зарплате + полезные ссылки
(+8) EventBus Система событий для Unity
(+7) Система сделал-измерил-узнал
(+7) Улучшая интерфейс: как связаны дизайн и успех продукта
(+4) Разворачиваем сервер для проверки In-app purchase за 60 минут
(+3) html2json
Radio QA #62: удалённые конференции.
Podlodka #189: IoT
Минцифры готовит новый пакет поддержки IT-отрасли
Производители просят отложить предустановку российского ПО
Дизайн приложений: примеры для вдохновения #22
6 небольших проектов, которые прокачают ваше резюме разработчика
В ноябрьском рейтинге TIOBE впервые на 2 место вышел Python
Как я выпустил своё приложение в App Store без знаний кода
Рекомендации по проектированию интерфейса смарт-часов
Как начать работать на React Native, чтобы не было мучительно больно
Разбираем UX популярной игры Among Us
Влияние производительности мобильного приложения на пользовательский опыт
Практические советы Junior-разработчикам
Основные уроки, извлеченные из работы с 10х разработчиком
Глубокие ссылки в Flutter с помощью Branch
Практики, которые удвоили мою продуктивность как разработчика
Внимание мой самый ценный актив продуктивности как разработчика
Лучшие практики документирования кода для программистов
Лучшие мобильные приложения для обучения программированию на ходу
React Native в Wix архитектура
Отличный Code Review суперсила, которая нужна вашей команде
Различные способы использования цвета в дизайне
4 лучших расширения VS Code в 2020 году

Аналитика, маркетинг и монетизация

(+26) Динамический лут в играх: что стоит учитывать
(+6) Монетизация рекламного трафика в мобильной экосистеме Huawei
(+6) Ролики-мислиды: почему они работают?
(+3) Как локализовать игру? Пошаговое руководство
App Annie: что ждет мобильный рынок в 2021 году
Успех Among Us: исследование циклов роста игры, поразившей мир
Fortnite может получить ежемесячную подписку
MeowTalk: кошачий переводчик
Ushur: эффективное общение с клиентами
6 типичных ошибок при запуске мобильного приложения

AI, Устройства, IoT

(+43) Как программировать многоядерные микроконтроллеры
(+20) Маркетинговые хитрости на рынке смартфонов. Где нет прогресса?
(+11) Под капотом: 4D радар для построения изображений от Vayyar
(+8) Медленный CrossWorks for ARM?
(+8) Как настроить сбор данных с датчиков IoT и SCADA для Data Governance
Hyundai Motor выбирает платформу NVIDIA DRIVE

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

Дайджест интересных материалов для мобильного разработчика 371 (16 22 ноября)

22.11.2020 14:12:21 | Автор: admin
В новом выпуске путь к 10х инженеру, снижение комиссии App Store, тестирование без доступа к коду, руководство по росту приложений, как работает компьютерное зрение, выбор кроссплатформенной технологии и многое другое!



Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

(+9) На пути к 10x инженеру: шорткаты, сниппеты, шаблоны
(+6) Впечатление от Стэнфордских курсов CS193P Весна 2020 г.: Разработка iOS приложений с помощью SwiftUI
(+1) Composable Architecture свежий взгляд на архитектуру приложения. Тесты
(0) Keychain API в iOS
NVIDIA GeForce Now и Google Stadia запускаются на iOS в виде веб-приложений
Apple выпустила версию TensorFlow для macOS Big Sur
Apple снижает комиссию App Store для небольших разработчиков до 15%
В App Store заработали промо-коды для подписок
Использование Kotlin Multiplatform Mobile в iOS-проекте
Как безопасно работать с Core Data в фоновом потоке
Общие потоки, широковещательные каналы
Работа с Diffable Data Sources в iOS 14
Серверный Swift с Vapor, AWS Fargate и AWS Cloud Development Kit
Внедрение зависимостей: почему мы выбрали Needle
Главные шорткоды Xcode для более эффективного программирования
MVVM в iOS с SwiftUI (подробный пример + подводные камни)
Создавайте удобные для контента макеты
Создание интерфейса чата с помощью SwiftUI
Жизненный цикл SwiftUI View
Полное руководство по использованию Azure CI/CD для iOS-приложений
SlideOverCard: выдвигающаяся карточка
PostgresKit: работа с PostgreSQL

Android

(+30) Корутины и синхронизация. Лучше не смешивать
(+4) Как устроен билд APK файла внутри
(+3) Android-разработка: что общего с Lego и как не натягивать сову на глобус
Роман Елизаров становится новым руководителем проекта Kotlin
Видео Android Summit 2020
Google устанавливает новый срок получения разрешений для приложений с фоновой геолокацией
Modern Android Development (MAD) Skills: обучение современной Android-разработке от Google
Oversecured запускается на самофинансировании в размере 1 миллиона долларов благодаря выплатам за найденные ошибки
Прагматичное руководство по Hilt с Kotlin
Обнаружение скриншотов в Android
Уменьшите использование памяти вашим Android-приложением вдвое
Что крутится, то крутится
Создаем приложение с помощью Dropwizard, Maven и Kotlin
Server-Driven UI для Android с Jetpack Compose
Используем Android Jetpack KTX
Новый способ обработки состояний и событий в Android с Sealed Classes
Модуляризация приложений Android быстрый взгляд
(Почти) полностью кроссплатформенные приложения на Kotlin
5 новых платформ автоматизации тестирования для Android (2020)
Lottie для Jetpack Compose
Анимация Android RecyclerView на Kotlin
Kotlin Flow в Android обрабатываем асинхронный поток
Полное руководство по интернационализации и локализации Android-приложений
BasicAirData GPS Logger: открытый GPS-трекер для Android
Currency Converter: конвертер валют для Android

Разработка

(+25) API для QA: тестируем фичи без доступа к коду
(+18) Опыт выбора кроссплатформенной технологии для разработки приложения. Доклад Яндекса
(+5) ECSвUIвклиентеWorld of Tanks Blitz
(+4) Разработка продукта: в какой парадигме работать?
(+4) Материальный дизайн. Создание анимаций в Kivy
(+3) Переходим В OFFLINE FIRST с использованием Core Data и Managed Document(s)
(+3) Тестирование игр
(+1) Flutter. Слушатель клавиатуры без платформенного кода
(+1) Figma выкатила новый Auto Layout
(0) Виды биометрии в мобильном приложении
Podlodka #190: обязательные знания для программиста
Что делать, если вас копирует Apple
MY.GAMES и Google запустили программу по поддержке игровых разработчиков
Что такое Server-Driven UI
Какие языки хотят изучать разработчики?
7 советов для создания лучшего UX: лучшие практики мобильного дизайна
Первый онлайн-хакатон по Flutter в России Liga Stavok Flutterthon
Навигация в mcommerce-приложениях: шаблоны и подводные камни
Прекратите оценивать продуктовых дизайнеров, как визуальных дизайнеров
Вот 20 советов по программированию, о которых вы не просили
4 признания после 4 лет работы разработчиком-самоучкой
Что я узнал о UX, попивая чай
Переопределяем приложение для знакомств пример UX/UI
6 месяцев маленьких проектов
Я недостаточно умен, чтобы быть программистом
Чистая архитектура с точки зрения технического интервью
Simplenote: кроссплатформенный блокнот

Аналитика, маркетинг и монетизация

(+43) В тюрьму за приложение
(+3) Привлечение пользователей, улучшая игровой опыт и ROI
Приложения с виджетами получили более 45 млн. установок на iOS
make sense podcast: О формулировании гипотез, подходах и критериях корректности, и проверке гипотез разных уровней
Руководство по росту приложений для независимых разработчиков
Лучшие мобильные игры в The Game Awards 2020
Global App Testing исследовал привычки пользователей в работе с приложениями
Getaround: рост p2p каршеринга
Как получить больше трафика из рекомендаций похожих приложений?
Сравнительный анализ ценовой стратегии более 100 мобильных приложений на основе подписки
Навыки и требования к аналитикам данных на разных уровнях в Яндексе

AI, Устройства, IoT

(+19) Глубокие нейросети в компьютерном зрении: как работают, где используются и какие возникают проблемы
Урок цифры научит школьников работать с нейронными сетями
Ride Vision с помощью ИИ улучшает безопасность мотоциклистов
Huawei полностью продает свой бренд по производству смартфонов Honor

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

Дайджест интересных материалов для мобильного разработчика 372 (23 29 ноября)

29.11.2020 16:21:40 | Автор: admin
В этом выпуске сравнение карт iOS 1 и 14, паяльник для разработчиков, базовые концепции и базовые ошибки, продуктовые исследования и тренды рынка мобильных приложений в итоговом отчете за 2020 год. Подключайтесь!



Этот дайджест доступен в виде еженедельной рассылки. А ежедневно новости мы рассылаем в Telegram-канале.

iOS

(+10) Как добавить 3D в iOS приложение c помощью SceneKit
(+7) SwiftUI 2.0: будущее декларативно
(+4) Интеграция CI/CD для нескольких сред с Jenkins и Fastlane. Часть 3
98% приложений в App Store зарабатывает 8% всех денег
Сравнение iPhone OS 1.0 с iOS 14 с помощью карт
Каникулы App Store: с 24 по 27 декабря
Следует ли мне использовать SwiftUI в проде? Примеры кода, который поможет вам принять решение
Сборка и запуск Telegram-iOS в симуляторе Xcode 12.x
WebSockets и Swift: инженерные задачи на стороне клиента
Имитация плохой сети в iOS
До свидания, AppDelegate! Жизненный цикл приложения SwiftUI
Привет (новый) мир! Жизнь и разработка на Apple Silicon M1
Почему хорошему Swift-разработчику не нужно знать множество шаблонов
SwiftUI-Animations: библиотека анимаций
HHCustomCorner: скругление углов

Android

(+49) Я месяц провел в MIT и понял даже софтверным инженерам не стоит забывать про паяльник
(+20) TV Box или Smart TV?
(+10) Корутинная эволюция в Kotlin. Чем отличаются Channels, Broadcast channels, Shared flows, State flows
(+10) Магическая шаблонизация для Android-проектов
(+9) Kotlin FP: моноиды и сортировки
(+9) Избегаем поддельных шрифтов в Android
(+6) Koin библиотека для внедрения зависимостей, написанная на чистом Kotlin
(+6) Валидация элементов формы textInputLayout в Android с помощью связывания данных
(+4) Как сделать цветные тени в Android с градиентом и анимацией
(+1) Тестируем Android-приложение правильно
Android Broadcast: разработка под устройства
Основы Jetpack Compose: Урок 1
Android Runtime (ART) станет Mainline-модулем в Android 12
Android сможет быстрее обновлять эмодзи
Jetpack Compose: ViewModels
Обработчики эффектов Jetpack Compose
Практическая работа с сетью для разработчиков Android
Простая разработка под Android с использованием Simple-Stack
Android Vitals профилирование запуска приложения
Сегментация изображений в Android с Fritz AI
Kotlin на 60FPS: Kotlin медленный?
Переход с Dagger на Hilt стоит ли?
Android Architecture Components сделайте приложение своей мечты!
Простая кастомная клавиатура с InputConnection
NoNameBottomBar: нижнее меню для Android
My Memory: игра на запоминание на Kotlin

Разработка

(+28) Как я стал разработчиком игр для мобильных телефонов
(+17) Как и почему мы стали делать UI в Figma вместо Photoshop
(+16) Снятся ли телефонам с HMS электроовцы? Обзор функций и возможностей инструментов AI от Huawei
(+15) Конечные автоматы на страже порядка
(+11) Базовые концепции Unity для программистов
(+10) Math Invasion. Мой долгострой
(+8) Расширяемая и удобная в сопровождении архитектура игр на Unity
(+7) Разработка мобильных приложений на Python. Создание анимаций в Kivy. Part 2
(+7) Тестирование игр
(+5) Как устроена система тестирования платежного ядра мобильного приложения
(+3) MMORPG больше не в Telegram Swift и Kotlin Первый большой проект Часть 1
(+2) Качественное тестирование ПО
(+1) Автоматизация тестирования приложений Salesforce
15 ошибок в программировании, которые совершал каждый разработчик
Дизайн приложений: примеры для вдохновения #23
Что такое Разработка через тестирование (Test Driven Development)
Рабочий день геймдизайнера
Итоги конкурса Games Cup 2020
Зачем нужно знать всякие низкоуровневые вещи
UX и Точка. Как устроены продуктовые исследования
Разработка крупных приложений на Xamarin: в чем выгода
6 типов проектов, которые должен попробовать каждый разработчик
Полный курс Firebase Authentication и Flutter 2020
5 улучшений, которых заслуживает App Store исследование UX
Микровзаимодействия: суперсила дизайнеров
Глассморфизм в пользовательских интерфейсах
Flutter vs Kotlin Multiplatform: руководство на 2021 год
Управление идентичностью разработчиков в автономных командах
Flutter может и не станет Next Big Thing, но Kotlin Multiplatform никуда не денется
Масштабирование CI для мобильных инженеров
5 простых советов по написанию чистого кода
Как я стал более ценным программистом за 6 месяцев (и как вы тоже можете)

Аналитика, маркетинг и монетизация

(+27) Внедряем кросс-платформенные пуш-уведомления: дополнительные возможности
(+6) Сравнение аналитических систем для мобильного маркетинга
Тренды рынка мобильных приложений 2020 отчет Liftoff
make sense podcast: О механизмах внешней и внутренней мотивации и их применении в геймификации
LOVEMOBILE #09: NGrow.ai
Хочу в геймдев #16: Локализатор игр
Podlodka #191: маркетинг
Bald Dating дейтинг для лысых
ByteDance запускает магазин мобильных игр и собственное издательство
AudioMob получил $1.5 млн. на аудиорекламу в играх
Xesto: 3D сканирование стоп
Реклама в iOS 14: как получить согласие пользователя руководство Adjust
24 полезных совета для правильного управления репутацией мобильного приложения

AI, Устройства, IoT

(+57) Трансформеры в Поиске: как Яндекс применил тяжёлые нейросети для поиска по смыслу
(+6) От хорошей работы не только волки дохнут, но и движки распознавания возникают
Видео PyTorch Virtual Developer Day

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

Перевод Как устроен билд APK файла внутри

15.11.2020 18:09:06 | Автор: admin

Процесс создания APK и компиляции кода


Рассматриваемые темы


  • Архитектура процессоров и необходимость для виртуальной машины
  • Понимание Java виртуальной машины
  • Компиляция исходного кода
  • Виртуальная машина Андроид
  • Процесс компиляции в .dex файл
  • ART против Dalvik
  • Описание каждой части билд процесса
  • Исходный код
  • Файлы ресурсов
  • AIDL файлы
  • Модули библиотек
  • AAR библиотеки
  • JAR библиотеки
  • Android Asset Packaging Tool
  • resources.arsc
  • D8 и R8
  • Dex и Multidex
  • Подписывание APK файла
  • Ссылки


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



Архитектура процессоров и зачем нужна виртуальная машина


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

У андроида много удивительных характеристик и одна из них разные архитектуры процессоров такие как ARM64 и x86

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

image

Понимание Java виртуальной машины


JVM это виртуальная машина, позволяющая устройству запускать код, который скомпилирован в Java байткод

Используя JVM, вы избавляетесь от проблемы с разной архитектурой процессоров.

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

Но JVM была создана для систем с большими мощностями по ресурсам, а наш андроид имеет сравнительно мало памяти и заряда батареи.

По этой причине Google создал адаптированную под андроид виртуальную машину, которая называется Dalvik.

image

Компилируем исходный код

image

Наш исходный Java код для андроида компилируется в класс файл .class с байткодом с помощью javac компилятора и запускается на JVM

Для котлина есть kotlinc компилятор, который делает совместимый с Java байткод.

Байткод это набор инструкций, который выполняется на целевом устройстве.

Java байткод это набор инструкций для Java виртуальной машины.

Андроид виртуальная машина


Каждое андроид приложение работает на своей виртуальной машине. С версий 1.0 до 4.4, это был Dalvik. В андроид 4.4, вместе с Dalvik, Google представил в качестве эксперимента новый андроид runtime, который назывался ART

Сгенерированный класс файл .class содержит JVM Java байткод.

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

image

Комплияция в .dex файл


Во время компиляции происходит конвертация .class класс файл и .jar библиотеки в один classes.dex файл, который содержит Dalvik байткод.

Команда dx превращает все .class и .jar файлы в один classes.dex файл, который написан с форматом Dalvik байткода.

Dex это аббревиатура с английского Dalvik Executable.

image

ART против Dalvik


C версии 4.4 андроид мигрировал на ART. ART также работает с .dex файлом.

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

ART и Dalvik совместимы, так что приложения разработанные для Dalvik должны работать и на ART.

Компиляция Dalvik (JIT- just in time) имела такие минусы как быстрая трата батареи, лаги в приложениях и плохой перформанс. В Dalvik трансляция происходит только когда это нужно. Мы открываем новый экран и только в этот момент происходит трансляция, за счет этого установка происходит быстрее, но при этом проседает перформанс.

Это причина по которой Google сделал Android Runtime (ART).

ART основан на AOT (ahead of time) компиляции, она происходит до того как приложение запустится.

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

Несмотря на то, что Dalvik был заменен на ART, .dex формат файлов еще используется

В андроид 7.0 JIT вернулся. Гибридная среда сочетает фичи как от JIT компиляции так и
от ART


Среда запуска байткода это очень важная часть андроида и она вовлечена в процесс запуска и установки приложения

image

Каждый этап описанного процесса


image

Source Code (Исходный код)


Это Java и Kotlin файлы в src пакете.

Resource Files


Файлы находящиеся в директории с ресурсами

AIDL Files


AIDL аббревиатура Android Interface Definition Language, позволяет вам описать интерфейс межпроцессорного взаимодействия.

AIDL может использоваться между любыми процессами в андроиде.

Library Modules


Модули библиотек содержат Java или Kotlin классы, компоненты андроида и ресурсы.

Код и ресурсы бибилотеки компилируются и пакуются вместе с приложением.

Поэтому модуль библиотеки может считаться компайл тайм артефактом.

AAR Libraries


Андроид библиотеки компилируются в AAR android archive файл, который вы можете использовать как зависимость для вашего android app модуля.

AAR файлы могут содержать андроид ресурсы и файл манифеста, что позволяет вам упаковать туда общие ресурсы такие как layouts и drawables в дополнение к Java или Kotlin классам и методам.

JAR Libraries


JAR это Java библиотека и в отличие от AAR она не может содержать андроид ресурсы и манифесты.

Android Asset Packaging Tool


AAPT2 аббревиатура (Android Asset Packaging Tool) компилирует манифест и файлы ресурсов в один APK.

Этот процесс разделен на два шага компиляцию и линковку Это улучшает производительность так как если вы поменяете один файл, вам нужно компилировать только его и прилинковать к остальным файлам командой 'link'

AAPT2 может компилировать все типы андроид ресурсов, таких как drawables и XML файлы.

При вызове AAPT2 для компиляции, туда передается по одному ресурсному файлу на каждый вызов

Затем APPT2 парсит файл и генерирует промежуточный бинарный файл с расширением .flat

Фаза линковки склеивает все промежуточные файлы сгенерированные в фазе компиляции и дает нам на выход один .apk файл. Вы также можете сгенерировать R.java файл и правила для proguard в это же время.

resources.arsc


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

APK содержит AndroidManifest, бинарные XML файлы и resources.arsc

resource.arsc содержит всю мета информацию о ресурсах, такую как индексы всех ресурсов в пакете

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

R.java файл это выходной файл вместе с APK ему назначен уникальный id, который позволяет Java коду использовать ресурсы во время компиляции.

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

image

D8 и R8


Начиная с андроид студии 3.1 и далее, D8 был сделан дефолтным компилятором.

D8 производит более маленькие dex файлы с лучшей производительностью, если сравнивать со старым dx.

R8 используется для компиляции кода. R8 это оптимизированная версия D8

D8 играет роль конвертера класс файлов в Dex файлы, а также производит дешугаринг функций из Java 8 в байткод, который может быть запущен на андроиде

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

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

Обфускация имеет и другие преимущества для предотвращения реверс инжиниринга, но основная цель уменьшить размер.

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

С помощью дешугаринга мы можем использовать удобные фичи языка Java 8 на андроиде.

image

Dex and Multidex


R8 дает на выходе один DEX файл, который называется classes.dex

Если количество методов приложения переваливает за 65,536, включая подключенные библиотеки, то произойдет ошибка при билде</b

The method ID range is 0 to 0xFFFF.

Другими словами, вы можете ссылаться на 65,536, или от 0 до. 65,535, если говорить цифрами

Чтобы избежать этого, нужно внимательно следить за зависимостями своего проекта и использовать R8, чтобы удалять неиспользуемый код, или включать мультидекс (multidex)


image

Подписывание APK файла


Все Apk файлы требуют цифровую подпись до того как они могут быть установлены на ваш девайс

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

Дебажный кейстор и дебажный сертификат создаются автоматически

Для релиз билдов вам нужен кейстор, которым вы подпишете свой apk файл. Вы можете создать APK файл в андроид студии через Generated Signed Apk опцию.

image

Ссылки


Подробнее..

Избегаем поддельных шрифтов в Android

24.11.2020 18:14:36 | Автор: admin

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


В этой статье хочу рассказать об этой проблеме и о её решении.


Создание семейства шрифтов


Начиная с API 26, появилась возможность объединять шрифты в семейства.
Семейство шрифтов это набор файлов шрифтов с указанием их стиля и веса.


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


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


Пример файла:


<?xml version="1.0" encoding="utf-8"?><font-family xmlns:android="http://personeltest.ru/away/schemas.android.com/apk/res/android">    <font        android:fontStyle="normal"        android:fontWeight="400"        android:font="@font/lobster_regular" />    <font        android:fontStyle="italic"        android:fontWeight="400"        android:font="@font/lobster_italic" /></font-family>

Вариант для Support Library
<?xml version="1.0" encoding="utf-8"?><font-family xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto">    <font        app:fontStyle="normal"        app:fontWeight="400"        app:font="@font/lobster_regular" />    <font        app:fontStyle="italic"        app:fontWeight="400"        app:font="@font/lobster_italic" /></font-family>

Атрибут fontStyle определяет стиль начертания шрифта обычное(normal) или курсивное(italic).
В свою очередь, fontWeight устанавливает вес, aka насыщенность шрифта.
И конечно, font будет задавать шрифт который будет использоваться при заданном fontWeight и fontStyle.


Вес шрифта


Этот стандарт пришел с web-разработки. Значение устанавливается от 100 до 900 с шагом 100.


Следующая таблица соответствует распространенным именам насыщенности:


Значение Общее название
100 Тонкий (Волосяной)
200 Дополнительный светлый
300 Светлый
400 Нормальный
500 Средний
600 Полужирный
700 Жирный
800 Дополнительный жирный
900 Черный (Густой)

В основном, в файле семейства шрифтов, достаточно указать только шрифты для нормального начертания 400, и стандартного жирного 700.


Более подробно о насыщенности шрифта читайте здесь.


Поддельный курсив


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


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


Я сделал небольшое приложение которое показывает отличия искусственной стилизации от настоящей на примере Lobster Two шрифта:




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


Решение


В данном примере, я создал lobster_two.xml в котором указал шрифты для обычного, курсива, жирного, и жирного курсива:


<?xml version="1.0" encoding="utf-8"?><font-family xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto">    <font        app:fontStyle="normal"        app:fontWeight="400"        app:font="@font/lobster_two_normal" />    <font        app:fontStyle="italic"        app:fontWeight="400"        app:font="@font/lobster_two_italic" />    <font        app:fontStyle="normal"        app:fontWeight="700"        app:font="@font/lobster_two_bold" />    <font        app:fontStyle="italic"        app:fontWeight="700"        app:font="@font/lobster_two_bold_italic" /></font-family>

А также, lobster_two_incomplete.xml в котором указал только шрифт для обычного текста:


<?xml version="1.0" encoding="utf-8"?><font-family xmlns:app="http://personeltest.ru/away/schemas.android.com/apk/res-auto">    <font        app:fontStyle="normal"        app:fontWeight="400"        app:font="@font/lobster_two_normal" /></font-family>

И переключался между ними по нажатию на переключатель.


Когда использовался lobster_two_incomplete.xml, вместо lobster_two.xml, то происходило искусственное растягивание и наклонение шрифта.


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


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

// Правильноval typeFace = resources.getFont(R.font.lobster_two)textView.setTypeface(typeFace, Typeface.BOLD)// Не правильноtextView.typeface = resources.getFont(R.font.lobster_two_bold)// Не правильноval typeFace = resources.getFont(R.font.lobster_two_incomplete)textView.setTypeface(typeFace, Typeface.BOLD)// Не правильноval typeFace = resources.getFont(R.font.lobster_two_normal)textView.setTypeface(typeFace, Typeface.BOLD)

Используем в xml

// Правильно<TextView          ...          android:fontFamily="@font/lobster_two"          android:textStyle="bold|italic"/>// Не правильно<TextView          ...          android:fontFamily="@font/lobster_two_bold_italic"/>// Не правильно<TextView          ...          android:fontFamily="@font/lobster_two_incomplete"          android:textStyle="bold|italic"/>// Правильно<TextView          ...          android:fontFamily="@font/lobster_two"          android:textStyle="bold"/>// Не правильно<TextView          ...          android:fontFamily="@font/lobster_two_bold"/>// Не правильно<TextView          ...          android:fontFamily="@font/lobster_two_normal"          android:textStyle="bold"/>
Подробнее..

Категории

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

© 2006-2020, personeltest.ru