真面目にDeep Link対応したい話
Tags: deeplink, universallinks, appindexing
Published: 2020-03-26 02:30:00

---
title: 真面目にDeep Link対応したい話
published: true
description: 
tags: deeplink, UniversalLinks, AppIndexing 
---

今日は真面目にDeep Link対応したい話をしようと思います。今更Deep Link？感はありますが、真面目にやろうと思います。

会社のアプリでいきなり本番検証は無理なのでテスト用のアプリを作って検証することにしました。

Webはこちら( https://lgtm.lol )で、Androidはこちら( https://play.google.com/store/apps/details?id=lol.lgtm )を使ってます。
iOSは開発中です。

# 用語から
よく聞く Universal Links, Deep Link, App Indexing, App Linksなどなど、いろいろ用語があって、まずは混乱しますよね？整理しますとこんな感じになるかと思います。

<img width="762" alt="deep_link.png" src="https://qiita-image-store.s3.amazonaws.com/0/15490/2f56c8e4-e9a1-6d28-7fbe-74a59082d844.png">

Google: App Indexing
https://firebase.google.com/docs/app-indexing/

Twitter: Twitter カード
https://developer.twitter.com/en/docs/tweets/optimize-with-cards/guides/getting-started

Facebook: App Links
https://developers.facebook.com/docs/applinks

Apple: Universal Links
https://developer.apple.com/library/content/documentation/General/Conceptual/AppSearch/UniversalLinks.html
 
# ディープリンク(Deep Link)とは？
アプリの特定の画面に遷移させることのできるリンクのこと。

# Custom URL Scheme (iOS, Android共通)
```html
<a href="app-name://product/abc123">商品ページをアプリで開く</a>
```
のようにapp-nameというアプリのproductページを開くやつです。

問題は同じapp-nameを持つアプリを２つインストールされた場合どれが起動するか保証できません。

# Universal Links (iOS)

iOS用のDeep Linkです。

iOS 9（2015年9月16日）以降利用可能で、サーバーからjsonを返す必要あります。

https://lgtm.lol/apple-app-site-association

```json

{
  "applinks": {
       "apps": [],
        "details": [
           {
               "appID":"6SRWK494FT.lol.lgtm.ios.LGTM",
               "paths":[ "/i/*" ]
           }
        ]
    }
}
```

iOS側は、Associated Domainsを有効にしてドメインを追加します。

<img width="879" alt="Screen Shot 2017-11-30 at 22.10.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/15490/ebb4a210-0b13-b700-0802-0ca58d81e899.png">

書き出されたentitlementsファイルはこのようになります。

<img width="453" alt="Screen Shot 2017-11-30 at 22.11.10.png" src="https://qiita-image-store.s3.amazonaws.com/0/15490/b335153a-ca8c-6c84-8a84-d843fcc093e2.png">

Custom URL Schemeまで対応するとこうなります。

<img width="714" alt="Screen Shot 2017-11-30 at 22.50.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/15490/247f3d91-c80f-6bf2-8a64-8f93d9948093.png">


### 受け取ったリンクを処理する

```swift

func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
    if (url.scheme == "lgtm" && url.host == "item") {
        let components = url.pathComponents
        let itemId = components[1]
        let vc = ItemViewController()
        vc.itemId = itemId
        self.window?.rootViewController?.present(vc, animated: true, completion: nil)
    }
    return true
}

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
    if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
        let url = userActivity.webpageURL!
        if (url.scheme == "https" && url.host == "lgtm.lol") {
            let components = url.pathComponents
            if (components[1] == "i") {
                let itemId = components[2]
                let vc = ItemViewController()
                vc.itemId = itemId
                self.window?.rootViewController?.present(vc, animated: true, completion: nil)
            }
        }
    }
    return true
}
```

アプリが起動される時に `Associated Domains` に定義してるドメインの `/apple-app-site-association` にアクセスして許可してるパスを取得してアプリに認識させるみたいです。（サーバーログから）

これで外部リンクから https://lgtm.lol/i/234 にアクセスされた時は LGTM アプリのItemViewControllerが立ち上がるようになりました。

# App Indexing (Android)

サーバー側でjsonをレンダリングするように設定します。

https://lgtm.lol/.well-known/assetlinks.json

```json

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target" : { "namespace": "android_app",
      "package_name": "lol.lgtm",
                 "sha256_cert_fingerprints": ["0E:C7:C8:9F:40:03:28:73:9F:7B:8E:62:09:B1:C4:2E:B9:A3:02:65:F1:2A:29:C6:7D:40:56:DE:D7:B7:84:42"] }
  }
]
```

package_nameはandroidのパッケージ名です。
sha256_cert_fingerprintsは公式ドキュメントでは

```
keytool -list -v -keystore my-release-key.keystore 
```

このように書いていて、そのまましたら `adb install release.apk` の時は通るけど、playstoreからインストールした場合は認証通らないみたいです。
play store consoleのリリース管理 -> アプリの署名 -> アプリへの署名証明書の `SHA-256 証明書のフィンガープリント` を設定したところPlay Storeからインストールして認証通るようになりました。

Android側のAndroidManifest.xmlは以下のようになります。

```xml

<activity
    android:name=".ItemActivity">
    <intent-filter android:label="@string/app_name" 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="lgtm.lol" android:pathPrefix="/i"></data>
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="lgtm" android:host="item" />
    </intent-filter>
</activity>
```
これで `https://lgtm.lol/i/234` と `lgtm://item/234` をサポートすることになります。

### 受け取ったリンクを処理する

ItemActivity.java#onCreate

```java

String data = intent.getDataString(); // https://lgtm.lol/i/234 | lgtm://item/234
if (data != null) {
    String pathId = data.substring(data.lastIndexOf("/") + 1); // 234
    itemId = Integer.valueOf(pathId);
    setImageFromItemId(itemId);
} else {
    itemId = intent.getIntExtra("id", 0);
    imageView.setImageUrl(intent.getStringExtra("url"), Controller.getPermission().getImageLoader());
}
```

Androidでは対応するアプリが複数個ある場合どれで開くか選ばせるんですが、特定のアプリがデフォルトで開くかどうかは以下のコマンドで確認できます。

```shell-session

$ adb shell dumpsys package domain-preferred-apps 
  Package: lol.lgtm
  Domains: lgtm.lol
  Status:  always : 20000002c
```

Statusが ask の場合はurlクリックした時にブラウザで開くか、アプリで開くか選択させるやつが出ます。

以下のコマンドでテストできます。

```shell-session

$ adb shell am start -a android.intent.action.VIEW \
 -c android.intent.category.BROWSABLE \
 -d "https://lgtm.lol/i/234"
```

ここまでやるとGoogle検索結果か、ページにリンク( https://lgtm.lol/i/234 )クリックした時にデフォルトでアプリが起動するようになりました

# 成果

<img src="https://qiita-image-store.s3.amazonaws.com/0/15490/33f8616c-6d83-3f17-0215-6bf0f2c7fc54.gif" width=50%>

# Google検索結果にアプリアイコン表示

play store consoleの「開発ツール -> サービスとAPI」でドメイン追加して、google search consoleで認証を行います。
Google様がindex作成するのに時間かかるのでここは待つしかないかと思います。

# 終わりに
引き続きDeep Link勉強して会社のアプリに対応しようと思います。後は社内で https://lgtm.lol のAPIをgRPC対応しろ！の声もあるので、近いうちにgRPC対応しようとかと思います。

# 追記
Googleが検索結果にアプリを表示してくれました。実際Android向けにサーバー設定してから反映されるまで約二週間かかりましたね

<img src="https://qiita-image-store.s3.amazonaws.com/0/15490/1d9a5eb9-b7ed-c732-907d-7ee3491b3073.png" width=50%>

# 追記2
コードに関して問い合わせが来たのでアプリソースコードをgithubに公開しました

iOS: https://github.com/dongri/LGTM-iOS
Android: https://github.com/dongri/LGTM-Android