Перейти к содержимому
Все записи

Android App Links в 2026 году: почему ваши https:// ссылки всё ещё открываются в браузере

Верификация Digital Asset Links, подводные камни assetlinks.json и adb-команды, которые объясняют, почему Android не передаёт ссылку вашему приложению.

MFKAPPS 4 мин чтения

Кастомную URI-схему вроде myapp://item/42 легко настроить — и так же легко испортить другим способом: ту же схему может зарегистрировать любое приложение, поэтому система не может гарантировать, что откроет её именно ваше. Android App Link — настоящий URL вида https://yourdomain.com/..., открывающий приложение напрямую вместо вкладки браузера — решает эту проблему, но только если система может подтвердить, что ваше приложение действительно владеет доменом. Чаще всего такая ссылка молча откатывается на Chrome, а манифест при этом выглядит совершенно правильно. Не хватает почти всегда верификации Digital Asset Links, а не intent filter.

Две вещи, которые должны совпасть

App Link верифицируется автоматически только тогда, когда совпадают две независимые части: intent filter приложения и JSON-файл, который отдаёт сам домен. То, что ни одна сторона не знает о другой, — это и есть смысл: именно это не даёт случайному приложению claim-ить ваш домен.

Intent filter прописывается в манифесте:

<activity android:name=".MainActivity" android:exported="true">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="https"
              android:host="mfkapps.com"
              android:pathPrefix="/apps" />
    </intent-filter>
</activity>

Именно android:autoVerify="true" запускает реальную проверку владения доменом при установке, а не просто сопоставление по шаблону URL. Без него фильтр всё равно будет сопоставлять ссылки, но только после того, как пользователь один раз явно выберет ваше приложение в листе неоднозначности — до уровня «всегда открывать автоматически» это никогда не поднимется.

Со стороны домена — это статический файл по фиксированному, не подлежащему обсуждению пути:

https://mfkapps.com/.well-known/assetlinks.json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.mfk.granyn",
    "sha256_cert_fingerprints": [
      "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A3:88:1E:22:0B:99:19:33:4B:C0:5B:0B:64:0C:1D"
    ]
  }
}]

И package_name, и sha256_cert_fingerprints должны совпадать с точно тем билдом, который установлен. Именно в этой детали с отпечатком почти всегда и ломается любая неисправная настройка.

Несовпадение отпечатка, которое молча ломает всё

sha256_cert_fingerprints требует SHA-256 сертификата подписи установленного пользователем APK — а если приложение распространяется через Play App Signing, это ключ подписи Google, а не ваш локально подписанный upload-ключ. Укажите неверный отпечаток в assetlinks.json, и верификация провалится без какой-либо ошибки, видимой пользователю; ссылка будет вечно открываться в браузере, и ничто в Logcat не укажет на этот файл.

Правильный отпечаток берите из Play Console, а не из локального keystore:

Play Console → Setup → App integrity → App signing key certificate → SHA-256

Для debug-сборки во время разработки используйте отпечаток debug-keystore, и если хотите, чтобы debug и release верифицировались по одному и тому же assetlinks.json, добавьте оба в массив:

keytool -list -v -keystore ~/.android/debug.keystore \
  -alias androiddebugkey -storepass android -keypass android

sha256_cert_fingerprints принимает массив, так что оба отпечатка могут сосуществовать — верификация проверяет, совпадает ли любая запись с подписью установленного приложения.

Проверка, что всё действительно сработало

Android выполняет проверку верификации при установке и кеширует результат, поэтому быстрее всего узнать реальный статус, спросив систему напрямую, а не гадая по поведению:

adb shell pm get-app-links com.mfk.granyn

Рабочая настройка сообщает статус домена как verified:

com.mfk.granyn:
  ID: d3a1f0b2-...
  Signatures: [14:6D:E9:...]
  Domain verification state:
    mfkapps.com: verified

Если написано legacy_failure или none, значит отпечаток или JSON неверны — исправьте assetlinks.json, затем принудительно запустите повторную проверку без переустановки:

adb shell pm verify-app-links --re-verify com.mfk.granyn
adb shell pm get-app-links com.mfk.granyn

Чтобы проверить реальное поведение при нажатии на ссылку после успешной верификации, запустите intent так же, как это сделал бы браузер или другое приложение:

adb shell am start -a android.intent.action.VIEW \
  -d "https://mfkapps.com/apps/granyn"

Если приложение открывается без листа выбора — верификация работает. Если появляется диалог неоднозначности — верификация не прошла, и система обрабатывает ссылку как обычную, неверифицированную.

Три вещи, которые молча ломают всё после запуска

JSON должен отдаваться с правильным content-type и без редиректа. CDN или reverse proxy, который делает 301-редирект с /.well-known/assetlinks.json на канонический URL или отдаёт его как text/html, ломает верификацию, даже если браузер отображает файл нормально. Проверьте оба момента напрямую через curl:

curl -sI https://mfkapps.com/.well-known/assetlinks.json | grep -i content-type

Перевыпуск подписи приложения меняет отпечаток. Ротация ключа подписи, переход с локального keystore на Play App Signing или подключение нового build-пайплайна — всё это молча ломает каждую ранее верифицированную ссылку, пока assetlinks.json не будет обновлён новым отпечатком. Это заслуживает отдельной строки в чек-листе релиза, потому что режим отказа — ссылки, которые молча откатываются на браузер, — не даёт ни краша, ни отчёта об ошибке.

Несколько приложений на одном домене требуют нескольких утверждений. Если несколько приложений одной команды claim-ят пути под одним хостом, assetlinks.json становится JSON-массивом — по одному объекту на приложение, у каждого свой package_name и список отпечатков. Файл, содержащий утверждение только для одного приложения, молча проваливает верификацию для всех остальных приложений на этом домене.

Что это даёт помимо более приятного нажатия

Как только домен верифицирован, тот же intent filter позволяет приложению выступать обработчиком по умолчанию для этого домена везде, где система передаёт URL — в действии уведомления, QR-коде, ссылке, отправленной из другого приложения, в потоке учётных данных Smart Lock. Ничего из этого не требует отдельной настройки — это тот же фильтр autoVerify и тот же assetlinks.json, которые просто делают больше, как только система по-настоящему доверяет тому, что за ними стоит.

Если ссылка на приложение всё ещё открывается в браузере после всего этого, проверьте pm get-app-links, прежде чем снова трогать манифест — на практике сломанной половиной почти никогда не оказывается intent filter.

// По теме

Ещё из журнала

MFKAPPS 6 мин чтения

WorkManager в 2026 году: уникальная работа, цепочки задач и тестирование фоновых заданий на Android

Практическое руководство по WorkManager на Android — политики уникальной работы, цепочки задач, ускоренное выполнение и как на самом деле протестировать фоновое задание перед релизом.

#android #engineering #mobile
MFKAPPS 4 мин чтения

In-App Review API в Android: как просить оценку, не раздражая пользователя

Практическое руководство по In-App Review API от Google — как он на самом деле работает, когда его запускать и почему обычный попап «оцените нас» незаметно портит вашу оценку в Play Store.

#android #engineering #kotlin
MFKAPPS 5 мин чтения

Ярлыки приложений Android и плитка быстрых настроек: логирование воды без открытия приложения

Практическое руководство по динамическому ShortcutManager API и TileService в Android — как позволить действию в один тап полностью обойти приложение, и о подводных камнях, на которых спотыкается большинство реализаций.

#android #engineering #kotlin