自社ドメインのリンクをタップしたら、当然アプリが開くと思っていたのに――なぜかブラウザで開いてしまう。App Links(Digital Asset Links)の検証まわりで、こんな状況に頭を抱えたことはありませんか?
この機能の厄介なところは、失敗してもユーザー向けのエラーが一切出ない点です。設定は合っているように見えるのに、リンクが静かにブラウザへ流れていく。原因が見えないので、切り分けに何日も溶かしてしまいがちなんですよね。
この記事では、App Links の検証が失敗する原因を「起きやすい順」に整理し、assetlinks.json の確認方法から adb でのステータス確認まで、実際に手を動かして切り分ける手順をまとめていきます。
まず、検証の仕組みをざっくり押さえる
App Links の自動検証は、おおまかに次の流れで動きます。
- intent-filter に
android:autoVerify="true"があると、インストール時に Android が対象ホスト名を集める - 各ホストの
https://ホスト名/.well-known/assetlinks.jsonを取りに行く - ファイル内の
sha256_cert_fingerprintsと、アプリの署名証明書のフィンガープリントを照合する - 一致すれば「検証済み」。以降そのドメインのリンクはアプリで直接開く
ここで重要なのが、不一致や取得失敗が起きても、ユーザーに見えるエラーは出ないということ。Android 12 以降は、検証に失敗したリンクは黙ってブラウザで開かれます(11 以前は選択ダイアログが出ていたので、まだ異変に気づけました)。この「無言の失敗」こそが、原因究明を難しくしている元凶です。
assetlinks.json の正しい形
まず基本形を確認しておきましょう。https://あなたのドメイン/.well-known/assetlinks.json に、次の内容を置きます。
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.sampleapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:..."
]
}
}
]
マニフェスト側は、対象の Activity にこんな intent-filter を書きます。
<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="sample.com" />
</intent-filter>
原因1:SHA-256フィンガープリントの不一致(最頻)
本番で検証が失敗するケースのほとんどがこれです。特に Play App Signing を使っていると、ここでハマります。
Play App Signing では、Google がアップロード鍵とは別の鍵であなたのアプリを再署名してユーザーに配信します。つまり端末上のアプリの署名は、あなたのローカルの鍵ではなく Google が管理する「アプリの署名鍵」です。
そのため assetlinks.json に書くべきは、Play Console の「アプリの署名」(App integrity)に表示される署名鍵の SHA-256 であって、手元のデバッグ鍵やアップロード鍵のフィンガープリントではありません。ここを取り違えると、デバッグビルドでは検証が通るのに、Playからの本番ビルドで静かに壊れるという、いちばん気づきにくい形で失敗します。
あわせて、フィンガープリントは大文字(アッパーケース)で書く必要があります。小文字だと一致しません。
原因2:サーバー側でファイルが取得できない(FETCH_ERROR系)
SHA-256 が合っていても、そもそもファイルを Android(Google の検証基盤)が取得できなければ検証は失敗します。ログに ERROR_CODE_FETCH_ERROR のような取得エラーが出ている場合は、この系統を疑います。よくある原因は次のとおりです。
- リダイレクト:301 / 302 は検証を即失敗させます。
wwwへの付け替えや末尾スラッシュの付与が、いちばん多いサーバー側原因です。直接 200 で返す必要があります。 - Content-Type:
application/jsonでなければなりません。text/plainやtext/htmlで返していると弾かれます。 - 認証・bot保護:Basic認証、ログイン壁、WAFやCDNのbotチャレンジが
.well-knownをブロックしていると取得できません。この経路は公開されている必要があります。 - 証明書:HTTPS証明書が有効で信頼できるものである必要があります。自己署名や期限切れは失敗します(ブラウザのような「無視して続行」はありません)。
- ジオブロッキング:地域やアクセス元でコンテンツを出し分け・遮断していると、検証基盤からのアクセスだけ弾かれることがあります。特定地域からだけ取得できない、という形で表面化します。
まずは curl で、素直に 200 が返るかを確認するのが第一歩です。
# ヘッダーを確認:200か / Content-Typeは何か
curl -sI https://sample.com/.well-known/assetlinks.json
# リダイレクトの連鎖を丸ごと見る
curl -sIL https://sample.com/.well-known/assetlinks.json
原因3:package_name違い・複数ホストの巻き込み
package_name が実際のアプリと違っている、という単純ミスも意外とあります。あわせて、複数ドメインを扱う場合はドメインごとに assetlinks.json が必要です。1つのドメインに置いたファイルは、他のドメインをカバーしません。
さらにバージョンによる差もあります。Android 12 以降は検証がドメイン単位なので、あるドメインが失敗しても他には影響しません。しかし Android 11 以前は、宣言したドメインの1つでも検証に失敗すると、全ドメインが巻き込まれて失敗します。古い端末もサポートするなら、宣言したすべてのドメインで有効な assetlinks.json が用意できているかを、公開前に必ず確認しましょう。
原因4:autoVerifyの付け忘れ
基本的ですが、android:autoVerify="true" が intent-filter に付いていなければ自動検証は走りません。複数の intent-filter がある場合は、検証させたいものそれぞれに個別に付けるのが確実です。
端末側で検証ステータスを確認する
設定が正しいはずなのに開かない、というときは、端末が実際にどう判定しているかを見ます。アプリをインストールした状態で、次のコマンドを実行します。
adb shell pm get-app-links --user cur com.example.sampleapp
出力に各ドメインの状態が表示されます。読み方はこうです。
verified:検証済み。リンクはアプリで直接開く。selected:ユーザーが手動でこのアプリを選んだ状態(自動検証ではない)。none:まだ検証が走っていない、または状態がリセットされている。
設定を直したあと、再インストールせずに検証をやり直したいときは、状態をリセットしてから再検証をかけます。
# 現在の検証状態をリセット
adb shell pm set-app-links --package com.example.sampleapp 0 all
# 再検証を実行
adb shell pm verify-app-links --re-verify com.example.sampleapp
反映タイミングの罠
「サーバーのファイルを直したのに、まだアプリで開かない」――これはバグではなく反映待ちのことが多いです。OSバージョンで挙動が違います。
- Android 15(API 35)以降:システムがバックグラウンドで定期的に再検証します。ただしキャッシュと再検証スケジュールの都合で、変更が全端末に行き渡るまで最大7日かかることがあります。
- Android 14(API 34)以前:定期的な再検証は行われません。基本はインストール/更新時にだけ反映されるので、テストではアンインストール→再インストールで強制的に取り直します。
なお、再インストールしてもサーバー側やCDNのキャッシュで古いファイルが配られ続けることがあります。その場合は時間を置くと自然に解消することが多いです。
まとめ:切り分けチェックリスト
curl -sILで、リダイレクトなしの 200 &Content-Type: application/jsonを確認した- SHA-256 は Play Console の署名鍵のもので、大文字で記載した
package_nameが実際のアプリと一致している- 複数ドメインは、各ドメインに個別の assetlinks.json を置いた
- intent-filter に
autoVerify="true"を付けた adb shell pm get-app-linksでverifiedを確認した- 反映待ち(Android 15は最大7日 / 14以下は再インストール)を考慮した
App Links の不具合は「無言で失敗する」のがつらいところですが、逆に言えば確認できるポイントは決まっているので、上から順に潰していけば必ず原因にたどり着けます。中でも Play App Signing の SHA-256 は本当に事故が多いので、まずそこから疑うのがおすすめです。URLハンドリング系の話は他にもあるので、また少しずつ書いていければと思います。


コメント