Pull to refresh

Пишем виджет, использующий API Яндекс.Метрики

Reading time 10 min
Views 13K
Не так давно Яндекс.Метрика анонсировала открытый 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 токен для каждого пользователя приложения. Как его можно получить:
  1. Наиболее безопасен способ, когда пользователь перенаправляется приложением на специальную страницу Яндекса, авторизуется на ней, и (если еще не сделал этого) дает приложению доступ к своим счетчикам. После этого пользователь перенаправляется обратно в приложение, причем в URL-е, на который идет перенаправление, содержится параметр с OAuth токеном для этого пользователя. Приложение должно прочитать этот параметр и запомнить токен. Детали этой процедуры описаны в руководстве.
  2. Приложения также могут получить токен, непосредственно запросив у пользователя логин и пароль, передав их 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.

image

После установки у вас также появится приложение «Я.Метрика», которое на самом деле является мини-инструкцией к виджету.
Tags:
Hubs:
+63
Comments 12
Comments Comments 12

Articles