Сюда дошли лишь самые стойкие… Для тех кто впервые:
HTTP/1.1 303 See other
Location_1:
https://habr.com/ru/post/686990/
Location_2:
https://habr.com/ru/post/691220/
Location_3:
https://habr.com/ru/post/693368/
Location_4:
https://habr.com/ru/post/698094/
Мы уже поняли, что deep link это механизмы для навигации в Android ОС. Его можно использовать как внутри одного приложения, так и между несколькими. Всей этой навигацией заправляет Android ОС через intent resolution. Нам (разработчикам) лишь остается закинуть удочку (настроить intent-filter) и ждать поклевки (наших пользователей :)). На исход этой рыбалки может повлиять Android 8.0, предустановленные вместе с оболочкой приложения, разработчики приложений (из которых открывается ссылка), Android 12.+ (если не настроил App Link) да и мы сами. Как еще можно навредить себе?
Содержание
Нюансы. Ссылки без port
Ранее мы писали, что в data
теге можно прописать атрибут port
. Давайте пропишем его манифесте
Теперь попробуем покликать на ссылки:
Теперь удалим port из data тега в intent-filter
Как думаете, что получится? В прошлой статье мы разбирали пример, когда приложение начало перехватывать переходы по всем ссылкам (с одинаковыми scheme, host). Это произошло из-за того, что мы не указали path. Посмотрим, не подвела ли нас логика:
Не подвела!
Отсутствие явно указанного port означает, что может быть использовано любое значение.
Нюансы. Mime type
Теперь разбираемся с mimeType. Допустим, пользователь хочет открыть ссылку с картинкой в мобильном приложении. Для этого явно укажем mimeType в теге data.
Компилируем, устанавливаем, открываем приложение…
Видно, что после нажатия на ссылку, система перенаправила нас в браузер, а не в наше приложение. Возможно, это связано с тем, что для intent-filter ссылка это строка. А по строке ней нельзя точно определить mime type.
Для тех, кому все-таки хочется открыть ссылку из примера выше
Можно схитрить и указать путь к изображению в path:
Нюансы. Ссылки c параметризованным path
Мы хотели обработать вполне стандартный кейс: получить идентификатор из ссылки на детальную информацию о сущности (например, из http://example.com/user/1 достать 1).
Для этого можно настроить intent-filter одним из представленных способов. В этом случае pathPattern
и pathPrefix
имеют равносильный эффект.
<!-- Способ первый: pathPattern с произвольным содержимым после /about/ -->
<data
android:host="example.com"
android:pathPattern="/user/.*"
android:scheme="http"/>
<!-- Способ второй: pathPrefix на фиксированную часть URL, а дальше может быть что угодно -->
<data
android:host="example.com"
android:pathPrefix="/about/"
android:scheme="http"/>
Что мы получили?
Ссылки с валидными id (http://example.com/user/1, http://example.com/user/2…) начали обрабатываться
Ссылки с невалидным id (http://example.com/user/1ab2) начали перехватываться приложением
Ссылки без id (http://example.com/user/) также начали попадать в приложение
Почему так случилось? Атрибут pathPrefix выполняет мэтчинг (как следует из названия) лишь префикса URL. Такой ограниченный функционал не позволяет правильно обработать idшник (динамическую части path). Из-за этого появляются описанные выше проблемы.
C android:pathPattern
дела обстоят получше. В нем можно использовать .
и *
. Работают они точно также, как и в регулярных выражениях. К сожалению, этими двумя символами разработчики Android и ограничились.
Почему pathPattern поддерживает так мало специальных символов?
Возможно, это сделано для того, чтобы ускорить intent resolution. Представьте, что у вас 50 приложений и в каждом настроено по 5 intent-filter и вот вы переходите по ссылке… И Android начинает проходиться по 250 intent-filter. А теперь представьте, что в каждом написан pathPattern с такой здоровенной регуляркой… Поиск может затянуться…
Но даже с .
и *
можно обыграть некоторые сценарии:
URL с фиксированным количеством символов. Например,
pathPattern=”/user/..”
обраработает http://example.com/user/11 (положительно), http://example.com/user/1 (отрицательно), http://example.com/user/11а (отрицательно), http://example.com/user/1a (ложноположительно)URL изменяющейся частью в середине path. Например,
pathPattern=”/user/.*/books”
обработает http://example.com/user/11/books (положительно), http://example.com/user/blabla/books (ложноположительно), http://example.com/user/books (отрицательно)
Нюансы. Обработка сложных path на примере Jetpack Navigation
Неужели ситуация настолько безнадежна? Неужели нет библиотеки, которая сможет все это красиво обработать? Есть! Jetpack Navigation Component может обработать типизированные аргументы в path и query параметры!
Это глава не называлась бы “нюансы” если бы все вот так просто закончилось :) Давайте откроем смердженный манифест:
Кто-нибудь видит типизированные аргумент? :) Мы нет. К сожалению, чуда не случилось. В intent-filter были проставлены старые-добрые data теги со стандартными атрибутами.
Тогда как Navigation Component их обрабатывает? Дело в том, что обработка URL (или его диспетчеризация) проводится в 2 этапа.
Диспетчеризация URL
На первом (внешнем) уровне операционная система выполняет фильтрацию согласно настройкам из intent-filter приложений. Именно здесь происходит intent-resolution. И именно для этого мы настраивали intent-filter в своих приложениях.
Далее URL передается в выбранное на внешнем уровне приложение-обработчик. И именно в приложении начинается внутренняя диспетчеризация. Тут мы решаем что делать: открыть экран, залогировать intent и прочее.
URL dispatcher на уровне Android ОС не может быть изменен разработчиком приложения (если вы не разработчик Android :)). Поэтому здесь приходится довольствоваться тем, что есть. Что касается внутреннего URL dispatcher, то здесь есть пространство для маневров:
Использовать Jetpack Navigation Component. Весь тот крутой функционал с типизацией и прочим работает именно на уровне приложения, а не ОС
Использовать DeepLinkDispatch от airbnb
Использовать другие библиотеки…
Написать собственный диспетчер
О чем нужно помнить при внутренней диспетчеризации?
Он должен правильно обрабатывать нужны URLы
Должен иметь обработчик для внештатных ситуаций: для отображения экрана с пояснением, например
Также было бы хорошо иметь как можно больше обработчиков для ссылок на разный контент в приложении
MyUrlDispatcher
Мы решили попробовать самостоятельно обработать deep link. Начали с внешней диспетчеризации:
Мы настроили intent-filter, указали все схемы (1), домены (2). Установили нужны path с учетом слешей (3). Внешняя диспетчеризация настроена!
Теперь ко внутренней. Для начала надо перехватить исходный intent. Перехватили интенты в методах (4) onCreate, onNewIntent:
Сначала в catchAndWrapIntent
мы выполняем классификацию (5) intent по информации из него (category, action, data…). Именно здесь у нас появляется возможность использовать всю мощь регулярных выражений, которой нам так не хватало в pathPattern! Каждому классу intent соответствует специальная обертка (6)
Осталось выполнить обработку. Каждому классу-оболочке соответствует действие. В большинстве случаев это навигация на какой-нибудь экран (7). Обратите внимание на (8). Intent оболочки были специально сделаны для того, чтобы упростить извлечение полезных данных из intent.
Вот такая диспетчеризация получилась!
Обратная совместимость Deep Link
Еще одно важное замечание. Если пользователи в одной из версий приложения получили возможность перехода по deep link, то эту возможность необходимо поддерживать в следующих обновлениях.
Очень важно следить за изменением intent-filter, а именно, scheme, host, path, port, mimeType. Простой пример:
Изменения значений scheme, host, path, port, mimeType могут привести к потере обратной совместимости с уже используемыми deep link.
Как можно избежать проблем с обратной совместимостью deep link?
Дважды подумать при изменении data. Оно вам действительно надо?
При появлении новых ссылок, можно разрулить их обработку не на уровне ОС, а на уровне приложения.
Создать специальный UI для deep link, которые больше не поддерживаются, где объяснить пользователю ситуацию и порекомендовать пути решения.
Посмотреть в сторону Persistent URL, как одно из способов контроля изменений в существующих URL.
Резюме
Давайте подведем итоги всего цикла статей.
Deep link — это база. Все остальные виды ссылок наследуют ее недостатки и преимущества. Web Link пытается отобрать трафик у web. App Link ориентирован на UX.
Основная задача Deep Link — это навигация.
Для того, чтобы эффективно выполнить эту задачу необходимо помнить, что пользователю нет никакого дела до того, что написано в URL. Он видит ключевые слова и ему этого достаточно для перехода по ссылке. Поэтому учитывайте все схемы, все домены, все порты. Старайтесь максимально покрывать pathы, обязательно учитывая комбинации со слешами
Чем больше действий нужно сделать, тем меньше людей это сделает.
Большое количество кликов сужает воронку и снижает конверсию. Чтобы сократить количество кликов при переходе по Deep Link, рекомендуем обратить внимание на App Link.
На Android 12.0 web трафик (http, https) нельзя перехватить приложением с настроенными Deep Link. App Link способен обойти это ограничение.
Тут все понятно. Следим за развитием технологий и не забываем адаптироваться.
На обработку deep link не влияет:
Аппаратная часть. В устройстве нет специального чипа, который выполняет обработку deep link. Deep link реализован на уровне ПО.
Версия Android OS. С учетом примечания к Android 8.0. Напоминаем, там нужно было выбрать сначала браузер, а потом уже наше приложение.
Оболочка ОС. Если мы считаем, что оболочка — это, в том числе, и приложения, которые в ней предустановлены, то оболочка влияет на работу Deep Link. Но все-таки по итогу она влияет ЧЕРЕЗ ПРИЛОЖЕНИЯ.
На обработку deep link влияет:
Приложение, из которого открывается ссылка! Поэтому
Было бы хорошо знать, где и на чем будут распространятся ваши ссылки (приложение, оболочка, версия ОС). Эти знания помогут приоритизировать тестирование.
Существуют разные способы обработки схем и URL:
Linkify — специализированный API для решения этой задачи;
HTML удобен, когда в приложении есть размеченные с его помощью данные;
Span может быть использован при наличии прочих элементов этой разметки.
URI формат deep link в Android имеет свои особенности, некоторых атрибутов в нем нет в явном виде (по крайнер мере они не доступны для настройки в intent-filter). scheme, host обязательные. port и path опциональные (при их отсутствии может быть использовано любое значение).
Не существуют удобного и гибкого инструмента для фильтрации сложных path на уровне ОС.
Поэтому пытаемся выжимать максимум из pathPrefix и pathPattern. И помним, что дальше придется обрабатывать неудобные URL.
Эту неудобную обработку придется выполнит в URL dispatcher на уровне вашего приложения. Можете использовать существующие решения или создать свое. Главное помнить, что диспетчер должен уметь обрабатывать правильные URL, вести себя предсказуемо с кривыми URL и по возможности покрыть как можно больше видов URL.
Иногда (очень редко:)) web удобнее mobile. Не нужно пренебрегать пользователем в пользу повышения трафика.
Поздравляем! Цикл статей подошел к концу. Благодарим, что были с нами. Особенно тех, кто прочитал все 5 статей. Поделитесь этим материалом с друзьями и коллегами. И до новых встреч!
Валера Петров
Android-разработчик. @valeryvpetrov
Ангелина Евсикова
Android-разработчица Технократии. @Angelina_dev
Также подписывайтесь на наш телеграм-канал «Голос Технократии». Каждое утро мы публикуем новостной дайджест из мира ИТ, а по вечерам делимся интересными и полезными мастридами.
beeline09
Спасибо за цикл статей! Очень информативно, последовательно и вовремя вышел.