Chrome Custom Tabsを使ってWebViewを置き換える


Googleは、Chrome 45からCustom Tabs機能を導入しました。Custom Tabsを使うとアプリ内のWebページ読み込みを大幅に高速化できます。いままでのWebViewでは、動作速度のほかにもセキュリティホールが修正されないなど、OS組み込みブラウザ特有の問題がありましたが、アプリケーションとして更新しているChromeでは常に最新の環境でWebページを閲覧できます。

performance

https://developer.chrome.com/multidevice/android/customtabsより引用)

Custom Tabs機能は、アプリの組み込みブラウザとしてChromeを使えるだけではなく、外部アプリ利用ながらUIデザインも変更可能な点で優れています。アプリに違和感なく組み込めるようにカスタマイズ可能です。

  • ツールバーの色、表示内容の変更
  • Webページ切り替え時のアニメーション(Start/Exit)付与
  • オーバーフローメニューへのカスタムアクション登録
  • プリロード(プリフェッチ)をつかったChromeサービスの起動、Webページ読み込み高速化

CustomTabsをアプリに組み込む

実際にアプリに組み込んでみましょう。サンプルプロジェクトは以下から取得できます。

CustomTabs

サンプルコードと解説はつづきからどうぞ。

プロジェクトを追加する

追記: 本節の方法でなくてもSupport Libraryから利用できます。

■app/guild.gradleのdependenciesに依存パッケージを追加

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:customtabs:23.0.1'
}

以上で設定は完了です(「Chromeプロセスと接続する」まで読み飛ばしても問題ありません)。
追記ここまで

CustomTabsを組み込むためにAndroid Stduioのプロジェクトには、通常のapp以外にcustomtabsプロジェクトとsharedプロジェクト、2つを追加しています。これらプロジェクトの役割は、Chromeブラウザとアプリの間を取り持つService(aidlファイル)とヘルパーライブラリです。sharedプロジェクトはヘルパーライブラリのため必須ではありませんが、あれば便利です。

■settings.gradle

include ':app',':shared',':customtabs'

■app/guild.gradleのdependenciesに依存プロジェクトを追加

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.0'
    compile project(':customtabs')
    compile project(':shared')
}

注意:新規にアプリを作成する場合は、customtabsとsharedプロジェクトは公式リポジトリから取得してください。可能な限り、公式リポジトリから最新のプロジェクトを取得するほうがよいでしょう。今後Chromeブラウザの更新にともない、APIの変更・追加もありえるためです。

Chromeプロセスと接続する

CustomTabsを使うためには、幾つかの手順があります。

  1. アプリとChrome側Serviceをバインド(接続)する
  2. セッションを確立する
  3. Webページ表示画面のUIを設定(オプション)
  4. CustomTabsIntentで遷移する

まずは、アプリとServiceをつなぎます。サンプルコードではバインド、アンバインドのためにbindCustomTabsServiceメソッド、unbindCustomTabsServiceメソッドを用意しました(解説は後述)。まずはアプリの概要を見てみます。
次のサンプルはonCreateメソッドを抜粋したものです。

■src/MainActivity.java

public class MainActivity extends AppCompatActivity{

    private String TAG = "CustomTabs";

    private CustomTabsSession mCustomTabsSession;
    private CustomTabsClient mClient;
    private CustomTabsServiceConnection mConnection;
    private String mPackageNameToBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button b = (Button) findViewById(R.id.button);
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // URLをひらく
                lunchCustomTabs("https://google.co.jp/");
            }
        });
        bindCustomTabsService();
    }

    @Override
    protected void onDestroy() {
        unbindCustomTabsService();
        super.onDestroy();
    }
  ...省略...
}

サンプルアプリは、ボタンを押したらCustomTabsでgoogleを開きます。onCreateメソッドでServiceと接続して、onDestroyメソッドで切断します。クラスのメンバーにCustomTabsのコネクション、クライアント、セッションクラスを持っています。

それぞれの役割は次の通りです。

  • CustomTabsServiceConnection:ブラウザとの接続、切断を行う
  • CustomTabsClient:ウォームアップ(プリロード)やセッションを張る
  • CustomTabsSession:ページの先読みや遷移を行う

CustomTabsとアプリの接続部分をみてみましょう。このサンプルではbindCustomTabsServiceメソッドとunbindCustomTabsServiceメソッドが該当します。

bindCustomTabsServiceメソッドの中でmConnectionインスタンスを生成、ブラウザとの接続を確認した後、セッションを確立しています。

■src/MainActivity.java

public class MainActivity extends AppCompatActivity{
    ...省略...
    private void bindCustomTabsService() {
        if (mClient != null) return;

        // 接続先はChromeのバージョンによって異なる
        // "com.android.chrome", "com.chrome.beta",
        // "com.chrome.dev", "com.google.android.apps.chrome"
        mPackageNameToBind = CustomTabsHelper.getPackageNameToUse(this);
        if (mPackageNameToBind == null) return;

        // ブラウザ側と接続したときの処理
        mConnection = new CustomTabsServiceConnection() {
            @Override
            public void onCustomTabsServiceConnected(ComponentName name,
                                                     CustomTabsClient client) {
                // セッションを確立する
                mClient = client;
                mCustomTabsSession = mClient.newSession(new CustomTabsCallback() {
                    @Override
                    public void onNavigationEvent(int navigationEvent, Bundle extras) {
                        Log.w(TAG, "onNavigationEvent: Code = " + navigationEvent);
                    }
                });
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
                mClient = null;
            }
        };

        // バインドの開始
        CustomTabsClient.bindCustomTabsService(this,
                mPackageNameToBind,
                mConnection);
    }

    private void unbindCustomTabsService() {
        if (mConnection == null) return;
        unbindService(mConnection);
        mClient = null;
        mCustomTabsSession = null;
    }
    ...省略...

ブラウザとのバインドはCustomTabsClientクラスのbindCustomTabsServiceメソッドで行います。バインドの成否はコネクションのイベントとして通知されるので、CustomTabsServiceConnectionクラスのonCustomTabsServiceConnectedメソッド、onServiceDisconnectedメソッドで監視します。

サンプルではonCustomTabsServiceConnectedメソッド(接続済み)でセッションを取得しています。セッションの取得はCustomTabsClientクラスのnewSessionメソッドを利用します。

CustomTabsでウェブサイトを表示する

ボタンが押された際に指定のURLを開く処理をみてみましょう。
CustomTabsで設定できるUI項目は非常に多いため、ビルダーパターンを使って見た目を指定します。
lunchCustomTabsメソッドはビルダーを使いCustomTabsを開くインテントを実行します。

■src/MainActivity.java

    private void lunchCustomTabs(String url){
        // ビルダーを使って表示方法を指定する
        CustomTabsIntent.Builder builder =
                new CustomTabsIntent.Builder(mCustomTabsSession);
        builder.setToolbarColor(Color.BLUE).setShowTitle(true);
        builder.setStartAnimations(this, R.anim.slide_in_right, R.anim.slide_out_left);
        builder.setExitAnimations(this, R.anim.slide_in_left, R.anim.slide_out_right);
        // CustomTabsでURLをひらくIntentを発行
        CustomTabsIntent customTabsIntent = builder.build();
        customTabsIntent.launchUrl(this, Uri.parse(url));
    }

CustomTabsIntent.Builderクラスのコンストラクタにはセッション情報(CustomTabsSessionクラス)が必要です。
サンプルではツールバーを青、タイトルを表示する設定に変更しました。
アニメーションは表示時と終了時が指定できます。ウェブサイトを表示する契機はCustomTabsIntentクラスのlaunchUrlメソッドです。このメソッドを呼んだ後にブラウザ画面へと遷移します。このときsetStartAnimationsメソッドで指定したアニメーションが実行されるので、アプリケーション内部のアニメーションと合わせることで違和感のないブラウザ表示が行えます。

Chromeブラウザをアプリで使うCustomTabsを紹介しました。CustomTabsの組み込みには、customtabsプロジェクトを追加するなど依存ファイルが増える難点もありますが、UIのカスタマイズやブラウザアプリへのコンテキストスイッチの短縮、アップデートが止まったWebViewを使うリスクを回避できるなどパフォーマンス以外の部分でもメリットの大きい仕組みです。

今回紹介したCustomTabsサンプルプロジェクトは次からアクセスできます。

CustomTabsのより高度な利用はGoogleのサンプルリポジトリを確認してください。今回、紹介していないChromeプロセスのプリロード(CustomTabsClientクラスのwarmupメソッド)や、URLの先読み(CustomTabsSessionクラスのmayLaunchUrlメソッド)、ツールバーのカスタマイズなど応用例が豊富です。

以上、おつかれさまでした。

2 Comments