Android mvi что это

Обновлено: 18.05.2024

Сегодня в выпуске: простой и эффективный способ обмана Face ID, реверс-инжиниринг прошивок IoT-устройств, неожиданные результаты сравнения производительности Java и Kotlin, сравнение паттернов MVC, MVP, MVVM и MVI, советы по использованию Android Studio. А также: набор свежих инструментов пентестера и библиотек для программистов.

Почитать

Как обмануть Face ID

Biometric Authentication Under Threat: Liveness Detection Hacking — презентация с Black Hat, посвященная способам обмана биометрических сенсоров в смартфонах (датчик отпечатка пальца, сканер лица и сетчатки). Авторы приводят несколько способов обхода датчиков, в том числе с помощью прямого подключения к шине камеры и подачи на нее заранее снятого изображения (это позволяет обойти алгоритмы определения реального присутствия человека, но не лазерные сканеры в iPhone и некоторых других смартфонах). Но интереснее всего способ обмана сканера Face ID в iPhone X и более поздних моделях.

Во время сканирования iPhone строит подробную 3D-модель лица человека и требует, чтобы глаза были открыты. Такой подход сводит на нет почти все методы манипуляции, включая показ фотографии, видеозаписи, поднесение смартфона к лицу спящего человека и так далее.

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

Реверс-инжиниринг прошивок IoT-устройств

How to Start IoT device Firmware Reverse Engineering? — введение в реверс-инжиниринг прошивок различных IoT-устройств, таких как роутеры, камеры, устройства для умного дома и так далее. Статья короткая, простая, но содержит несколько вводных рекомендаций, которые могут пригодиться новичкам.

  1. Любой реверс начинается с поиска самой прошивки. Обычно ее можно скачать с сайта производителя или на форумах.
  2. Зачастую прошивки распространяются в zip-архиве, внутри которого находится файл с расширением .bin.
  3. Обычно файл .bin — это сплав из загрузчика, ядра Linux и образов одного или нескольких разделов. Чтобы узнать адреса смещений, по которым находятся эти образы в файле, можно использовать утилиту binwalk :
  1. Адрес смещения можно использовать, чтобы извлечь образ раздела с помощью утилиты dd (в данном случае образ корневой файловой системы squashfs):
  1. Получить содержимое файловой системы squashfs позволяет утилита unsquashfs :
  1. На этом все, можно начинать анализ содержимого прошивки.

Разработчику

Сравнение производительности Java и Kotlin

Java vs. Kotlin — Part 1: Performance — сравнение производительности Java и Kotlin с помощью набора бенчмарков Computer Language Benchmark Game (CLBG), используемого во многих исследовательских работах.

Автор взял шесть бенчмарков CLBG на Java, сконвертировал их в Kotlin с помощью встроенного в Android Studio конвертера, а затем переписал их в соответствии с особенностями Kotlin и рекомендациями JetBrains по написанию кода. Затем сравнил производительность и потребление памяти всех трех версий кода на стандартной виртуальной машине Java HotSpot.

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

Заметно также, что Kotlin значительно проиграл по потреблению памяти в тесте Fannkuch Redux. Причина: использование полноценного объекта Integer для хранения простых чисел в противовес Java, которая использует для этого переменные.

В целом Java-код оказался на 6,7% быстрее кода на Kotlin.

MVC, MVP, MVVM, MVI: в чем разница?

MVWTF: Demystifying Architecture Patterns — разбор и сравнение различных паттернов проектирования ПО, позволяющих разделить бизнес-логику, данные и графический интерфейс приложения.

MVC (Model — View — Controller) — старейший паттерн проектирования, родившийся в 1970-х годах в среде разработчиков Smalltalk. Он разделяет приложение на три компонента:

  • Модель — источник данных: база данных, сетевой ресурс, файл, что угодно. Модель отвечает только за извлечение данных, но не за их отображение.
  • Представление (View) отвечает только за отображение данных, без какой-либо логики: что получил, то и показал.
  • Контроллер — связующее звено между моделью и представлением. Контроллер ответственен за получение данных модели и передачу их представлению с внесением необходимых преобразований. Также контроллер отвечает за получение данных из форм ввода и передачу их модели.

Главная проблема MVC в случае с Android заключается в том, что контроллер и представление приходится реализовать внутри активности или фрагмента, а это значит, что они не будут по-настоящему разделены, а написание unit-тестов существенно затруднится (нельзя просто так взять и написать JUnit-тест для активности или фрагмента).

MVP (Model — View — Presenter) — развитие идеи MPC, в котором контроллер отделен от представления с помощью абстрактного интерфейса. MVP позволяет по-настоящему отделить компоненты приложения друг от друга и упрощает unit-тестирование.

MVVM (Model — View — ViewModel) — дальнейшее развитие MVP, в котором контроллер/презентер полностью отвязан от представления. Роль контроллера здесь выполняет ViewModel, которая, как и контроллер/презентер, получает данные от модели и выполняет необходимые преобразования, но не отдает их напрямую представлению. Вместо этого представление подписывается на обновления ViewModel и отображает их по мере обновления. MVVM реализуется с помощью реактивных фреймворков, таких как RxJava или Android Architecture Components.

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

MVI (Model — View — Intent) — паттерн, призванный решить проблему MVVM под названием «согласованность состояния». Дело в том, что на определенном уровне сложности ViewModel на поверхность быстро вылезет сложность контроля текущего состояния: ViewModel может быть изменена сразу несколькими компонентами приложения, а каждое изменение может привести к конфликтам в общем состоянии ViewModel.

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

Примеры реализации паттернов размещены на GitHub автора статьи.

Советы по использованию Android Studio

Get the most out of Android Studio as an IDE — очередная статья на тему эффективного использования Android Studio.

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

Предисловие

Впервые наткнувшись на статью о Model-View-Intent (MVI) под Android, я даже не открыл ее.
— Серьезно!? Архитектура на Android Intents?

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

Изучая MVI, я невольно столкнулся с проблемой, что весь подход выглядит как-то запутанно, как какие-то дебри. Да, на выходе получается решение с плюсами по отношению к MVP и MVVM, но, смотря на эту всю комплексность, задаешься вопросом: "А стоило ли?".

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

Так я решил написать свое решение. Основные требования (по значимости):

  1. Простое;
  2. Покрывает все UI кейсы, которые я только могу придумать;
  3. Структурированное.

И что это?

Позвольте представить — Джунгли (Jungle). Под капотом — только RxJava с ее реактивным подходом.

  • State — "устойчивые" данные об UI, которые должны быть показаны даже после пересоздания View;
  • Action — "неустойчивые" данные об UI, которые не должны быть показаны после пересоздания View (например, данные о Snackbar и Toast);
  • Event — Intent из Model-View-Intent;
  • MviView — интерфейс, через который поставляются новые Actions и обновления State;
  • Middleware — посредник между одной функциональностью бизнес логики и UI;
  • Store — посредник между Model и View, который решает, как обрабатывать Events, поставлять обновления State и новые Actions.

image


Все отношения, показанные на картинке, — опциональны

Как это работает?

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

Давайте напишем нашу UI часть:

Что из этого (пока) можно понять? Мы можем послать DemoEvent.Load нашему DemoStore (по клику на Reload кнопку); получить DemoAction.ShowError (с данными об ошибке) и отобразить Toast; получить обновление по DemoState (с данными о странах и состоянии загрузки) и отобразить UI компоненты в соответствии с требованиями. Вроде бы не так уж и сложно.

Теперь приступим к нашему DemoStore. В первую очередь, унаследуем его от Store, разрешим получать DemoEvent, производить DemoAction и изменять DemoState:

Затем, создадим CountryMiddleware, который будет ответственным за предоставление данных о загрузке стран:

Что такое Command? Это специфичный сигнал, который побуждает "что-то" сделать. А CommandResult? Это результат выполнения этого "чего-то".

В нашем случае CountryMiddleware.Input сигнализирует, что логика CountryMiddleware должна быть выполнена. Каждое выполнение логики Middleware возвращает CommandResult; для лучшей структуры приложения можно хранить этот результат внутри sealed класса (CountryMiddleware.Output).

В нашем случае мы попросту возвращаем Observable, который испустит Output.Loading во время загрузки, Output.Loaded с данными на успешную загрузку, Output.Failed с информацией об ошибке на ошибку.

Давайте вернемся к DemoStore и заставим обработать CountryMiddleware на нажатие Reload кнопку:

Переопределяя поле middlewares мы указываем, какие Middlewares наш DemoStore может обработать. Под капотом Store использует Commands. Поэтому нам следует сконвертировать наш DemoEvent.Load в CountryMiddleware.Input (для того, чтобы принудить перезагрузку).

Итак, теперь мы можем получать результат от CountryMiddleware. Давайте позволим последнему изменять наш DemoState:

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

Для отображения ошибки загрузки используется DemoAction.ShowError. Чтобы сгенерировать последний, необходимо предоставить новую Command (из CommandResult) и связать ее с нашим Action:

Последнее, что осталось сделать — привязать автоматический запуск выполнения CountryMiddleware. Все, что нужно сделать, это добавить его Command в bootstrapCommands :

Просто?

Можно использовать конкретно только то, что вам нужно, без какой-либо лишней логики. Несколько классов и щепотка магии под капотом. Один Store, опционально несколько Middlewares, опционально имплементация MviView.

Ваша View должна только отображать обновления какой-то функциональности бизнес логики? Вам даже не нужны Events, только Store, Middleware и переопределение метода render функции в MviView.

Только кнопка, по клику которой происходит какая-то навигация? Окей, стоит только поиграться с Event внутри Store и ничего больше.

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

Структурировано?

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

  • Хранить Commands в sealed классах внутри Store, группируя их по назначения: генерирующие Actions или напрямую изменяющие State?
  • Хранить Commands, относящихся к Middlewares, внутри последних.

Также стоит помнить, что Middleware — про одну функциональность, что делает его похожим на UseCase (Interactor). На мой взгляд, присутствие последнего (и, как следствие, какого-то domain layer) говорит о хорошо структурированном проекте. По этой же аналогии, я считаю, что использование Middleware способствует улучшению структуры проекта.

Заключение

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

Более подробные разборы работы можно найти на wiki. Отвечу на любые вопросы. Буду рад, если вам данное решение покажется полезным!

Если использовать VM + livedata то возникает проблема с синхронизацией нескольких LiveData. Поэтому приходится использовать единый стейт и единственный метод его отрисовки render(state) . Этот паттерн назвается Model-View-Update (MVU) и основан он на Unidirectional data flow (UDF). Есть несколько реализаций UDF. Самые популярные - это TEA(The Elm Architectire), MVI, Redux и Flux. И многие ребята которые о них рассказывают сами путаются в различиях.

Во всех из них используется единый стейт и сайд эффект/команда/евент (везде называется по разному, но суть SingleEventLiveData).

Работа с единым стейтом

Единый стейт удобно тестировать, но при его использовании вздникают другие проблемы:

Как обновлять единый стейт? Если View передает все дейтвия во ViewModel/Presenter, то обновление стейта может проихожить во многих местах. Из-за этого, на сложных экранах, появляются состояния гонки и новый стейт может перезатереть другой новый стейт.
Решений этой проблемы 2:

Конвертировать все действия в единую шину (Action) и на основе этой шины делать reducer, который применяет действие с Action в стейт.

  • Плюсы: все изменения в одном месте из проще читать и понимать.
  • Минусы: очень много кода получается, в котором особой логики нет. Можно использовать генераторы кода, например actions-dispatcher, но тогда время компиляции увеличивается и это грустно.

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

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

Работа с сайд эффектами

Сайд еффект - это одноразовое событие: навигация, отображение snackbar, диалога и обработка интентов (шаринг, intent chooser и т.п.).

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

Для изменения состояний экрана у нас используется state-delegator. Он позволяет объединить несколько view в группу и менять их состояния 1 строчкой. Ну и самое важное - это возможность добавления анимации перехода из одного состояния в другое.

Мне на моем проекте недостаточно было набора состояний Loading Content Error , поэтому я расширил этот набор и получил следующие состояния (ViewState):

Как стать разработчиком Android-приложений с нуля, с чего начать и куда развиваться — поможет понять дорожная карта:

Дорожная карта Android-разработчика

Java или Kotlin?

Мы уже проводили сравнение Java и Kotlin, в ходе которого пришли к выводу, что оба языка важны. Так, без знания Java будет сложно разобраться в Kotlin, который всё ещё использует JVM. С другой стороны, Kotlin сделал большой шаг вперёд и ориентируется именно на Android-разработку, тогда как Java используется также для бэкенда, десктоп-приложений и Big Data.

Начало работы

Скачайте Android Studio и разберитесь с данной IDE.

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

Что Kotlin, что Java являются полноценными ООП языками, поэтому освойте принципы объектно-ориентированного программирования.

3–5 декабря, Онлайн, Беcплатно

Изучите структуры данных и алгоритмы, подкрепите теорию практикой.

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

Система контроля версий

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

Android Architecture

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

  • MVVM (Model–View–ViewModel);
  • MVI (Model-View-Intent);
  • MVP (Model-View-Presenter).

Подробнее о них можно узнать из статьи.

Создание приложений

А здесь самое интересное.

Activity в Android — это по сути окно приложения. Простые приложения состоят из одного окна, сложные — из двух и более. Жизненный цикл Activity включает в себя шесть коллбэков:

  • onCreate() — создание активити;
  • onStart() — подготовку к отображению на экране;
  • onResume() — отображение на экране;
  • onPause() — приостановку процессов;
  • onStop() — состояние невидимости;
  • onDestroy() — завершение работы.

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

Для работы с компонентами используйте такие инструменты, как Room, Navigation, WorkManager, LiveData, Data Binding. Из сторонних библиотек будут полезны Glide, Retrofit, GSON и Firebase.

Разберитесь с Intent для взаимодействия между объектами Activity и объектом Context, который предоставляет доступ к ресурсам и другим объектам, в том числе к Activity.

Ну и как стать Android-разработчиком с нуля без практики? Следуйте инструкциям из данного видеоурока и подключитесь к NASA через API, чтобы получить доступ к фотографиям из космоса:

Продвинутая разработка

Продолжайте развиваться, осваивая безопасную разработку. Для тестирования подойдут фреймворки JUnit и Espresso, для управления зависимости — библиотеки Dagger и Koin. Дополнительно почитайте о параллелизме и многопоточности, узнайте, что это и как используется в создании Android-приложений.

Заключение

Так как же стать Android-разработчиком с нуля? Определитесь с языком программирования, научитесь работать с Android Studio, Gradle и веб-сервисом для хостинга проектов. Вам предстоит разобраться в основных паттернах и их отличиях, изучить создание гибких интерфейсов, а также подключаться к другим сервисам через API. Продвинутая разработка предполагает тестирование, управление зависимостями, работу с параллелизмом и многопоточностью.


С MVVM (Model-View-ViewModel) процесс разработки графического интерфейса для пользователей делится н а две части. Первая — это работа с языком разметки или кодом GUI. Вторая — разработка бизнес-логики или логики бэкенда (модель данных). Часть View model в MVVM — это конвертер значений. Это значит, что view model отвечает за конвертирование объектов данных из модели в такой вид, чтобы с объектами было легко работать. Если смотреть с этой стороны, то view model — это скорее модель, чем представление. Она контролирует большую часть логики отображения. Модель представления может реализовывать паттерн медиатор. Для этого организуется доступ к логике бэкенда вокруг набора юз-кейсов, поддерживаемых представлением.

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

На следующей картинке — разные элементы, которые мы собираемся создать при помощи компонента Architecture и библиотеки Koin для внедрения зависимостей.


Архитектуру ниже можно разделить на три различные части.

Представление

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

Модель представления

Этот компонент связывает модель и представление. Отвечает за управление ссылками данных и возможных конверсий. Здесь появляется биндинг. В Android мы не беспокоимся об этом, потому что можно напрямую использовать класс AndroidViewModel или ViewModel.

Модель

Это уровень бизнес-данных и он не связан ни с каким особенным графическим представлением. В Android, согласно “чистой” архитектуре, модель может содержать базу данных, репозиторий и класс бизнес-логики. Картинка ниже описывает взаимодействие между разными компонентами.


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

А с момента появления компонента архитектуры, логичное общее решение — реализовать Android-приложения при помощи модели с изображения ниже. Там вы увидите стрелки, которые ведут от представления (активности/фрагмента) к модели.

А это значит, что View знает о View-Model, а не наоборот, и View Model знает о Model, и не наоборот. То есть у представления будет связь с моделью представления, а у модели представления будет связь с моделью. Строго в таком порядке, никак иначе. Благодаря такой архитектуре приложение легко поддерживать и тестировать.


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

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

Пользовательская модель

Приложение будет обрабатывать данные такой структуры. А для простоты я выберу всего лишь некоторые параметры. У класса GithubUser есть room-аннотация и у данных в локальной БД будет такая же структура, как и у данных в API.

У пространства DAO есть только два метода. Один — добавление информации в БД. Второй — ее извлечение.

Пространство базы данных выглядит так:

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

Если вы хотите узнать, как пользоваться Retrofit вместе с сопрограммами, загляните сюда.

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

Как сами видите, у репозитория есть конструктор с двумя параметрами. Первый — это класс, который представляет онлайн-данные, а второй — представляет данные оффлайн.

После того, как мы описали модель и все ее части, пора ее реализовать. Для этого возьмем класс, родителем которого является класс ViewModel Android Jetpack.

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

View-model берет репозиторий в качестве параметра. Этот класс “знает” все источники данных для нашего приложения. В начальном блоке view-model мы обновляем данные БД. Это делается вызовом метода обновления репозитория. А еще у view-model есть свойство data. Оно получает данные локально напрямую. Это гарантия, что у пользователя всегда будет что-то в интерфейсе, даже если устройство не в сети.

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

Это последний компонент архитектуры. Он напрямую общается с представлением-моделью, получает данные и, например, передает их в recycler-view. В нашем случае представление — это простая активность.

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

Вот вы и увидели, как я получил экземпляр view-model, используя для этого внедрение. А как это сработает, мы увидим дальше.

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

Так мы создадим важные объекты. Нашему приложению они нужны там же и нам останется только вызвать их в разные точки программы. Для этого и нужна магия библиотеки Koin.

В Module.kt есть объявление объекта, который нужен приложению. А в представлении мы берем inject, который говорит Koin, что нужен объект view-model. Библиотека в свою очередь старается найти этот объект в модуле, который мы определили ранее. Когда найдёт, назначит ему свойство userViewModel. А если не найдёт, то выдаст исключение. В нашем случае, код скомпилируется правильно, у нас есть экземпляр view-model в модуле с соответствующим параметром.

Похожий сценарий применится к репозиторию внутри view-model. Экземпляр будет получен из модуля Koin, потому что мы уже создали репозиторий с нужным параметром внутри модуля Koin.

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

Читайте также: