A/B тестування в Android-додатках

Напевно, у кожного розробника/замовника виникало по кілька ідей реалізації того чи іншого функціоналу, інтерфейсу або чогось ще, що він міг придумати. Проблема в тому, що одразу вибрати підходящий варіант реалізації для основної маси користувачів, м'яко кажучи, проблематично... І якщо в уже реалізованому проекті ще можна на основі аналітики припустити про вподобання своїх користувачів, то на старті залишається лише гадати. Але в такому нелегкому виборі нам допоможе проведення A/B тестування.
A/B тестування — це надзвичайно необхідний інструмент сучасного маркетингу, він дозволяє визначити, який варіант UI є найбільш ефективним для більшості користувачів. А ще з допомогою цього інструмента можна балансувати різного типу константи у ваших додатках, наприклад, в іграх можна проводити балансування рівнів складності.
Я довго шукав спосіб реалізації цього виду тестування для мобільних додатків, навіть намагався побудувати велосипед, а все тому, що, як мені здається, Google приховує можливості своїх сервісів у купі застарілої документації та подібного. Коли мені нарешті вдалося знайти матеріали по Google Tag Manager, щастю моєму не було меж. Він запропонував стільки нових можливостей, що стало навіть страшно.
Google Tag Manager дає можливість змінювати налаштування додатка, не вдаючись до змін у коді та подальшої компіляції і розповсюдження проекту між тестерами/користувачами. Це здійснюється завдяки так званим «контейнерам», які зберігають кілька варіантів карт з налаштуваннями, і одна з них обов'язково є дефолтною. Додаток при першому старті, маючи доступ до мережі, підтягує ті налаштування, які будуть відповідати одній з тестових груп користувачів. Користувачів можна ділити на групи в процентному співвідношенні, а також вказати відсоток користувачів, які братимуть участь у тестуванні, від загальної кількості користувачів ресурсу або додатка.
Як же нам інтегрувати це благо в проект?
По суті, не так вже й складно. Для початку проведемо певні маніпуляції зі своїми акаунтами. Щоб відстежувати результати експерименту, нам знадобиться акаунт Google Analytics, для реалізації самого контейнера потрібен акаунт Google Tag Manager. У Google Analytics нам потрібно створити представлення для свого додатка (якщо це раніше не було зроблено) і визначити цілі для експерименту в розділі «Цілі». По суті, це все, що стосується аналітики.
Далі авторизуємося в Google Tag Manager і створюємо акаунт, під яким у подальшому будуть створюватися контейнери для наших додатків. Уже в акаунті створимо контейнер:
По завершенні нам запропонують скористатися застарілим sdk, ми люб'язно від нього відмовимося, адже всі інструменти для роботи з контейнером вже впроваджені в Google Play Services lib, яку ми будемо використовувати у своєму проекті.
Перш за все, нам потрібно встановити зв'язок між своїми акаунтами. У подальшому це дозволить використовувати цілі з акаунта Google Analytics.
Встановивши зв'язок, можна приступати до налаштування макросу. Макрос, у даному випадку, є набором правил, згідно з якими наш контейнер стає доступним для користувачів, або деактивується відповідно до правил розподілу та варіацій даних.
Давайте створимо простий макрос для уявної гри. Припустимо, він буде ділити нашу величезну аудиторію на групи в рівних пропорціях, надаючи їм при цьому різні налаштування балансу супротивників:
Повільні, але витривалі вороги
slow_hard {'enemy_speed':'20'}{'enemy_life':'8'}
Швидкі, але слабкі вороги
fast_easy {'enemy_speed':'80'}{'enemy_life':'2'}
І для любителів випробовувати свої нерви, швидкі і витривалі вороги
fast_hard {'enemy_speed':'80'}{'enemy_life':'8'}
Відповідно, кожна група користувачів буде по-різному реагувати на наданий їм варіант балансування гри. Проаналізувавши їхню поведінку на прикладі досягнення тих чи інших цілей (наприклад, проведений час у грі або повернення користувачів), ми можемо визначити найкращий варіант, який найбільше відповідатиме нашим цілям.
Далі потрібно створити правило, за яким буде активуватися макрос — тобто перевірка на версію, з якою у нас передбачена підтримка контейнера в додатку.
Додатково вказуємо параметри нашого експерименту — відсоток від загальної кількості користувачів, які братимуть участь в експерименті, правило розподілу трафіку та інші.
Ну от, контейнер готовий, тепер його потрібно якось доставити до кінцевих користувачів, і для цього потрібно створити версію готового контейнера.
Створивши версію контейнера, в ній вже нічого змінити не можна, версія готова до публікації, тому завантажуємо дефолтний варіант для оффлайнового використання, натиснувши кнопку «Завантажити», і публікуємо версію контейнера.
Таким чином, у нас все готово для інтеграції контейнера в додаток.
Щоб почати роботу з контейнером безпосередньо в додатку, нам знадобиться бібліотека Google Play Services lib та файл самого контейнера, завантажений після створення його версії. Одразу створимо папку raw у директорії ресурсів нашого проєкту та завантажимо туди файл контейнера.
Як це працює? Користувач встановлює додаток на пристрій, в ньому вже присутній контейнер з дефолтними налаштуваннями. Якийсь код намагається звернутися до сервісу, щоб отримати налаштування контейнера. Під час виконання запиту користувач отримує оновлення контейнера з відповідними налаштуваннями, при відсутності мережі або за інших обставин у додатку буде використовуватися дефолтний контейнер, який ми поклали в ресурс raw. Власне кажучи, ось цей якийсь умовний код:
(CONTAINER_ID можна знайти у себе в консолі в розділі версій контейнерів.)
TagManager tagManager = TagManager.getInstance(this); tagManager.setVerboseLoggingEnabled(true); PendingResult<ContainerHolder> pending = tagManager.loadContainerPreferNonDefault(GAExperimentsUtil.CONTAINER_ID, R.raw.gtm_default_container); pending.setResultCallback(new ResultCallback<ContainerHolder>(){ @Override publicvoid onResult(ContainerHolder containerHolder){if(!containerHolder.getStatus().isSuccess()){ Log.e("Loger", "не вдалося завантажити контейнер");}else{ GAExperimentsUtil.setContainerHolder(containerHolder, MainActivity.this); Log.i("Loger", "Контейнер доступний");} initFragments();}}, 2, TimeUnit.SECONDS);
Якщо коротко, то тут все дуже просто, ініціалізуємо TagManager, створюємо об'єкт PendingResult і завантажуємо в нього контейнер tagManager.loadContainerPreferNonDefault, у колбеку в результаті нам повернеться ContainerHolder. Перевіряємо, чи отримали ми адекватну відповідь, якщо так — зберігаємо холдер у DataLayer для зручності подальшого використання в будь-якій частині коду. Ініціюємо додаток з отриманим контейнером. У даному варіанті "не вдалося завантажити контейнер" ми отримаємо лише в тому випадку, якщо не виконується умова активації макроса, або ж якщо ми забули покласти файл контейнера в директорію raw.
Наступний клас написаний для зручності роботи з холдером контейнера:
publicclass GAExperimentsUtil {publicstaticfinalString CONTAINER_ID ="GTM-ХХХХХХ";publicstaticfinalString TESTING_GROUP_TYPE_KEY ="balans_type"; publicstaticfinalString TESTING_GROUP_SLOW_HARD ="slow_hard";publicstaticfinalString TESTING_FAST_EASY ="fast_easy";publicstaticfinalString TESTING_FAST_HARD ="fast_hard"; privatestatic ContainerHolder containerHolder; /** * Utility class; don't instantiate. */private GAExperimentsUtil(){} publicstatic ContainerHolder getContainerHolder(){return containerHolder;} publicstaticvoid setContainerHolder(ContainerHolder c, Context context){ containerHolder = c;// Put the image_name into the data layer for future use. TagManager.getInstance(context).getDataLayer().push(TESTING_GROUP_TYPE_KEY, getType());} privatestaticString getType(){return containerHolder.getContainer().getString(TESTING_GROUP_TYPE_KEY);}}
Для отримання значення звертаємося до раніше записаного DataLayer з будь-якої частини коду:
TagManager.getInstance(this).getDataLayer().get(GAExperimentsUtil.TESTING_GROUP_TYPE_KEY);
Ось і все. Таким чином, ми можемо отримати будь-яке потрібне нам значення та ініціалізувати на його основі додаток, а в Google Analytics спостерігати за ходом експерименту та досягненням цілей по кожному з тестових варіантів.