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 всё работает без костылей и во всех интересных нам случаях.
Полезные ссылки:
Спасибо за внимание! Буду рад обсудить в комментариях, как вы решали аналогичные проблемы.
Автор: Антон