Краткий рассказ о разработке SDK для Android. Часть 1.

Добро пожаловать, отважный искатель приключений! Если вы пришли сюда, чтобы послушать историю о страсти и тяжелом труде, о взлетах и ​​падениях, историю о жизни и выживании - что ж, вы не будете разочарованы! Последние два года я работал в Estimote в качестве члена команды, ответственной за поддержку нашего SDK для Android. Что это за продукт, спросите вы - мы пишем лучшее программное обеспечение для обеспечения контекстного взаимодействия ваших приложений на основе маяков!

Я хотел бы дать вам несколько советов, которые могут оказаться полезными при разработке собственного SDK, библиотеки или даже приложения. Благодаря этим методам наш изначально простой SDK теперь может обрабатывать такие сложные вещи, как Внутренняя навигация, Уведомления о сближении, Ячеистая сеть или Отслеживание активов.
Итак, приступим, ладно?

Солнце медленно садится, когда вы приближаетесь к таинственному городу Эсдикья. В последний раз, когда вы были в этом месте, вы помните, что
оно было совершенно другим. Город снаружи ничем не отличался от любого другого типичного средневекового города - у него были красиво отполированные стены, широкие ворота, крутые геральдические знамена и даже небольшой ров, окружавший все строение.
Прошло много времени, и вы снова здесь, перед совершенно изменившимся городом. Стены намного выше, ворота кажутся еще шире, а количество башен заметно увеличилось вдвое. Вы легко находите дорогу к таверне, проходя по процветающим, ухоженным улицам, вы берете кружку глубокого темного пива и с любопытством в уме подходите к бармену.
«Что ж, храбрый странник, позволь мне объяснить, что случилось», - сказал он.

Psst… в этот момент вы можете включить эту крутую музыку в таверне!

1. Управление модулями библиотеки

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

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

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

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

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

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

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

Принцип единой ответственности

Принцип инверсии зависимостей

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

Как видите, основным уровнем является модуль interfaces-api, который поддерживается как отдельная библиотека. Он содержит только интерфейсы всех объектов, используемых в стеке. Конкретная реализация заключается в отдельных модулях, реализующих интерфейсы, таких как, например, cloud-module или core-module.
Таким образом, каждый модуль должен только объявить зависимость от interfaces-api. Это позволит им получать доступ к другим частям вашего стека через эти интерфейсы, тем самым делая их независимыми от конкретной реализации.

«Ну, так как же связать эти библиотеки вместе, не компилируя напрямую одну в другую?»

Что ж, идея состоит в том, чтобы использовать третий слой для объединения всех модулей вместе.
У sdk-starter есть только одна обязанность: объединить все необходимые библиотеки и удовлетворить их потребности во внешних объектах.

Например, в модуле бизнес-логики есть главный объект с именем UsefulDataProvider, который позволяет вам… ну, получить некоторые полезные данные.
Для выполнения своей задачи ему нужны два внешних объекта - UsefulDataRepository и UsefulDataTransformer. Оба они являются интерфейсами, определенными в interfaces-api, и их реализация находится в отдельных модулях:

  • UsefulDataCloudRepository в облачном модуле,
  • UsefulDataTopSecretTransformer в основном модуле.

Все эти модули связаны вместе в sdk-starter, который содержит UsefulDataProviderFactory, который создает объекты UsefulDataRepository и UsefulDataTransformer и передает их конструктору UsefulDataProvider .

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

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

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

2. Используйте Kotlin - это повысит вашу продуктивность.

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

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

Да, кстати, термин «изменяемая переменная» - тавтология, потому что «переменная» по определению изменчива.

Kotlin имеет полную нулевую безопасность

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

Что ж, при использовании Kotlin вы получаете это прямо из коробки. Плюс элегантные и безопасные проверки на null, если они вам нужны:

У Котлина потрясающие лямбды

Вы когда-нибудь попадали в ад обратных вызовов? Да, я тоже! Мои самые сокровенные кошмары были связаны с выполнением асинхронных вызовов Cloud API, объединением нескольких из них, а затем выполнением пары асинхронных задач внутри SDK. Результатом было огромное количество обратных вызовов.

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

Kotlin позволяет легко скрывать внутренние классы

В Kotlin есть четыре модификатора видимости: private, protected, public и internal. Последний действительно полезен, потому что вам больше не нужно объявлять отдельные пакеты Java для вашей внутренней логики. Вы можете разделить свой код на пакеты так, как хотите, а затем сделать все внутренние классы… внутренними. Код внутри того же модуля проекта по-прежнему сможет использовать эти классы, но пользователя, использующего скомпилированный SDK, не будут беспокоить ненужные вещи.

Kotlin имеет функции расширения

Это, наверное, самая полезная вещь для меня на данный момент. Официальная документация Kotlin хорошо описывает это:

Kotlin, подобно C # и Gosu, предоставляет возможность расширять класс с помощью новых функций без необходимости наследовать от класса или использовать какой-либо тип шаблона проектирования, такой как Decorator. Это делается с помощью специальных объявлений, называемых расширениями.

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

… В это:

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

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

3. Попробуйте RxJava.

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

Реактивное программирование - замечательная концепция, позволяющая работать с асинхронной средой. Он работает с объектами, называемыми Observables, которые генерируют множество событий, которые можно изменять с помощью различных операторов. Таким образом, вы можете создать простой, понятный и сжатый поток операций, который будет обрабатываться асинхронно с любой политикой потока, которую вы хотите. О, и я уже упоминал, что здесь нет блокировок, семафоров, synchronized, wait() или игры с обработчиками Android?

Кстати, вот классная статья Джанишара Али о луперах и хендлерах, которая многое мне прояснила.

Чаще всего RxJava используется в сочетании с Retrofit 2 и GSON для доставки асинхронных сетевых вызовов. Вот пример простого вызова POST, который возвращает Completable, который является разновидностьюObservable, который указывает, что операция завершена или вызывает ошибку:

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

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

Резюме

  1. Если ваш SDK или библиотека состоит из многих модулей, подумайте о том, чтобы переместить их в отдельные библиотеки и управлять ими независимо. Создайте связывающие библиотеки («стартеры»), чтобы сгруппировать несколько модулей в одну функциональность. Это позволит вам повторно использовать общие части вашего кода и избавит вас от множества проблем в будущем.
  2. Используйте Котлин. Используйте Котлин. Используйте Котлин. О, я же сказал использовать Котлин, не так ли?
    Это круто, и скоро он станет основным языком Android!
  3. Если у вас много асинхронных вызовов, сложная облачная коммуникация, обширная бизнес-логика - рассмотрите возможность использования RxJava или RxKotlin.

«Гарольд! На кухне крыса! Сделай что-нибудь!" Бармен грустно вздыхает. «Простите меня на минутку, странник. Я продолжу рассказ вскоре после того, как победу этого мерзкого монстра в своем подземелье ». Он подмигнул вам и исчез за дверью.

Надеюсь, вы узнали что-то новое! На этом история не заканчивается. Если вам понравилось чтение, есть еще две части:

Также помните, что вы можете ознакомиться с созданным мной примером проекта:



Если у вас есть какие-либо комментарии, отзывы или вопросы - не стесняйтесь спрашивать меня! Вы можете связаться со мной по электронной почте: [email protected],
подписаться на меня в Twitter, Github или LinkedIn,
или просто задать вопросы прямо в комментариях :)

Большое спасибо:
Джону Цеслику-Бриджену - за помощь в улучшении моего английского.
Яцеку Дилонгу - за отличные фотографии Кракова.
Wojtek Wawerek - за полезные предложения и отзывы.