ChromeCustomTabsってなんすか?

2024/05/21に公開

ChromeCustomTabsってなんなの?

  • アプリ開発者がカスタマイズされたブラウザ エクスペリエンスをアプリ内に直接追加できるようにする Android ブラウザーの機能
  • アプリ内の画面としてウェブコンテンツを表示する機能
  • Webコンテンツを表示する画面のUIを触れる機能
  • Chromeのセキュリティ機能とプライバシー保護を継承しているためWebViewよりも安全
  • 2015年くらいに登場したけどあまり流行ってない機能
  • 正式名称はCustomTabsIntent
    (ここではChromeCustomTabsで基本書きます)
  • WebViewの改良版というより、異なる目的で使用されるもの

ChromeCustomTabsを取り上げようと思ったわけ

  • とある事情でChromeCustomTabsを使うという話になり、調査してみた
  • 情報が全然出てこなくて驚きました
    (WebViewと比較すると、なんで候補に挙がったのか分からないくらい情報が少なかった)
  • WebView+αだと思ってたけど調べてから結構違うことがわかって少し困った
  • Zennだと記事として書いてる人がいないので自分レベルでも書く価値が少しはあるんじゃないかと思った[1]
  • 最近触った中だと一番気楽にかけそうな内容で初投稿のネタにもちょうどよさそうに感じた
  • 意図せず嘘書いてコメントで指摘来たとしても、それはそれで価値があると思ったから

ChromeCustomTabsのサンプル

とりあえず最低限動くもの

動作確認環境

  • Android Studio Jellyfish | 2023.3.1
  • 実機:SO-41A(Android12,API31)

gradleファイルにて以下の依存関係を追加する

    implementation("androidx.browser:browser:1.5.0")

MainActivity.ktに実装

package com.example.chromecustomtabstest

import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsIntent.ACTIVITY_HEIGHT_ADJUSTABLE
import androidx.browser.customtabs.CustomTabsIntent.CLOSE_BUTTON_POSITION_END
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

class MainActivity : AppCompatActivity() {
    private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        // コールバックを入れる場合ここに記載
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        val openUrlButton = findViewById<Button>(R.id.openUrlButton)
        openUrlButton.setOnClickListener {
            val builder = CustomTabsIntent.Builder().apply {
                setToolbarCornerRadiusDp(16)
                setCloseButtonPosition(CustomTabsIntent.CLOSE_BUTTON_POSITION_END)
            }

            val customTabsIntent = builder.build().apply {
                intent.data = Uri.parse("https://developer.android.com/")
            }
            Log.d("CustomTabs", "Launching Custom Tab with URL: ${customTabsIntent.intent.data}")
            startForResult.launch(customTabsIntent.intent)
        }
    }
}
<Button
    android:id="@+id/openUrlButton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Open URL" />

実際の画面

※エミュレーターを使って動作確認する場合、ChromeのVersionが古いことが原因で全画面で表示されることがあります(Version105以上が目安)

本当は

val builder = CustomTabsIntent.Builder().apply {
    setInitialActivityHeightPx(1500)
    setToolbarCornerRadiusDp(16)
    setCloseButtonPosition(CustomTabsIntent.CLOSE_BUTTON_POSITION_END)
}

と書いてボトムシート部分を全画面で表示したいのですが、
setInitialActivityHeightPxがなぜか適用されず想定通りになりません
ChatGPTに聞いてみたら
intent.putExtra(CustomTabsIntent.EXTRA_ACTIVITY_HEIGHT_PX, 1000)
でできるようになると答えてくれましたが、僕の環境が悪いのかダメでした
というか公式ドキュメントにそんな関数は書いてないので恐らく他の機能から持ってきたと予想
他の記事書いてる人はボトムシートの全画面表示できてるみたいだし、GitHub等でIssueとして取り上げられてるわけでもないんですが何故?

ChromeCustomTabsのメリット・デメリット

メリット

  • UIのカスタマイズが可能
    ダークテーマに対応したり、使用するモバイルアプリの色合いに合わせてデザインの調整が可能
  • ブラウザアプリとCookieの共有が可能
    Chrome(と互換性のあるブラウザ)のアプリとChromeCutomTabsのCookieを共有することが可能
    例えば、ChromeでZennを開き、ログインした状態にした後、アプリでChromrCustomTabsでZennを開くとログイン済みの状態でZennを開くことが可能
    ChromeCustomTabsを使用するアプリ同士でも共有が可能
    複数のアプリでChromeCustomTabsでセッション管理をしたりすることも可能
  • WebViewよりも読み込みが速い
    あらかじめリンクを読み込んでおくことで早く表示することが可能
  • Chromeのセキュリティ機能やパフォーマンスを利用可能
    WebViewよりも安全に使うことが可能

デメリット

  • JavaScriptの埋め込みができない
    脆弱性の関係で出来ないのは当然と言えば当然ですが、ChromeCustomTabsでページを開いた場合WebViewのようにJavaScriptを埋め込んだりすることが出来ない
    言い換えると、ChromeCustomTabsで開くブラウザは別アプリ扱いなので、開こうとするアプリからJavaScriptを埋め込めることが出来ない
  • ChromeCustomTabsから何らかのパラメーターを受け取ることが出来ない
    上に関連することですが、ChromeCustomTabsからデータを渡したりすることが出来ない
    (APIの処理が終了したことをアプリに通知するなど)
    一応、ChromeCutomTabsで表示するページでディープリンクを使用してでアプリを開く等をすれば対応できるが、ユーザーエクスペリエンスが劣悪になる
  • URLが表示されてしまう
    タイトルは隠せるんですが、URLは基本隠せません
    上の対策でURLパラメーターを埋め込む場合、ユーザーに見られたくないデータを含む場合困ることになります
    スタックオーバーフロー の回答にあるTrusted Web Activitiesを使用すると回避できるらしいが、環境用意できなかったのと実践例を見つけることが出来なかったため未実施
    時間制限で今回はできませんでしたが、いつか挑戦してみたい
    これをやろうとするとかなり先まで書けなさそうな気がしたので諦めました
  • iOSで使えない
    ASWebAuthenticationSession等似ている機能はあるが、どうしても動き方や制約が異なる部分があるため実装内容次第では問題になる可能性がある
  • 情報が少ない
    登場してから8年以上経過しているがWebViewと比べると情報がかなり少ない

試してみた感想

  • ユーザー側からしたら安全で便利な機能だと思うが、実装する側からすると制約が多すぎて使いづらいなぁという印象でした。
  • 私がiPhoneユーザーなのであまり恩恵を感じる機会がないのですが、ChromeでAmazonをログインしておいて、TwitterでAmazonの商品ページのリンクをタップするとログインした状態で操作できたりするみたいなので便利そうだなと思いました。
  • 制約の多さとiOS側で使えないことが原因で、互換性があって、JavaScriptで色々できるWebViewがまだまだ使われてるんだなと感じました。
  • ChromeCustomTabsの説明を見た最初の印象がWebViewの機能+セキュリティ向上したものでした。
    勝手にそう捉えた自分が悪いんですが、WebViewは出来るのにChromeCustomTabsではできないの?って思うことが結構あったので、WebViewとは似て非なるものだと捉えるべきだなと思いました。

余談

初投稿なので見辛い箇所やわかりくい箇所が多々あると思います。
拙い内容ではありますが、Chrome Custom Tabsについていきなり質問されたときに、このブログ記事を見て大まかなイメージを掴めるような内容を目指しました。
脆弱性の関係で制約があるのは実装する人間としてはなんとなくわかるのですが、これを根拠をもって説明するのが中々難しくて苦労しました。
最初ChromeCustomTabsの正式名称がCustomTabsIntentだと知らなくて少し大回りしました。これをChromeCustomTabsって言いだした理由は何かあるのでしょうか?
書き方やChromeCustomTabsの内容に関するコメントがあれば是非お願いいたします。

参考資料

https://developer.android.com/reference/androidx/browser/customtabs/CustomTabsIntent.Builder
https://developer.chrome.com/docs/android/custom-tabs

脚注
  1. 5/20に検索した結果
    QiitaでCustomTabsIntentで検索

    ZennでCustomTabsで検索

    比較用にQiitaでWebViewで検索(Zennは79件程度)
    ↩︎

Discussion