Facebookアプリからのディープリンク【Android編】

# はじめに
**※本記事では通常のwebなどからのディープリンクについては記載を割愛しますm(_ _)m**
[前回の記事](https://qiita.com/190yamashita/items/8fb8336432e854639c77)に続き、今回はAndroidのFacebookアプリからディープリンクについてを記事にしたいと思います。
有料（従量課金）のツールとか使えば楽できるみたいなのですが、今回のミッションはFacebookアプリからだけなのでお金をかけずにとのことでした。
ので自前で実装することになったんですが…かなり苦戦しましたので記事にして残しておきたいと思います。

# 実現方法

どのように実現するか調べたらまず公式の[App Links on Android](https://developers.facebook.com/docs/applinks/android)が見つかりました。
早速導入して試したんですが、全く機能せず…色いろ調べても解決方法が見つけられず…
一旦諦めました（＞＜）

が、ベテラン技術者の方に色々調べてもらいサーバ側にプログラムを仕組んだらできるのでは無いか？ということで試していただくことに。

導入したのは

- Chromeのintent
- [App Links on Android](https://developers.facebook.com/docs/applinks/android)


の２つを導入しました。
が、おそらく[App Links on Android](https://developers.facebook.com/docs/applinks/android)だけで動くと思われますのでChromeのintentは概要だけ記載します。


# Chromeのintent
[App Links on Android](https://developers.facebook.com/docs/applinks/android)以外にもintent起動する方法があって最初はこれを実装していました。
以下が構成イメージです。meta情報を加工してリダイレクトするだけの割とシンプルな構成です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325497/44cabd52-06f0-b2f5-2c0a-95dbae5c5c05.png)
ただ古い機種（ASUS Zenfone4：Android8）ならうまくいくんですが、比較的新しい（Android10とか）の機種ではなぜかうまくいきませんでした。原因はわからないまま…
なので次項の[App Links on Android](https://developers.facebook.com/docs/applinks/android)の導入に至りました。

# [App Links on Android](https://developers.facebook.com/docs/applinks/android)

こちらが本題。以下のようなイメージ構成です。
iOSの時と大差無いですが、違っているのはサーバ側にプログラムを仕組んだことです。
通常のmetaデータだけだとFacebookアプリがうまくintent起動してくれないのでサーバ側でmetaデータを加工してあげることになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325497/7481144f-96c0-5086-668b-d38b8a22ecf9.png)
Androidの場合は①③④をFacebookアプリがやってくれます。

### サーバ側
[Facebookアプリからのディープリンク【iOS編】](https://qiita.com/190yamashita/items/8fb8336432e854639c77)の時と同じようにサーバ側にページを用意し
[https://hogehoge.com]
にアクセスがきたら用意したページに飛ぶようにサーバ側で設定します。
今回はmetaデータの加工が必要なのでphpファイルを準備しました。

そして、そのphpでmetaデータを加工する処理を仕組みます。
※パラメータの解説は[App Linksの技術文書を和訳してみました](https://www.eisbahn.jp/yoichiro/2014/05/app_links.html)が参考になりました。

```php
<?php

// 定数定義
define('ANDROID', 0);
define('IOS', 1);
define('OTHER_OS', 999);
define('FACEBOOK', 0);
define('OTHER_BROWSER', 999);
define('APP_NAME', 'あなたのアプリの名前');
define('ANDROID_STORE', 'https://play.google.com/store/apps/details?id=あなたのアプリID');
define('ANDROID_SCHEME', 'hogehoge-app');
define('ANDROID_CUSTOM_URL_HOST', 'hogehoge-link');
define('ANDROID_PACKAGE', 'あなたのアプリのパッケージ名');

// サーバ変数からホスト、url、ユーザエージェント取得
$httpHost = $_SERVER[HTTP_HOST];
$requestUri = $_SERVER[REQUEST_URI];
$uerAgent = $_SERVER[HTTP_USER_AGENT];

header("Content-type: text/html; charset=utf-8");

$os = findOS($uerAgent);
$browser = findFacebook($uerAgent);
if($os === ANDROID || $browser === FACEBOOK) {
    $host = ANDROID_CUSTOM_URL_HOST;
    $scheme = ANDROID_SCHEME;

    // metaデータに出力するlink
    $androidDeeplink = "$scheme://$host$requestUri";
}

function findOS($uerAgent) {
    if(preg_match('/Android/ui', $uerAgent)) {
        return ANDROID;
    } else if(preg_match('/iPhone|iPod|iPad/ui', $uerAgent)) {
        return IOS;
    }
    return OTHER_OS;
}

function findFacebook($uerAgent) {
    if(preg_match('/FB_IAB/ui', $uerAgent)) {
        return FACEBOOK;
    } else if(preg_match('/facebook/ui', $uerAgent)) {
        return FACEBOOK;
    }
    return OTHER_BROWSER;
}
?>

<html>
<head>
    <meta property="al:web:should_redirect" content="false" />
    <meta name="viewport" content="width=device-width,initial-scale=1">

    <meta property="al:android:package" content="<?php echo ANDROID_PACKAGE ?>" />
    <!-- ここで加工したlinkを出力。このmetaタグでFacebookアプリが動いてくれる -->
    <meta property="al:android:url" content="<?php echo $androidDeeplink; ?>" />
    <meta property="al:android:app_name" content="<?php echo APP_NAME ?>" />

    <!-- キャッシュは無効にしておく（でも、多分OGP情報のキャッシュには無力と思われる） -->
    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="-1" />
    <meta http-equiv="expires" content="Tue, 31 May 2011 10:15:00 GMT+3" />
    <meta http-equiv="pragma" content="no-cache" />

</head>
<body style="font-size: medium;">

<!-- アプリがインストールされていない場合ストアに遷移させる -->
<?php
    if($os === ANDROID || $browser === FACEBOOK) {
?>
    <script>
        window.onload = function() {
            setTimeout(function() {
                window.location = "<?php echo ANDROID_STORE; ?>";
            }, 3000);
        };
    </script>
<?php
    }
?>
</body>
</html>
```
※自分のAndroidは[Play Store](https://play.google.com/store/apps)で探しましょう。
※Androidの説明なので**<font color="red">iOSの記載は省略</font>**しています。iOSも同じurlを使う場合は[Facebookアプリからのディープリンク【iOS編】](https://qiita.com/190yamashita/items/8fb8336432e854639c77)の処理をphpファイルに移植してください。

### アプリ側
まずはintent起動できるようにAndroidManifest.xmlに追記します。

```xml

        <activity android:name=".DeepLinkActivity"
            android:launchMode="singleTop"
            android:noHistory="true"
            android:screenOrientation="portrait"
            android:theme="@style/AppTheme.NoActionBar">
            <!-- app linkとfacebookアプリからのアプリを起動は共存できないのでapp linkは使わない。 -->

            <!-- deep link for facebook app -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <!-- dataタグのschemeとhostはphpで定義したANDROID_SCHEMEとANDROID_CUSTOM_URL_HOSTに合わせます -->
                <data android:scheme="hogehoge-app" android:host="hogehoge-link" />

                <!-- リンクさせるパスを追記します -->
                <data android:pathPattern="/top" />
                <data android:pathPattern="/setting" />

            </intent-filter>
        </activity>

```

次にディープリンクを制御するアクティビティを準備します

```kotlin

import android.content.ComponentName
import android.content.Intent
import android.net.Uri
import android.os.Bundle

class DeepLinkActivity {

    companion object {
        private const val PATH_TOP = "/top"
        private const val PATH_SETTINGS = "/setting"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val intentFromDeepLink = intent
        /**
         取れるのはこんな情報
         Intent { 
           act=android.intent.action.VIEW 
           dat=hogehoge-app://hogehoge-link/top&target_url=https://hogehoge.com/top
         }
         **/

        val newIntent = Intent(Intent.ACTION_MAIN)
        newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

        var originalUrl: String? = null

        // facebookアプリからurl情報を受け取り
        intentFromDeepLink?.let { facebookIntent ->
            val uri = facebookIntent.data
            /**
             uriはこんな情報
             hogehoge-app://hogehoge-link/top&target_url=https%3A%2F%2Fhogehoge.com%2Ftop
             **/
            val facebookUrl = uri?.getQueryParameter("target_url")
            facebookUrl?.let {
                originalUrl = it
                /**
                 originalUrlはこんな情報
                 https://hogehoge.com/top
                 **/
            }
        }

        // urlを元に起動する画面のintentを設定する
        originalUrl?.let {
            val uri = Uri.parse(it)
            setIntent(uri, newIntent)
        }

        startActivity(newIntent)
    }

    private fun setIntent(uri: Uri?, intent: Intent) {
        if (uri == null) {
            setIntentToTop(intent)
            return
        }

        when (uri.path) {
            PATH_TOP -> {
                setIntentToTop(intent)
            }
            PATH_SETTINGS -> {
                setIntentToSetting(intent)
            }
            else -> {
                setIntentToTop(intent)
            }
        }
    }

    private fun setIntentToTop(intent: Intent) {
        val cls = TopActivity::class.java
        intent.component = ComponentName(packageName, cls.name)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    }

    private fun setIntentToSetting(intent: Intent) {
        val cls = SettingActivity::class.java
        intent.component = ComponentName(packageName, cls.name)
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    }
}

```
これでFacebookアプリからのディープリンクができるようになったと思います。
iOSと同じようにOGP情報のキャッシュがあるので、サーバ側を更新したらOGPキャッシュのクリアは必須だと思っていいです。



# 終わりに
私一人では解決できなかったですが、ベテラン技術者の方に協力いただいてなんとか機能させることができました。大感謝です。
Facebook公式に書いてある情報だけでは実現できないって…ちょっと不親切かな〜と思いました。
他のアプリの中身はわからないので手探り状態でしたがとってもいい経験になったと思います。

日本語の記事は見当たらなかったので誰かのお役に立てたらなと思います。