Поводом к появлению этого материала послужила задача из прошлого проекта. Нужно было расширить покрытие контента приложения новыми Deep Link-ами. Все звучало просто, но, когда начали тестировать решение, появились неочевидные проблемы: на разных версиях Android, оболочках, устройствах, приложениях Deep Link-и работали по-разному.
Вы знаете ответы на вопросы?
Как отработает Deep Link на ссылку, если открыть ее в SMS клиенте Honor 9 Lite (Android 9.0, EMUI 9.1.0)?
А если попытаться в Gmail на Samsung A70 (Android 10.0, One UI 2.5)?
Или попробовать Telegram на Xiaomi Mi5 (Android 8.0, MIUI Global 10.2.2.0)?
Мы знаем, потому что провели 280+ тестов и увидели все своими глазами! Еще мы узнали, как можно обработать ссылки на уровне приложения (Linkify, HTML, Span), как выполняется их диспетчеризация (Jetpack Navigation Component, airbnb/DeepLinkDispatch, Custom), в какие нюансы Android придется удариться и как эти удары смягчить :)
Надеемся, этот цикл статей поможет вам сэкономить время и силы при работе с Deep Link.
В этой части мы зафиксируем основные понятия: Deep Link, Web Link, App Link. Разберем особенности каждого вида Deep Link-ов и разницу между ними.
Содержание
Deep Link
Перед тем, как начать наше глубокое погружение, надо расставить точки над i и разобраться что же такое Deep Link?
“Deep links are URIs of any scheme that take users to your app”
developer.andoroid.com говорит, что это URI произвольной схемы, которые переносят пользователей в наше приложение. Давайте разберем это определение по частям.
“Deep links are URIs of any scheme that take users to your app”
Что такое URI? Это идентификатор ресурса. Какие виды URI есть? С этим сложнее. Основная сложность состоит в том, чтобы классифицировать URL и URN. Подробнее о “классическом” и “современном” взгляде на эти понятия можно почитать здесь.
Мы будем пользоваться классификацией с картинки: URI — идентификатор общего вида, URL и URN его подвиды, которые не пересекаются.
Но это не все. Давайте посмотрим на таблицу.
Виды идентификаторов ресурсов, их описание и примеры.
Имя |
Описание |
Пример |
Обобщенный URI для унификации многообразия идентификаторов |
xri://broadview.library.example.com/(urn:isbn:0-395-36341-1) |
|
URI с локализацией для удобства использования в не англоговорящих странах |
http://ru.wikipedia.org/wiki/Кириллица |
|
Единый идентификатор ресурса |
mailto:John.Doe@example.com |
|
Единый локатор ресурса. Указывает на место, где ресурс расположен |
file://C:\UserName.HostName\Projects\Wikipedia_Articles\URI.xml |
|
Единое имя ресурса. Независимо от его расположения |
urn:isbn:0451450523 |
|
Единая характеристика ресурса. Содержит метаинформацию о ресурсе / описание ресурса |
URN:IANA:626:oit:cs:ftp-and-telnet; URL:http://www.gatech.edu/oit/cs/ftp-and-telnet.html; Author: Arrowood, Adam; |
|
Постоянный URL. Защищает от перемещения ресурса (404) |
||
URL, способный определить схему по контексту использования |
//domain.com/img/logo.png |
|
Compact URI. Сокращенное представление URI |
[isbn:0393315703] |
Видов оказалось сильно больше. И мы уверены, что в таблице представлены не все. Давайте кратко о некоторых из них:
IRI — это URI с поддержкой разных языков мира
Uniform Resource Characteristics определяет метаданные о ресурсе
PURL — Persistent URL не подразумевает изменение (используется для избежания 404 ошибок)
Графически эту таблицу можно представить так:
Пару слов о формате URI. Он состоит из обязательных параметров:
scheme — определяет контекст / смысл идентификатора;
path — иерархические данные для идентификации ресурса.
И опциональных:
userinfo — информация о том, кто взаимодействует с ресурсом;
host — адрес расположения ресурса;
port — точка взаимодействия с host / конкретный процесс или сервис;
query — неиерархические данные для идентификации ресурса;
fragment — идентификация вторичного ресурса внутри исходного ресурса.
Подробнее о параметрах можно почитать тут и тут.
“Deep links are URIs of any scheme that take users to your app”
Следующая часть определения про схемы. Deep link — URI произвольной схемы. Схем море. Если схема зарегистрирована в IANA, то она считается официальной. Некоторые из популярных официальных схем:
geo:55.79378663446035,49.11699501723111
mailto:technokratos.com
tel:+78432776410
sms:+78432776410?body=hello%20technokratos
Другие примеры тут
Если схема не зарегистрирована в IANA, то она неофициальная. Но даже не смотря на это есть схемы, которые сыскали популярность в сообществе, например:
jdbc:mysql://host:port/database?params…
slack://open?team={TEAM_ID}
zoommtg://zoom.us/join?confno=<confno>...
Другие примеры тут
“Mobile deep links are one example of a class of unofficial URI schemes that allow for linking directly to a specific location in a mobile app.”
Есть специальных подвид неофициальных схем, которые используются для перехода в мобильные приложения. Наверное, кто-то из вас видел подобное:
myapp://example/path/to/resource
superapp://example/path/to/resource
wow://example/path/to/resource
“Deep links are URIs of any scheme that take users to your app”
Ну и наконец, deep link, в большинстве случаев, используется для редиректа пользователя в ваше мобильное приложение. Посмотрите на три примера. На каждом из них изображены сценарии перехода в мобильное приложение по ссылке.
“Users following links on devices have one goal in mind: to get to the content they want to see.”
Снова обратимся к developer.android.com. Там написано нечто подобное: “Пользователи, переходящие по ссылкам на устройствах, преследуют одну цель: добраться до контента, который они хотят видеть”. Казалось бы, вcе просто:)
Но это был бы не Android, если история закончилась вот так. Чтобы добраться до “content they want to see” нужно еще кое-что. Видели когда-нибудь такой диалог?
При нажатии на ссылку система показывает (не всегда, но об этом после) пользователю disambiguation dialog. В нем отображается список приложений-кандидатов для обработки перехода по ссылки. Список формируется в зависимости от ссылки и intent-filter установленных приложений (об этом тоже после). Кроме списка приложений, пользователю доступны две опции:
Just once (Только сейчас) открывает выбранное приложение и попросит заново выбрать из списка кандидатов при повторном нажатии. При этом система может запомнить предыдущий выбор и показать его в самом вверху диалога (возможно, для удобства пользователя).
Always (Всегда) запомнит выбор и больше не будет просить его сделать. В настройках приложения можно будет найти ссылки, которые всегда обрабатывается приложением и изменить поведение при необходимости.
Web Link
“Web links are deep links that always use the HTTP, HTTPS schema”
Теперь поговорим о web link. Это подвид deep link. Но в отличие от них, web link могут иметь только http и https схемы.
Очень важный момент: на Android 11 и ниже клик по web link может быть обработан вашим приложением, но начиная с 12 версии, web link открываются только в браузере. Это может быть связано с тем, что Google настаивает на использовании Android App Links.
Различие в обработке Web Link на разных версиях Android. На Android 11- (сверху) по клику на https-ссылку пользователю предлагается выбрать приложение. На Android 12+ (снизу) выполняется прямой переход в браузер.
Android App Link
“Android M (23) Will Support App Deep Linking Without That Annoying Selector Prompt”
В Android 6.0 появилась возможность выполнять переход по deep link без disambiguation dialog. Эта фича дает пользователю возможность не совершать дополнительных действий и сделать прямой переход в приложение, улучшая пользовательский опыт. Именно с версии 6.0 и появились Android App Link.
“Android App Links are web links that contain the autoVerify attribute”
Давайте по порядку.
Q: Почему app link это web link?
A: Потому что app link связывается с веб-доменом по протоколу http (ссылка).
https://domain.name/.well-known/assetlinks.json
Q: Зачем связываться с веб-доменом?
A: Для того, чтобы выполнить верификацию приложения, то есть указать системе, что именно это приложение ответственно за выбранный домен (“... to associate an app with a web domain they own."). Получается, после успешной верификации система знает, кому адресовать переход по ссылке и может не открывать disambiguation dialog.
https://domain.name/.well-known/assetlinks.json
Q: Как происходит связывание между приложением и веб-доменом?
A: Автоматически (“...contain the autoVerify attribute”). Посмотрим на схему. Во время установки приложения система по autoVerify атрибуту в intent-filter понимает, что необходимо выполнить верификацию. Так как приложение обычно скачивается из сети (Google Play Store, например), это самый удобный момент для выполнения http запроса на специальный ресурс (/.well-known/assetlinks.json
, который описан в спецификации Digital Asset Link). В случае успеха система выполняет ассоциацию. Подробнее здесь.
Спойлер для самых пытливых читателей
Пытливый читатель спросит: «А что если не будет интернета? Или что-то пойдет не так на сервере?». Очень резонные вопросы! Выдают ваш профессионализм. Нюансов действительно много. Подробнее читаем здесь:
А мы пока идем дальше.
Ранее было сказано, что web link не откроется на Android 12. Но web link можно превратить в app link и обойти это ограничение!
Другие фичи Deep Link
Также доступен поиск по контенту, на который есть deep link. На примере видно, что при вводе “Kenny” в поисковую строку у нас есть возможность перейти в другое приложение, для которого настроен deep link для обработки этой информации. Подробнее тут и тут.
Также deep link используются в дополнении к instant apps, делая вход в ваше приложении более нативным и прозрачным (ссылка). Посмотрите на каждый из примеров: пользователь нажимает на ссылку и сразу же переходит в ваше приложение (уменьшается порог вхождения, расширяются воронки, повышается конверсия!).
Что нам нужно было сделать?
Теперь, когда вы на 50% в теме, можем переходить к разбору задачи. Нам было необходимо реализовать переход в приложение по ссылкам:
scheme: http, https
host: domain1.domain2.ru, domain1.domain3.ru
path: /, /landing, /landing/, /path1/path2/{id}
То есть сделать так, чтобы все эти ссылки:
Тонна ссылок
http://domain1.domain2.ru
http://domain1.domain2.ru/
http://domain1.domain2.ru/landing
http://domain1.domain2.ru/landing/
http://domain1.domain2.ru/path1/path2/{id}
http://domain1.domain2.ru/path1/path2/{id}/
https://domain1.domain2.ru
https://domain1.domain2.ru/
https://domain1.domain2.ru/landing
https://domain1.domain2.ru/landing/
https://domain1.domain2.ru/path1/path2/{id}
https://domain1.domain2.ru/path1/path2/{id}/
http://domain1.domain3.ru
http://domain1.domain3.ru/
http://domain1.domain3.ru/landing
http://domain1.domain3.ru/landing/
http://domain1.domain3.ru/path1/path2/{id}
http://domain1.domain3.ru/path1/path2/{id}/
https://domain1.domain3.ru
https://domain1.domain3.ru/
https://domain1.domain3.ru/landing
https://domain1.domain3.ru/landing/
https://domain1.domain3.ru/path1/path2/{id}
https://domain1.domain3.ru/path1/path2/{id}/
стали диплинками. На это уже сложно смотреть... Проще только потерять какую-нибудь ссылку или сделать опечатки :)
Тогда мы решили придумать визуальную документацию deep link — Deep Link Tree. Она нужна для того, чтобы не забыть обработать ни один URL и сделать представление более наглядным.
Теперь переложим нашу задачу на Deep Link Tree. Изначально, наше приложение умело обрабатывать не все возможные диплинки — они все начинались с единственной https scheme, имели один host и заканчивались несколькими path.
Но нам этого оказалось мало. Мы хотели добавить обработку ссылок с http и еще один host. При этом, чтобы все это работало и не пришлось писать много однотипного кода.
Теперь посмотрим, как это выглядит в коде. Внутри интент-фильтра прописываем параметры (подробнее читайте в документации):
Сначала указываем action VIEW, чтобы наше приложение умело обрабатывать intent на просмотр контента.
Затем обязательно устанавливаем category DEFAULT для пометки активности как обработчика для событий по умолчанию.
Опционально добавляем category BROWSABLE. В этом случае мы говорим системе, что наше приложение способно обработать события, которые происходят в браузере.
Следующий обязательный тег — data. В нем описано, по каким данным фильтруется intent. Для deep link в качестве этих данных выступают части URL:
scheme
host
port
различные варианты путей: path, pathPrefix, pathPattern. Об их отличиях расскажем в следующих частях
mimeType
<activity
android:name="com.technokratos.app.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<!-- 1 -->
<action android:name="android.intent.action.VIEW" />
<!-- 2 -->
<category android:name="android.intent.category.DEFAULT" />
<!-- 3 -->
<category android:name="android.intent.category.BROWSABLE" />
<!-- 4 -->
<data android:scheme="http" />
<!-- 5 -->
<data android:host="technokratos" />
<!-- 6 -->
<data android:port="8080" />
<!-- 7 -->
<data android:path="/android/meetup" />
<data android:pathPattern="/*/meetup" />
<data android:pathPrefix="/android" />
<!-- 8 -->
<data android:mimeType="*" />
</intent-filter>
</activity>
Исходная задача описана. Можем приступать к решению! Но перед этим давайте поговорим о паре очень важных моментов.
Еще пару слов о Deep Link
Сравним URI форматы из основной спецификации и из спецификации Android. Зеленым отметим общие параметры, серым уникальные. В силу ограничений Android, мы не можем использовать параметры: userinfo, query, fragment. Поэтому URI формат урезается до нижней схемы. Обратите внимание, что в ней scheme и host стали обязательными (в отличие от scheme и path в основной спецификации).
Почему именно такой формат?
Почему именно такой формат? Есть предположение, что это сделано для того, чтобы ускорить intent-resolution в тот момент, когда система ищет приложения-кандидаты. Чем меньше атрибутов, тем меньше сравнивать, тем быстрее сравнение. Это проблема станет более ярко выраженной, когда мы будет говорить про регулярные выражения в path.
Получается, что из-за ограничений формата URI, Android не может обрабатывать все их виды. Из рассмотренных ранее примеров могут быть обработанными только: URL, PURL, Clean URL, а значит:
“Deep links are URIs of any scheme that take users to your app”
заменяется на:
“Deep links are
URIsURLs (but not all) of any scheme that take users to your app”
и классификация идентификаторов принимает вид:
Резюме
Подведем итоги. Отношения между deep link, web link, app link можно представить в виде пирамиды.
В ее основании которой лежит Deep Link — технология, пусть и самая старая, но базовая. Deep Link используется в уведомлениях, при поиске контента, в instant apps, передаче данных внутри приложения или между ними и т.д. Основная задача — перенести пользователя в мобильное приложение. Web Link нацелен на привлечение трафика из web в mobile. Поскольку http ссылки сильно распространены, именно такие ссылки дают возможность контенту шире распространяться в интернете. Android App Link — это современный и более продуманные с технической точки зрения Web Link. Нацелен на улучшение пользовательского опыта и безопасность.
Android накладывает ограничения на формат URI. Доступно всего четыре параметра для настройки:
Два обязательных: scheme, host
Два опциональных: port, path
Чтобы не допустить ошибки в ваших deep link и сделать их список более наглядным, рекомендуем обратить внимание на Deep Link Tree. Для его построения начните с определения scheme, далее соедините их с host, потом с path. Deep Link Tree может быть представлен графически, простым списком URL, XML-разметкой / программным кодом в intent-filter.
Поздравляем! Вы дочитали статью и потому можете сказать себе: “Я молодец!”. Надеемся, вам понравилось. Ответь, пожалуйста, на опрос и напиши комментарий (твои впечатления, замечания, рабочий опыт, мнение — все, что считаешь нужным). Встретимся во второй части!
Валера Петров
Android-разработчик
Ангелина Евсикова
Android-разработчица Технократии
Также подписывайтесь на наш телеграм-канал «Голос Технократии». Каждое утро мы публикуем новостной дайджест из мира ИТ, а по вечерам делимся интересными и полезными мастридами.