Не так давно Яндекс.Метрика анонсировала открытый API, с помощью которого можно получить доступ практически ко всем функциям Метрики из собственной программы. Сегодня я хочу немного рассказать об использовании этого API и о том, как на его основе создать простой widget для Android-устройств.
Основы работы с API
API Яндекс.Метрики построен по принципу REST. Все чем можно управлять через API, представлено ресуром: счетчики, цели, фильтры и т.п. Операции над ресурсами (чтение, удаление, изменение) совершаются посредством HTTP запросов к серверам API Я.Метрики, каждому типу операции соответствует свой HTTP метод:
- GET: чтение содержимого
- POST: добавление ресурса
- PUT: изменение ресурса
- DELETE: удаление ресурса
Например, для получения списка счетчиков надо обратиться к ресурсу counters:
GET /counters. Для получение информации об одном счетчике, обратиться к ресурсу по его идентификатору: GET /counter/{id}. Для удаления — обратиться к тому же ресурсу, но используя метод DELETE: DELETE /counter/{id}.Входные данные для REST вызовов и результаты могут кодироваться в двух форматах: XML и JSON. В дальнейшем мы будем использовать JSON, как более компактный и удобный для отображения на структуры, использующиеся в языках программирования, формат.
Естественно, работа через API возможно только со счетчиками, к которым есть доступ из своего аккаунта Я.Метрики. Для идентификации владельца аккаунта используется OAuth авторизация. Устроена она очень просто. Разработчик, желающий воспользоваться API Я.Метрики, регистрирует свое приложение (см. инструкции) и получает идентификатор приложения. Зарегистрировав приложение, можно получить для него отладочный OAuth-токен и сразу же начать работу с API Метрики. Отладочный токен разрешит работу со счетчиками, доступными из аккаунта разработчика, зарегистрировавшего приложение. Токен надо передавать в каждом HTTP запросе, или как дополнительный параметр в URL (
...&oauth_token=<acces_token>), или как заголовок в HTTP запросе Authorization: OAuth <access_token>.Чтобы приложение могло работать с произвольными счетчиками, а не только с доступными разработчику, нужен отдельный OAuth токен для каждого пользователя приложения. Как его можно получить:
- Наиболее безопасен способ, когда пользователь перенаправляется приложением на специальную страницу Яндекса, авторизуется на ней, и (если еще не сделал этого) дает приложению доступ к своим счетчикам. После этого пользователь перенаправляется обратно в приложение, причем в URL-е, на который идет перенаправление, содержится параметр с OAuth токеном для этого пользователя. Приложение должно прочитать этот параметр и запомнить токен. Детали этой процедуры описаны в руководстве.
- Приложения также могут получить токен, непосредственно запросив у пользователя логин и пароль, передав их OAuth серверу Яндекса, и получив токен в ответ. Этот способ менее безопасен: логин и пароль запрашиваются и передаются в явном виде.
Благодаря использованию REST, простейшая работа с API (запросы на чтение) возможна непосредственно из браузера, без какого-либо кодирования. Например, этот запрос выдаст информацию о счетчике в демо-аккаунте API Я.Метрики. Такой запрос выдаст отчет по посещаемости счетчика.
Несмотря на простоту REST интерфейса, для полноценного использования API в программе, написанной на языке высокого уровня, необходимо совершить довольно много телодвижений: формирование URL-ов запросов, отправка HTTP запросов и получение результатов, формирование и парсинг JSON, обработка ошибок и т.д. Чтобы упростить жизнь, я создал готовую библиотеку для работы с API Я.Метрики на языкe Java: Metrika4j. Код библиотеки распространяется под Apache License, вы можете свободно изменять и дополнять его под ваши требования. Далее я расскажу, как с помощью этой библиотеки создать widget для Android устройств, отображающий посещаемость сайта.
Metrika4j
Metrika4j позволяет работать с Яндекс.Метрикой, оперируя привычными Java разработчику понятиями (классы, вызовы функций, типы) и не задумываясь о низкоуровневой работе с HTTP и JSON. Центральный интерфейс библиотеки — MetrikaApi. В нем есть методы для работы с главными сущностями Метрики: счетчиками и отчетами. Работа с дополнительными сущностями (цели, фильтры и т.п.) вынесена в отдельные мини-API, получаемые из главного класса
MetrikaApi.Структура методов примерно соответствует структуре REST-вызовов API Я.Метрики. Аргументы, передаваемые в REST-вызов, соответствуют аргументам, передаваемым в методы API (кроме API работы с отчетами, о котором мы расскажем отдельно).
Metrika4j поддерживает «из коробки» две библиотеки для работы с JSON: Jackson JSON processor и org.json. Для Jackson поддерживается 100% функциональности, для org.json — минимум, достаточный для работы с отчетами: чтение списка счетчиков и получение отчетов. Библиотека org.json встроена в Andriod API, поэтому её использование удобно для Android приложений. При необходимости, разработчик может воспользоваться любой другой JSON-библиотекой, имплементировав с её помощью интерфейсы JsonMapper и JsonObject.
Использование Metrika4j
Сначала необходимо создать экземпляр
MetrikaApi c помощью ApiFactory. При создании надо передать OAuth-токен пользователя:// Создаем экземпляр API, использующий демо-токен API Метрики и Jackson JSON processor MetrikaApi api = ApiFactory.createMetrikaAPI( "05dd3dd84ff948fdae2bc4fb91f13e22", new JacksonMapper());
Далее с помощью созданного экземпляра можно выполнять любые операции:
// Получаем список счетчиков в текущем аккаунте Counter[] myCounters = api.getCounters(); // Создание счетчика Counter newCounter = new Counter(); newCounter.setSite("mysite.ru"); newCounter.setName("Мой сайт"); Counter createdCounter = api.createCounter(newCounter); // В createdCounter содержится новый счетчик, загруженный из Метрики, со всем значениями полей, // проставленными Метрикой, например Id System.out.println(createdCounter.getId()); // Удаление счетчика api.deleteCounter(createdCounter.id);
Работа с отчетами
Через API доступны почти все отчеты, существующие в Метрике. Набор доступных отчетов содержится в классе Reports. Для построения выбранного отчета надо вызвать метод
MetrikaApi.makeReportBuilder(Reports report, int counterId), который вернет специальный объект — построитель отчета ReportBuilder. В построителе отчета надо задать требуемые параметры отчета (временной интервал, сортировку и т.п.). Когда все параметры будут установлены, вызывайте метод ReportBuilder.build(), который отправит запрос HTTP запрос к API Метрики и вернет отчет.// Создаем построитель отчета "популярное содержимое" для счетчика с id=2138128 ReportBuilder builder = api.makeReportBuilder(Reports.contentPopular, 2138128); // Задаём параметры отчета (отчет за неделю) и строим отчет Report report = builder.withDateFrom(MetrikaDate.yesterday()) .withDateTo(MetrikaDate.today()) .build();
Отчет возвращается в виде объекта Report, представляющего собой таблицу с результатами, плюс некоторая дополнительная информация (итоги, интервал дат, к которым относится отчет и т.п.). Каждая строка таблицы результатов — объект ReportItem, данные из которого можно получить одним из методов
getXXX(String fieldName) (аналогично получению значений из ResultSet при использовании JDBC). Имена полей и возвращаемые дополнительные данные надо уточнять для каждого отчета в документации на API Яндекс.Метрики.// Вытаскиваем результаты из отчета ReportItem[] items = report.getData(); for (ReportItem item : items) { System.out.printf("pageViews: %4d, url: %s", item.getInt("page_views"), item.getString("url")) .println(); }
Более подробное описание работы с Metrika4j содержится в Javadoc.
Виджет для Android
Имея в своем распоряжении такие мощные инструменты, как API Я.Метрики и Metrika4j, можно решать любые задачи, вплоть до создания альтернативных пользовательских интерфейсов к Яндекс.Метрике. Но в рамках этой статьи мы ограничимся более скромной целью: создадим виджет для Android, который будет показывать текущую посещаемость сайта. Исходный код виджета доступен на GitHub в проекте MetrikaWidget. Так же, как и код Metrika4j, он свободно распространяется с минимумом лицензионных ограничений.
OAuth авторизация
Начнем с создания
MetrikaApi и авторизации. Приложение работает только с одним аккаунтом, поэтому экземпляр MetrikaApi можно сделать Singleton-ом. Его код содержится в классе Globals.private static MetrikaApi api; public static synchronized MetrikaApi getApi(Context context) { if (api == null) { // Получаем сохраненный OAuth token из SharedPreferences String token = getOAuthToken(context); if (token == null) { throw new AuthException(); } else { // Используем библиотеку org.json, встроенную в Android api = ApiFactory.createMetrikaAPI(token, new OrgJsonMapper()); } } return api; }
Если приложение запускается в первый раз и OAuth token-а еще нет в SharedPreferences, надо получить его. Для этого переходим на URL запроса токена:
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://oauth.yandex.ru/authorize?response_type=token&client_id=1359488e196b4bfa92615d0885b106d4")); startActivity(intent);
Для пользователя это будет выглядеть, как открывшаяся страница «Приложение запрашивает доступ к вашим данным на Яндексе». Предположим, что пользователь успешно авторизовался и разрешил доступ. Дальше начинается интересное: как передать токен из веб-интерфейса обратно в наше приложение? Чтобы сделать это, надо зарегистрировать одну из активностей в приложении, как обработчик специфического для нашего приложения протокола (специфического — чтобы наш обработчик не пересекся с другими приложениями). В
AndroidManifest.xml укажем следующее:<activity android:name="ru.metrikawidget.AuthTokenActivity" android:label="OAuth"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="metwd" android:host="oauthtoken"/> </intent-filter> </activity>
Мы зарегистрировали AuthTokenActivity, как обработчик протокола «metwd» для псевдохоста «oauthtoken». Теперь достаточно указать Callback URI
metwd://oauthtoken в настройках зарегистрированного приложения, и после успешной OAuth-авторизации пользователя будет вызываться AuthTokenActivity. В этой активности нужно распарсить полученную строку и сохранить токен в SharedPreferences:String fragment = getIntent().getData().getFragment(); String[] parts = fragment.split("\\&"); for (String part : parts) { if (part.startsWith("access_token=")) { String token = part.substring(param.length(), part.length()); // Запоминаем полученный токен в preferences getSharedPreferences(Globals.PREF_FILE, 0) .edit() .putString(Globals.PREF_TOKEN, token) .commit(); } }
Получение данных
Код получения данных для виджета предельно прост:
MetrikaApi api = Globals.getApi(context); // Собственно запрос к Metrika API Report report = api.makeReportBuilder(Reports.trafficSummary, counterId) .withDateFrom(new MetrikaDate()) .withDateTo(new MetrikaDate()) .build(); return new Result(report.getTotals().getInt("visits"));
Нюанс в том, что этот код нельзя выполнять «в лоб». Сервера API Метрики отвечают довольно быстро, а вот сам HTTP запрос может доооолго идти до сервера и обратно по медленным и ненадежным каналам мобильной связи. В результате виджет, ожидающий ответ, «зависнет» с точки зрения Android OS, и будет выдано окошко, предлагающее аварийно завершить подвисшее приложение. Это явно не то, что нам нужно. Поэтому все запросы к API Метрики выполняются асинхронно, с помощью класса AsyncTask. Вот упрощенный код загрузки списка счетчиков (полная версия — в классе WidgetSetupActivity):
private class CountersLoadTask extends AsyncTask<Void, Void, Counter[]> { private ProgressDialog progressDialog; protected void onPreExecute() { progressDialog = ProgressDialog.show(...); } protected void onPostExecute(Counter[] counters) { progressDialog.dismiss(); counterList.addAll(Arrays.asList(counters)); // Уведомляем адаптер, чтобы он перерисовал список счетчиков listAdapter.notifyDataSetChanged(); } protected Counter[] doInBackground(Void... voids) { return Globals.getApi(WidgetSetupActivity.this).getCounters(); } }
Для виджетов работа с данными немного сложнее. Начнем с того, что виджет-провайдер, получающий события «пора обновить виджет», в терминологии Android является broadcast receiver-ом. Жизненный цикл broadcast receiver-а короток: он обрабатывает событие, после чего немедленно умирает. Если стартовать из обработчика события поток, то смерть receiver-a может наступить раньше, чем закончит работу поток: печаль. Android developer guide настоятельно рекомендует пользоваться для таких случаев сервисами. Так и сделаем: виджет-провайдер (MetrikaWidgetProvider) получает события и передаёт их для обработки в UpdateService.
UpdateService, в свою очередь, использует асинхронную загрузку данных через AsyncTask (в противном случае опять получим окошко «Application Not Responding» при длительном ожидании ответа от API).Отображение виджета
Работа с виджетами в Android — отдельная большая тема, выходящая за рамки этой статьи и хорошо освещенная как в руководстве разработчика Android, так и в дополнительных статьях. Поэтому расскажу кратко, а за деталями отошлю к исходному коду классов
MetrikaWidgetProvider и UpdateService.Виджет может находиться в трех состояниях: «данные получены», «обновление данных» и «нет связи». Обновление виджета происходит по таймеру или при клике на виджет.
- Данные получены — штатное состояние виджета, в котором он отображает количество визитов на сайте за сегодняшний день. В этом состоянии виджет отображает стандартную столбчатую диаграмму — «радугу», которую видно в шапке интерфейса Яндекс.Метрики
- Обновление данных — промежуточное состояние, сделанное исключительно для удобства пользователя, чтобы он получил визуальный feedback от клика на виджет. Виджет переходит в него перед отправкой запроса к API Метрике, и выходит после завершения запроса. В этом состоянии отображается инвертированная «радуга»
- Нет связи — состояние, указывающее на то, что актуальных данных получить не удалось. Отображается «радугой» с уменьшенной насыщенностью, почти серого цвета.
Если виджет может находиться в состоянии «нет связи», логично было бы обновлять его при появлении этой самой связи, чтобы пользователь сразу увидел актуальные данные. Для этого виджет-провайдер подписывается на системные события, связанные с изменением статуса сетевого интерфейса:
<receiver android:name="ru.metrikawidget.MetrikaWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"/> <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/> </intent-filter> ... </receiver>
При получении такого события происходит проверка, появилась ли связь, и вызывается обновление:
if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { // Изменилось состояние подключения к Internet NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); if (info != null && info.isConnected() && !info.isRoaming()) { // Если появилась связь, обновляем виджеты, которые могли быть в состоянии "offline" ... } }
Установка виджета
Виджет может быть собран из исходного кода, находящегося в проекте MetrikaWidget.
Вы не Android-разработчик, но хотите пользоваться виджетом? Нет проблем — можно загрузить готовый виджет. Релиз с Android Market или debug build c GitHub.

После установки у вас также появится приложение «Я.Метрика», которое на самом деле является мини-инструкцией к виджету.
