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", "failure loading container");}else{ GAExperimentsUtil.setContainerHolder(containerHolder, MainActivity.this); Log.i("Loger", "Container Available");} initFragments();}}, 2, TimeUnit.SECONDS);
Если вкратце, то здесь все очень просто, инициализируем TagManager, создаем объект PendingResult и загружаем в него контейнер tagManager.loadContainerPreferNonDefault, в калбек в итоге нам вернется ContainerHolder. Проверяем, получили ли мы адекватный ответ, если да — сохраняем холдер в DataLayer для удобства последующего использования в любой части кода. Инициируем приложение с полученным контейнером. В данном варианте "failure loading container" мы получим лишь в том случае, если не выполняется условие активации макроса, или же если мы забыли положить файл контейнера в директорию 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 наблюдать за течением эксперимента и достижением целей по каждому из тестовых вариантов.