Ви бачите, як спрацював клік, резолвнувся intent, Android передав керування цільовому застосунку — і за секунду відвідувач знову на вашій landing-сторінці, ніби посилання від початку відкрило браузер замість застосунку. У вашому логу написано, що застосунок відкрився. Клієнт хоче знати, чому відвідувач нічого не зробив, опинившись нібито всередині. Ви обидва праві, і в цьому і є проблема: успішний перехід і відвідувач, який усе одно опиняється назад у браузері, — речі, які цілком можуть відбуватися одночасно.
Той самий механізм, два результати, один пристрій
Це не гіпотеза. Це те, що сталося на одному Pixel 8, Android 15, Chrome 131, коли той самий механізм диспетчеризації запустили двічі проти двох різних застосунків:
| TikTok | ||
|---|---|---|
| Диспетчеризація | intent://www.instagram.com/instagram/#Intent;scheme=https;package=com.instagram.android;end | Той самий механізм intent://, package TikTok |
| Handler activity | com.instagram.url.UrlHandlerActivity | com.ss.android.ugc.aweme.deeplink.AppLinkHandlerV2 |
| Системний лог | Task створено, застосунок лишився на передньому плані | Task створено, потім закрито з numActivities=0 |
| Що побачив відвідувач | Instagram, на профілі призначення | Браузер, на початковій веб-сторінці |
| Чому | У застосунку була сесія, щоб відрендерити призначення | Застосунок був розлогінений, і йому не було що рендерити |
Instagram отримав посилання, відкрився і лишився на профілі призначення. TikTok отримав такий самий клас посилання, у власному handler’і, і закрився одразу — системний лог показує, що task створено з deep link на ньому, потім закрито з numActivities=0, і відвідувач опинився назад у браузері на тій самій сторінці, з якої пішов. Обидва застосунки отримали посилання коректно. Лише одному з них було що показати натомість.
numActivities=0 — це власний облік Android щодо щойно створеного task: застосунок відкрив task, щоб вмістити цільовий екран deep link, і цей task тепер містить нуль activities, бо застосунок розібрав його замість того, щоб заповнити. Це не збій, і ніде в звіті кампанії це не логується як помилка. Це свідомий вибір застосунку — що робити з посиланням, яке він не може відрендерити, — і застосунок обирає «нічого» замість екрана помилки чи вимоги залогінитись.
Це не дефект, специфічний для TikTok, і мета поставити ці два запуски поруч — не виділити один застосунок. Річ у тім, що той самий режим відмови доступний будь-якому застосунку на іншому кінці deep link, і який саме ви спостерігаєте, залежить від стану акаунта відвідувача, що тримає телефон, а не від того, який застосунок ви таргетували чи як побудували посилання. Залогінений акаунт TikTok, якому передали той самий intent у той самий handler, має сесію, щоб відрендерити призначення, — та сама умова, що зробила успішним запуск Instagram.
Що насправді вимірює «відкрито»
Робота рівня роутингу закінчується на handoff: побудувати правильний intent://, резолвнути правильний package і змусити Android запустити цільовий застосунок. Саме це бачить лог кліків чи воронка відкриттів застосунку, і за цим критерієм обидва запуски вище були успішними — Android запустив застосунок в обох випадках. Чого рівень роутингу не бачить і ніколи не міг бачити — це що застосунок робить із посиланням, отримавши його. У Instagram була активна сесія, і він відрендерив призначення. У TikTok — ні, і замість того, щоб показати помилку чи вимогу залогінитись, він закрив власний handler і дозволив відвідувачу впасти назад туди, звідки той прийшов.
Саме тому «чи відкрився застосунок» — неправильне питання, навколо якого варто будувати звіт. Це реальне питання з реальною відповіддю, і ця відповідь часто «так» для відвідувача, який так нічого і не побачив усередині застосунку. Воронка відкриттів, що зупиняється на handoff, зарахує запуск TikTok у цьому порівнянні рівно так само, як запуск Instagram — як успіх, — бо з погляду рівня роутингу це і був успіх.
Дві різні речі істинні одночасно, і одна метрика не може вмістити обидві:
| Що вимірює | Де вирішується | |
|---|---|---|
| Handoff | Android запустив правильний застосунок у правильному handler’і | Рівень роутингу, в момент резолву intent |
| Результат усередині застосунку | Чи відрендерив застосунок призначення, чи повернув відвідувача назад | Сам застосунок, залежно від власного стану сесії на момент запуску |
Дашборд кампанії, у якому є місце лише для одного з цих рядків, за замовчуванням обере handoff, бо handoff — легша половина для інструментування: це подія на боці кліку з чітким сигналом успіху, той самий легкий для фіксації сигнал, який видає pixel, незалежно від того, що станеться після нього. У результату всередині застосунку немає еквівалентного сигналу на стороні відправника взагалі — він існує лише на пристрої, всередині застосунку, вже після того, як контроль перейшов туди.
Ніщо на боці відправника не може цьому запобігти
Тут немає fallback URL, повтору чи кращого intent, який змінив би те, що сталося. Рівень роутингу зробив свою роботу: доставив правильний застосунок, у правильний handler, з прикріпленим посиланням. Що застосунок зробив далі — вирішив стан, до якого рівень роутингу не має доступу і не може заздалегідь запитати: чи мав той акаунт TikTok валідну сесію в момент надходження посилання. Залогінений відвідувач отримує інший результат, ніж незалогінений, від абсолютно однакового посилання, доставленого абсолютно однаково. Це не помилка роутингу, яку треба виправляти. Це умова, яку рівень роутингу ніколи не міг виявити.
Як розрізнити ці два випадки під час діагностики
З боку кліку успіх у стилі Instagram і відкат у стилі TikTok виглядають однаково аж до моменту handoff — той самий intent, той самий резолвнутий package, той самий запущений застосунок. Різниця проявляється лише після цієї точки, і єдине місце, де вона реально видима, — те саме місце, де ці два запуски розрізнили спочатку: сам пристрій, у момент кліку, з відкритим системним логом. Відтворити клік на реальному пристрої й подивитись, що звітує Android — який handler activity отримав посилання, чи лишається результуючий task на передньому плані, чи закривається з numActivities=0, — дає остаточну відповідь, чи утримав застосунок відвідувача, чи повернув його назад. Відтворити той самий клік залогіненим у цільовий застосунок і розлогіненим — найшвидший спосіб побачити, чи залежить відкат конкретного застосунку від сесії, як це було з TikTok у цьому випадку.
Чого ви не можете зробити — це вивести різницю з будь-чого доступного на етапі побудови посилання. intent://, який ви будуєте, package, який таргетуєте, правила fallback, які встановлюєте, — ніщо з цього не несе інформації про те, що застосунок зробить, отримавши контроль, бо цієї інформації не існує, доки застосунок не оцінить власну сесію в момент запуску.
Практична версія цієї перевірки, коли ви підозрюєте, що конкретний застосунок повертає відвідувачів назад:
- Відтворіть клік на реальному пристрої з установленим і залогіненим цільовим застосунком. Підтвердіть handler activity й зафіксуйте, чи лишається результуючий task на передньому плані.
- Розлогіньтесь із того самого застосунку і відтворіть ідентичний клік. Якщо результат змінюється — застосунок тепер закриває свій handler і повертає вас у браузер, — відкат залежить від сесії, а не від зламаного посилання.
- Порівняйте два handler activities, які звітує ОС. Симптом, помітний відвідувачу, що з вашого боку читається однаково («застосунок відкрився, відвідувач не конвертувався»), може походити від двох різних компонентів на боці застосунку, і лише пристрій каже вам, який саме ви бачите.
Жоден із цих трьох кроків не торкається самого посилання, бо посилання ніколи не було змінною. Змінна — те, що залогінено на телефоні, який його запускає.
Що це означає для того, що ви обіцяєте клієнту
«Застосунок відкрився» і «відвідувач конвертувався всередині застосунку» — це два різні твердження, і звіт, що зводить їх в одне число, дає обіцянку, яку рівень роутингу ніколи не мав можливості виконати. Чесна версія звіту про роутинг каже, що посилання успішно передали правильному застосунку — це те, що реально відбувається і вимірюється на рівні роутингу. Чи лишився відвідувач там після цього — окремий результат, визначений власним станом сесії цільового застосунку, і жодне налаштування на стороні відправника не змінює це число.
Конкретно: якщо клієнт питає, чому кампанія з відкриттям застосунку не конвертує так, як пропонує рівень відкриттів — те саме за формою питання, що й конверсія, яка так і не прийшла, — відповідь може бути взагалі не в кампанії. Може виявитися, що помітна частка відвідувачів відкриває цільовий застосунок розлогіненими, і сам застосунок відмовляється щось їм показувати — що рівень роутингу виконав коректно, і що ніщо в таргетингу, креативі чи побудові посилання змінити не може. Показувати успішний handoff і результат усередині застосунку двома окремими рядками замість одного змішаного показника «застосунок відкрито» — це різниця між клієнтом, який розуміє, що він насправді бачить, і клієнтом, який вважає, що кожне відкриття досягло призначення.
Саме такий поділ DarkCore PWA Tracker утримує на рівні роутингу: зафіксований handoff цільовому застосунку, окремо від того, що відвідувач робить після того, як контроль перейшов туди, — бо ці дві речі вирішують різні системи, і подання їх одним числом відповідає на питання, яке ніхто не ставив.