Конвеєрне виробництво Android додатків

Багато розробники стикаються із завданням створення кастомізованих додатків. Наприклад, розробка декількох версій одного додатка або зміни стандартного додатка під вимоги замовника. Ми в Rambler&Co зіткнулися з такою задачею при розробці Rambler каси і її брендованих версій під окремі кінотеатри. У цій статті розглянемо еволюцію архітектури такого додатка, а також інструменти, які полегшують нам життя.



Постановка завдання

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

Цілі і конверсія
Змінені програми позитивно впливають на конверсію. Основна мета користувачів у програмі – перегляд розкладу улюбленого кінотеатру і покупка квитків. Наявність додатків дозволяє скоротити кількість кліків від відкриття до натискання кнопки купити квиток на 2-3 кліка. А як відомо, кожен клік це -N% користувачів. Так само є можливість доносити до користувачів інформацію про акції і знижки.

Архітектура

Зміни стосуються UI, основні сутності і функціонал залишаються без змін. Фільтрація джерел не потрібно, виконується з боку API.

Спочатку було завдання зробити одну брендовану версію, плани з розвитку були досить туманні. Розгалуження за версіями було через if. Приблизно так:

if (TYPE == KASSA1) {
return 1;
} else if (TYPE == KASSA2) {
return 2;
} else {
return 3;
}

Або через switch:

switch (TYPE) {
case KASSA1:
return 1;
case KASSA2:
return 2;
default:
return 3;
}

Рішення дуже погане, безліч недоліків і майже немає переваг. Вся розробка в одній кодовій базі, величезна кількість коду, складна налагодження і дебаг, проблема деплоя. Вся гама емоцій на одній картинці:
IF, IF EVEREWHERE

Flavors
Наступного гілкою еволюції стало використання Gradle Flavors + build Types
Створюються окремі версії додатків, перемикання між ними відбувається в віконці build types, паралельно можна задавати конфігурації, наприклад: debug, release, manager, testOrder і так далі. Кожна конфігурація містить свій код і ресурси. Однією з неприємних особливостей flavors є складна навігація по коду в неактивній гілці. Приклад конфігурації:

productFlavors {
kassaCinema1 {
applicationId "ru.rambler.cinema1"
versionName "1.3"
}
kassaCinema2 {
applicationId "ru.rambler.cinema2"
versionName "1.1"
}
}

Основні можливості:
  • — Окремі ресурси
  • — Окремий код
  • — Легка збірка
Недоліки:
  • —Відсутність можливості перевірки коректності інших гілок
  • Складність навігації при великій кількості гілок.
Більш докладно можна почитати: tools.android.com/tech-docs/new-build-system/user-guide і тут: developer.android.com/tools/building/configuring-gradle.html
Для більшості додатків даного функціоналу повинно вистачати, проте із-за можливості активної роботи тільки в одній гілці, а також незручності при великій кількості версій, довелося відмовитися від цієї системи на користь SDK або android library.

«SDK або додаток – бібліотека»
Наступним кроком, став перенесення основного коду в SDK. Основне додаток створюється як бібліотека, т. е. у build gradle змінюємо:

apply plugin: 'com.android.application'

на

apply plugin: 'com.android.бібліотека'

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

Реалізація
Вся логіка брендованих додатків знаходиться в головній фабриці. Саме вона відповідає за тонку настройку всієї програми. Ініціалізація у Application.

public class KassaApp extends Application {
private MainFactory mainFactory;

@Override
public void onCreate() {
super.onCreate();
mainFactory = createMainFactory()
}

protected MainFactory createMainFactory() {
return new MainFactory();
}
}

Приклад Application в кастомном додатку:

public class CustomApp extends KassaApp {
@Override
protected MainFactory createMainFactory() {
return new CustomFactory();
}
}

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

public class MainFactory {

public FragmentManager createFragmentManager() {
return new FragmentManager();
}

public UIManager createUIManager() {
return new UIManager();
}

public String getLatLng() {
return LocationSettingsManager.getInstance().getLatLngParams();
}

public String getCustomUrl() {
return getString(R. string.custom_url);
}

public Place getCustomPlace() {
throw new IllegalStateException(getString(R. string.error_illegal))
}
}

Зміна view допомогою менеджерів

public class UIManager {
public boolean hasHeaderLocation() {
return getBoolean(R. bool.config_header_location_enabled);
}

public boolean hasPosterSearch() {
return getBoolean(R. bool.config_poster_search_enabled);
}
}

Приклад використання:

if (!uiManager.hasHeaderLocation()) {
disableHeaderLocationView();
}

За роботу з фрагментами відповідає FragmentManager

public class FragmentManager {

public Fragment getOneCinemaFragment() {
throw new IllegalStateException(getString(R. string.error_illegal)); 
}

public Fragment getNavNowFragment() {
return new NavNowFragment();
}


public Fragment getNavSupportFragment() {
return new NavSupportFragment();
}
}

При додаванні елементів характерних для всіх типових кінотеатрів, ми додаємо їх в основний проект
При додаванні нетипових елементів, ми використовуємо підміну фрагментів

WebView
У разі, якщо замовник хоче мати можливість динамічно змінювати інформацію в додатку, ми додаємо webView з вшитими посиланнями. За роботу з webView відповідає окремий фрагмент, просто підміняємо посилання.

<string name="custom_url_news">http://cinema1.ru/news/</string>

Вміст і оформлення сторінки залишається за замовником.

Приклад меню з 4 елементами – webView (кинобар, реклама, оренда залів, акції).


Дизайн

Дизайн в даному процесі один з найбільш трудомістких моментів, за домовленістю дизайн отрісовиваємих за прикладом основною версією, один-в-один. Всі елементи поставляються в такому ж форматі, з такими ж іменами файлів, таким же розташуванням елементів. У підсумку необхідно просто скопіювати каталог res-drawable. Така ж ситуація з кольорами та шрифтами.

<resources>
<color name="kassa_main_color">#1aa0f0</color>
<color name="kassa_delimiter">#bbe2f9</color>
<color name="kassa_clickable">#E3E3E3</color>
<color name="action_color_active">#1aa0f0</color>
<color name="action_color_pressed">#bbe2f9</color>
</resources>

Вся інформація щодо кінотеатру зберігається в ресурсах:

<string name="support_custom_email ">cinema1@cinema1.ru</string>
<string name="app_name">Cinema 1</string>
<string name="support_custom_phone">+7 495 777 77 77</string>

Аналогічно зберігається інформація про видимість елементів, дизайні та інше.

Приклад зміни програми тільки одними ресурсами



Майстер створення додатків.

Як видно, всі налаштування для окремого кінотеатру (при відсутності окремих фрагментів), виносяться в resource файли. Дизайн також підміняється на рівні картинок і ресурсів.
Це дозволяє створювати додатки для окремих кінотеатрів з допомогою майстер (звичайний додаток) який візьме зразок проекту, перезапише налаштування ключів, кольори, назви та іншу інформацію, покладе в потрібну директорію і додасть в загальний проект. Нам залишиться тільки скомпілювати проект і викладе в гугл плей (хоча навіть і це можна автоматизувати).

Тестування

Для автотестів використовується Robolectric, для UI — espresso.
Для тестування використовується fabric.io (ex Crashlytics). Для інтеграції в додаток використовується плагін для android Studio. Заливка на сервер з допомогою команди gradle. Параметри для тестування:

ext.betaDistributionReleaseNotes="Release Notes for this build."
ext.betaDistributionEmails="BetaUser@y.com, BetaUser2@y.com"
ext.betaDistributionGroupAliases="my-best-тестерів"

Тести для додатків виконуються автоматично на Jenkins, при успішному проходженні тестів, зборка автоматично йде в fabric. Більш детально про інструментарій тестування, можна почитати тут:
habrahabr.ru/company/rambler-co/blog/266837
Доступний аналіз покриття коду тестами і успішності виконання

Публікація на Google play

При великій кількості додатків, публікація займає досить тривалий час. У даний момент програми публікуються вручну, але розглядається можливість використання Publishing API developers.google.com/android-publisher. За заявами розробників, це апі дозволяє:
  • —Завантажувати нові версії
  • Змінювати статус програми (альфа, бета)
  • —Змінювати інформацію про програму

Висновок

У даній статті ми розповіли про розвиток архітектури Rambler каси, від if-ів до повноцінного майстра створення додатків. Виділення основного коду в бібліотеку або sdk, використання майстра, внесення змін з допомогою ресурсів та адаптація дизайну дозволяють спростити створення брендованих додатків.

Спасибі за увагу!

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.