Введение
Раз, два, три, четыре! В прошлый раз мы выяснили вот что
Гипотеза 3. Разные оболочки ОС — ОПРОВЕРГНУТА.
Оболочка Android ОС НЕ ВЛИЯЕТ на работу Deep Link
Гипотеза 4. Приложения — ПОДТВЕРЖДЕНА.
Приложение, из которого открывается ссылка, ВЛИЯЕТ на работу Deep Link.
Как обычно не обошлось без тонкостей: оболочка может влиять на обработку, но через свои (специфичные для оболочки) приложения. У приложений есть встроенные браузеры, но даже они не повлияли на однозначный вывод – приложения влияют на работу deep link.
Сегодня мы завершаем разбор наших семи проблем. Наконец-то! Набирайте кислород. Погружаемся!
Содержание
Проблема №5. Ссылки без scheme
Ссылки вида host/path обрабатывались по-разному
Одна и та же ссылка, одно и то же устройство, но разные приложения (Messaging, Gmail):
Теперь эксперименты:
Мы уже знаем, что приложение влияет на обработку deep link. Но мы еще не пробовали переходить по ссылкам без host. Как видно выше переход по ссылке возможен, даже несмотря на отсутствие host. Предположим:
Гипотеза 5. Существует специальный механизм обработки ссылок без scheme.
Может появиться ощущение, что если прописать в манифесте схему null
, то ссылки без схемы будут работать. Давайте попробуем
Есть еще один способ тестировать deep link, помимо ручного клика по ссылкам. Для этого нужно вызвать команду adb:
adb shell am start -W -d null:developer.android.com
Эта команда запустит intent с указанным Data URI (-d
) и будет ожидать завершения запуска (-W
).
Если настроить соответствующий intent-filter, то ОС сможет понять может ли ваше приложение обработать этот intent. Мы так и сделали. Теперь давайте посмотрим на скринкаст:
На заднем фоне видно, как наше приложение запустилось. Это значит, что
android:scheme="null" — это конкретное значение scheme, а не ее отсутствие.
Если null это не значит отсутствие scheme, то может вообще ее не указывать? Интересное предположение. Настраиваем intent-filter:
Студия подсказывает, что мы не указали обязательный атрибут scheme. Но не смотря на это скомпилировать получится.
Попробуем запустить intent через adb, но в этот раз без scheme:
adb shell am start -W -d developer.android.com
В консоли видим:
Starting: Intent { dat=developer.android.com/about }
Error: Activity not started, unable to resolve Intent { dat=developer.android.com/about flg=0x10000000 }
Intent resolution не завершился успехом, даже не смотря настроенный intent-filter. Давайте кликнем на ссылку:
Обратимся к документации. Там написано, что, как минимум, нужно прописать хотя бы одну схему для интент-фильтра, иначе ни один другой атрибут не будет обработан (схожее сообщение мы видели в студии):
“...at least one scheme attribute must be set for the filter,
or none of the other URI attributes are meaningful.”
— А что если написать android:scheme=””
?
— Будет тоже самое, что и для только что разобранного примера.
После нескольких попыток у нас появилось предположение, что на обработку влияет http. Это оказалось действительно так. Посмотрите, что происходит на записи:
Давайте посмотрим на нечто более убедительно. Открываем logcat:
I/ActivityTaskManager: START u0 {
act=android.intent.action.VIEW
dat=http://developer.android.com/about
flg=0x14002000 cmp=com.android.chrome/
org.chromium.chrome.browser.ChromeTabbedActivity
(has extras)
} from uid 10116
Здесь уже не остается никаких вопросов. http scheme была подставлена в URL без scheme. Причем это сделало именно приложение, а не мы или что-либо еще! Это в очередной раз доказывает, насколько сильно приложение, из которого открывается ссылка, влияет на работу deep link. Но как так получилось? Узнаем дальше.
Обработка scheme приложением
Кажется, что developer.android.com/about как-то сам сумел превратиться в ссылку. Так ли это на самом деле. Проверить просто: добавим TextView
на экран и в android:text
укажем три вида ссылок:
Никакой магии не случилось. Ссылки не стали кликабельными. Все то сколько на нее потом не нажимай, она не будет кликабельной, если не настроен специальный обработчик.
В Android есть много способов обработать URL (и scheme, в частности):
Рассмотрим каждый подробнее на примере. Дано:
android:autoLink
Начнём с самого лаконичного способа. Атрибут android:autoLink
запускает поиск паттернов по содержимому TextView. Среди паттернов для поиска есть email, phone, map
. Константа web
, выполняет поиск URL'ов. То, что там нужно.
Добавляем autoLink c значением web в TextView:
Запускаем. Видим, что ссылка стала кликабельной. Более того, по клику запускается интент, где в data лежит URL с автоматически проставленной http схемой. Красота!
android.text.util.Linkify.addLinks
Эта статическая функция применяет регулярное выражение к содержимому TextView и выполняет поиск соответствий. Похоже на работу autoLink. В качестве аргумента addLinks
можно передать defaultScheme
, которая будет приставлена к ссылке, если та не начинается со схемы, указанной в другом параметре.
Вызываем addLinks
и передаем нашу TextView, Pattern
для WEB URL, дефолтную схему https, стандартный UrlMatchFilter
, и null TransformFilter
.
Запускаем. Результат как и в прошлом примере, но только в этот раз была подставлена https scheme.
android.text.util.Linkify.TransformFilter
Та же функция addLinks
, но в этот раз используется параметр transformFilter
, который позволяет изменять найденные ссылки.
В этот раз defaultScheme
равны null
, а в качестве transformFilter
выступает наш HttpsTransformFilter
, который добавляет префикс к URL.
Запускаем. Результат такой же, как и в прошлом примере.
android.text.Html
Если в приложении есть работа с HTML тегами, то этот способ может быть кстати. Воспользуемся статической функцией fromHtml и классом LinkMovementMethod.
fromHtml
из html-тега возвращает отображаемый текст и устанавливает ему ссылку из атрибута href
. LinkMovementMethod представит переданный ему текст как кликабельную ссылку.
Запускаем. Ссылка без scheme превратилась в кликабельную и указывает на https-ссылку.
android.text.style.URLSpan
Теперь посмотрим на механизм, который лежит в основе большинства разобранных способов обработки — spannable.
Интерфейс Spanned позволяет добавлять разметку к тексту. Одним из видов Span является URLSpan, который преобразует выбранный текст в кликабельный. По клику на URLSpan запускается Intent с ACTION_VIEW. Фильтр на этот aciton мы настраивали в нашем приложении.
Во ViewModel
мы создали SpannableString
, передали ссылку без схемы, добавили URLSpan
, указали необходимую ссылку. Так как SpannableString
не является наследником String, то меняем тип данных на CharSequence
. Обратите внимание на LinkMovementMethod
. Он триггерит запуск интента по ссылке из URLSpan
.
Запускаем. Работает как часы!
Мы разобрали три наиболее распространенных способов обработки ссылок. Есть и другие (например, Better-Link-Movement-Method), но они, как правило, используют разобранные нами API.
Все еще помните, о какой гипотезе шла речь? :) Мы проделали большую работу и смело можем заключить:
Гипотеза 5 — ПОДТВЕРЖДЕНА.
Существует специальный механизм обработки ссылок без scheme.
Проблема №6. Второй host
Мы забыли о том, что у нас есть второй домен.
У нашего проекта два хоста: один из них основной, а другой редиректит на основной. Нам заранее неизвестно, по какому из них пользователь перейдет в наше приложение. При этом рабочими должны быть оба варианта.
Сначала мы думали, что можно прописать для каждого хоста свой интент-фильтр:
Потом вспомнили, что по итогам все data-теги из одного фильтра, смерджатся и будут созданы всевозможные комбинации из их атрибутов. Не стоит забывать об этой особенности, потому что она может привести к неожиданным последствиям :)
Мы решили не поддерживать два отдельных intent-filter и переписали предыдущий код. Получилось сильно короче и проще.
Проблема №7. Перехват всех ссылок
Приложение начало перехватывать все ссылки с нашим доменом
(даже те, для которых у нас не было сценариев обработки)
Есть два ресурса: privacy-policy и terms-of-use. Подразумевается, что пользователь будет обращаться к ним через браузер (на эти ссылки мы явно не настраивали deep link). В нашем приложении нет экранов для отображения этого контента. Но как вы можете видеть на скринкасте, при переходе по ссылкам система предлагает открыть наше приложение. У нас этого контента нет, поэтому пользователь видит 404. Знатоки, внимание: вопрос: “Почему так случилось?”.
Изначально мы хотели добавить диплинки на ссылки без path. То есть ссылки, у которых есть только схема и хост (например, http://developer.android.com). Для этого мы настроили наш intent-filter так:
После этого ссылки без path начали обрабатываться нашим приложением. Это поведение мы и хотели получить. Ссылки на существующий контент также обрабатывались нашим приложением. Все как надо.
Но помимо этого появилась обработка ссылок с path на несуществующий контент, что вело к появлению 404 ошибок. Дело в том, что
Отсутствие явно указанного path подразумевает использование любого значения.
Чтобы избавиться от нежелательного поведения, нам пришлось отказаться от обработки ссылок без path. Иначе нам пришлось бы обрабатывать всевозможные вариации путей. В тот момент мы не были к этому не готовы да и особой надобности в этом не было.
Резюме
Фух! Много нового в этот раз:
Гипотеза 5 — Подтверждена. Существует специальный механизм обработки ссылок без scheme.
Для тестирования deep link иногда удобно пользоваться командой: adb shell am start -W -d “Data URI”.
android:scheme="null" — это конкретное значение scheme, а не ее отсутствие.
android:scheme="" или отсутствие этого атрибута приведет к тому, что ни один другой атрибут не будет обработан и deep link работать не будет.
android:autoLink — короткий и простой способ сделать URL кликабельной ссылкой.
Linkify.addLinks функция, которая применяет регулярное выражение к содержимому TextView и выполняет поиск соответствий и имеет параметры для обработки.
android.text.Html.fromHtml преобразует html-теги в Spanned, URLSpan в частности.
android.text.style.URLSpan специальный вид span, который преобразует текст в кликабельный и запускает intent с ACTION_VIEW и Data URL по клику.
android.text.method.LinkMovementMethod обрабатывает нажатие по тексту-ссылке.
Data-теги мерджатся между собой. По итогу получаются всевозможные комбинации из значений. Например: http, https, host1, host2 преобразуются в http://host1, http://host2, https://host1, https://host2.
Аккуратнее с ссылками без path: его отсутствие в <data /> понимается ОС как возможность передавать любое значение. Ваше приложение может начать обрабатывать то, что не должно.
На сегодня это все. В заключительной части серии мы разберем несколько суперважных нюансов:
Почему path паттерны такие слабые
Как происходит диспетчеризация URL?
Почему Jetpack Navigation Component не так крут, как кажется?
Как мы кастомизировали обработка deep link.
И почему очень важно помнить про обратную совместимость deep link.
Скоро увидимся!
Валера Петров
Android-разработчик. TG: @valeryvpetrov
Ангелина Евсикова
Android-разработчица Технократии. TG: @Angelina_dev
Также подписывайтесь на наш телеграм-канал «Голос Технократии». Каждое утро мы публикуем новостной дайджест из мира ИТ, а по вечерам делимся интересными и полезными мастридами.