Android-приложения являются отражением сайта или сервиса и зачастую представляют собой сходный функционал в удобной оболочке. Из-за этого становится насущным вопрос навигации между страничкой в вебе и установленным клиентом. Для решения этой проблемы были изобретены диплинки (deeplink). Под катом вас ждёт увлекательная история о том, как мы внедряли их у себя и обрабатывали случай, когда у пользователя ещё не было установлено наше приложение.

Диплинки были придуманы так давно, что сейчас уже сложно представить приложение без них. Сама по себе технология не требует свежего Android API, однако если допиливать App Indexing, то можно столкнуться с тем, что работает оно с API 17.

Вернёмся к диплинкам. Их конфигурация представляет собой набор настроек для intent-filter в манифесте приложения, которые описывают паттерны поддерживаемых ссылок.

Например:

<intent-filter>

…

<data
  android:host="best.memes"
  android:pathPrefix="/memes"
  android:scheme="http" />

<data
  android:host="best.memes"
  android:pathPrefix="/jokes"
  android:scheme="https" />

…

</intent-filter>

После этих нехитрых манипуляций при каждом нажатии на ссылку, удовлетворяющую настройкам фильтра, пользователю предлагается выбор между несколькими приложениями, в том числе и вашим. Далее активити, для которой мы задали intent-filter, получит Intent, содержащий в себе линк. Если достать его методом Intent#getData и распарсить необходимые параметры, то можно направить пользователя сразу в интересующий раздел.

После реализации может возникнуть вполне резонный вопрос: что делать, если у пользователя ещё нет приложения? Ответом будут особые диплинки, которые в этом случае умеют направлять человека в Маркет. При должном усердии такую ссылку можно генерировать самим, но нет никаких гарантий, что она будет работать со всеми браузерами и на всех версиях Android. Сейчас довольно много сервисов, предлагающих решение по крайней мере части этих проблем, например, AppsFlyer с их OneLink или Firebase с DynamicLink. Все они работают примерно одинаково, только DynamicLink использует для обработки диплинков предустановленные сервисы Google.

OneLink


Сам по себе OneLink ведёт на серверы AppsFlyer; они определяют, с какого устройства пользователь вышел в сеть, и перенаправляют его на соответствующий адрес. Можно задать редиректы для десктопа, Android и iOS. Когда Android-приложение установлено, линк прилетает в него через Intent как обычный диплинк. Когда приложения нет, в работу вступают Google Chrome и Google Play.

Наличие приложения проверяется браузером. У Chrome есть спецификация особого формата ссылок, которые потом конвертируются им в Intent и отправляются в систему. Она предусматривает задание ссылки на Google Play в случае, если приложение не установлено. Подробнее с ней можно ознакомиться тут.

Вообще в Google Play можно передать ссылку на приложение таким образом, чтобы после установки и запуска он прокинул часть её дальше. Это реализуется с помощью query-параметра url и будет выглядеть примерно так:

play.google.com/store/apps/details?id=memes.best&url=https%3A%2F%2Fbest.memes%2Fjokes

В этом случае best.memes/jokes попадёт внутрь приложения после его установки в виде диплинка. По умолчанию AppsFlyer работает не так: он предлагает получить ссылку через интерфейс библиотеки. Сам диплинк при этом, видимо, передаётся в приложение через серверы сервиса.

AppsFlyerLib.getInstance().init(KEY, new AppsFlyerConversionListener() {
  @Override
  public void onInstallConversionDataLoaded(final Map<String, String> map) { }
 
  @Override
  public void onInstallConversionFailure(final String s) { }
 
  @Override
  public void onAppOpenAttribution(final Map<String, String> map) { }
 
  @Override
  public void onAttributionFailure(final String s) { }
}, mContext);

Это очень неудобно, потому что, во-первых, мы не можем понять наверняка, надо ли нам ждать какие-то параметры или пользователь просто тыкнул в иконку и параметров не будет. Во-вторых, мы хотим сразу открывать нужный раздел приложения, без лишних блокировок и ожиданий. AppsFlyer же предлагает открывать главный экран, а когда пришли (и если пришли) параметры, то редиректить. Нас такой подход не устроил, поэтому мы сгенерировали свой url в Google Play с параметром для случая, когда пользователь переходит по диплинку с Android-устройства и у него нет приложения. Его мы задали в Onelink, чтобы получать диплинк в приложении без необходимости дожидаться библиотеку.

OneLink работал отлично, пока мы не попробовали пошарить его в Slack. Дело в том, что он открывает ссылки в своём встроенном браузере через Chrome Custom Tabs. Если коротко, то это вкладка браузера, которая открывается в процессе вашего приложения и может быть кастомизирована, чтобы не выбиваться из общего стиля (подробнее можно почитать тут). В этом случае откроется веб-версия Google Play и диплинк в приложение после установки проброшен не будет. Аналогично браузер ведёт себя, если руками скопировать OneLink в адресную строку и перейти по ссылке. Об этом случае разработчики Chrome писали в Release Notes несколько версий назад. Суть в том, что при таком подходе в браузере не срабатывает редирект в Google Play, когда приложение не установлено, и пользователь остаётся в вебе. Силами OneLink побороть это поведение не удалось, поэтому мы обратились к DynamicLink.

DynamicLink


Глубокая интеграция Google Play Services в систему позволяет им оптимизировать проверку наличия целевого приложения на устройстве. Это довольно закрытая экосистема, поэтому досконально разобраться в принципах её работы не удалось, однако всё указывает на то, что Chrome открывает активити с прогрессом, принадлежащую Google Play Services, которая определяет, как ей поступить с диплинком. После этого либо происходит редирект либо в Google Play, либо в приложение. При этом диплинк потом попадает в приложение через Intent, то есть без дополнительных библиотечных костылей.

Субъективно, такой подход функционирует не быстрее, чем OneLink, однако он работает при открытии ссылки в Chrome Custom Tabs, что является существенным преимуществом, потому что их используют многие приложения.

Кроме прочего, Firebase позволяет посмотреть схему работы ссылки и куда редиректится пользователь на каждой платформе в каждом случае. Выглядит это примерно так:


Выводы


В качестве подведения итогов я подготовил сводную таблицу. Я думаю, что под OneLink можно понимать любое конкурентное решение, потому что доступ к Google Play Services есть только у DynamicLink, соответственно, каких-то значимых различий между другими сервисами быть не должно.
OneLink. Целевое приложение установлено OneLink. Целевое приложение НЕ установлено DynamicLink. Целевое приложение установлено DynamicLink. Целевое приложение НЕ установлено
Ссылка открывается системой (ACTION_VIEW) + Пришлось «закостылить», чтобы получать диплинк сразу на старте + +
Ссылка открывается в Chrome Custom Tabs - - + +
По ссылке нажимают в браузере + Пришлось «закостылить», чтобы получать диплинк сразу на старте + +
Ссылку копируют в адресную строку - - + +

Из таблицы видно, что в реализации с DynamicLinks всё работает без костылей и во всех интересных нам случаях.

Полезные ссылки:


Спасибо за внимание! Буду рад обсудить в комментариях, как вы решали аналогичные проблемы.

Комментарии (5)


  1. ulman95
    28.11.2019 12:13

    Вроде как для DynamicLink копирование в андресную строку также не работает

    На схеме стрелочка ведет в плей стор
    firebase.google.com/docs/dynamic-links/debug


    1. FlashLight13 Автор
      28.11.2019 12:16

      На практике открывается либо стор, либо прложение. На схеме они немного привирают


  1. LaoAx
    28.11.2019 20:02

    Мне бы очень помог какой-никакой пример использования этого dynamic link.
    Интент можно упрятать в href, это я понял.
    А как выглядит этот гугл линк и как его правильно вставить?


    1. FlashLight13 Автор
      29.11.2019 11:13

      Для создания Dynamic link надо сперва завести ее в консоли Firebase, чтобы получить домен. Там можно заморочиться и использовать свой или дефолтный с постфиксом «page.link». После этого есть много вариантов, как собрать ссылку. Например, можно сделать ее руками


    1. 402d
      30.11.2019 21:23

      deep-link предназначен для случаев, когда пользователь может увидеть данные
      как через обычный броузер, так и получить более расширенный функционал в вашем приложении. Например, страница товара в интернет магазине.

      Если же требуется, чтобы приложение Выполнило действие, по ссылке с сайта(-ов),
      то больше подходят свои custom URI схемы или уже существующая схема intent:
      intent:данные_для_приложения#Intent;component=...;package=.....