2015 年 4 月 9 日に開催された Google Developers Summit より「Service Worker で作る最新モバイル ウェブ エクスペリエンス」をダイジェストでブログ記事としてお届けします。


ウェブ vs ネイティブ

同じ機能を提供するアプリケーションでも、スマートフォン上のブラウザで動作するのか、それとも単体のソフトウェアとして動作するのかは、大きなテーマとしてここ数年議論されてきました。

デスクトップでは、ブラウザだけで様々なことがこなせるのはもう当たり前です。Chrome OS のように、ブラウザそのものを OS にしてしまうくらいの発展を、ウェブ技術は遂げてきた結果と言えます。

それではモバイルではどうでしょうか?



これは Flurry が行った調査結果です。

アメリカのスマートフォン ユーザーが使用時間の実に 80% 以上をネイティブ アプリケーションに費やし、それに比べるとウェブの利用時間はごく僅かであることがわかります。利用時間だけを基準にすれば、議論の余地もなくネイティブ アプリケーションの圧勝に見えます。

一体なぜなのでしょう?

私が個人的に立てた仮説はこうです。

デスクトップは広大な海、スマートフォンは小さな宇宙

デスクトップでアプリケーションを使う際、ユーザーは通常、キーボードとマウスを使います。デスクトップの世界では、キーボードやマウスさえ使いこなせれば、広大なインターネットという海の中から、自分が欲しい情報やサービスを自在に探し当てることができます。

それに対しスマートフォンでは、指先 1 つで様々なことをこなさなければなりません。ソフトウェア キーボードで文字を打つだけでもちょっとした作業なため、検索するのも楽な作業ではありません。使いたい機能がワンタップですぐに立ち上がるとしたら、どれほど楽でしょう?

つまり、キーボードとマウスでの操作が想定されたデスクトップから、タッチでの操作が想定されたスマートフォンへの最適化がネイティブ プラットフォームの方が早く進んだためではないか、という仮説です。

それでは、ウェブをよりモバイル フレンドリーにしていくために、我々ができることはなんでしょうか?Chrome がブラウザとして提供できる新しい機能、みなさんが開発者として実装できる新しい機能を
  • エクスペリエンス
  • スピードとオフライン
  • エンゲージメント
という 3 つのパートに分けてご紹介していきます。

エクスペリエンス

ウェブとネイティブを比較した際、最も大きく異なるのはそのアクセス方法です。

ウェブでは最初のアクセスの際、検索やリンクを辿ることでサービスに到達します。ネイティブではマーケットプレイスでインストールすることにより、サービスに到達します。

それでは二回目以降はどうでしょうか?ウェブでは、ブックマークしていない限り、検索やリンクをもう一度辿る必要があります。ブックマークしていたところで、フォルダ構造を辿るなどするため、最低でも数タップが必要です。逆にネイティブの場合、インストール済みのアプリケーションはホーム画面にあるため、アイコンを一度タップするだけで済みます。

インストールのハードルが高いとはいえ、リピートしてもらうことを考えれば、スマートフォンにおいて、ネイティブアプリのようなアクセス方法が理想的なのは明らかですね。ウェブでこれを実現することはできないものでしょうか?

ホーム画面に追加

既に Chrome で利用できる機能に「ホーム画面に追加」というものがあります。これを選択することで、ウェブサイトのブックマークがホーム画面に追加され、ユーザーはワンタップでブラウザを開き、アクセスできるようになります。
さらに、もう一工夫加えることで、これをよりネイティブに近いエクスペリエンスにすることができます。そこで登場するのが Web Manifest です。

manifest.json
{
  "name": "Kinlan's Amazing Application ++",
  "short_name": "Kinlan's Amaze App",
  "icons": [
    {
      "src": "launcher-icon-3x.png",
      "sizes": "144x144",
      "type": "image/png"
    }
  ],
  "start_url": "index.html",
  "display": "standalone"
}

index.html
<link rel="manifest" href="/manifest.json">

Web Manifest は JSON で記述されたウェブサイトのメタ データで、サーバー上に配置したものを HTML から link[rel="manifest"] で参照することにより効力を発揮します。上記の例ではアプリの正式名称(name)、ホーム画面に追加された際のショートネーム (short_name)、アイコンの指定(icons)(画面サイズに応じて複数指定可能)、アイコンをタップした際に開く URL(start_url)、表示モード(display)、を指定しています。この Web Manifest により、ウェブサイトをホーム画面に追加した際の挙動を、よりモバイル フレンドリーなものへと変えることができるのです。

参考 URL はこちら:

フルスクリーン

Web Manifest の displaystandalone とすることで、ホーム画面のアイコンから起動した際に URL バーを隠し、よりネイティブ アプリに近い見た目にすることもできます。

インストール バナー

実はここまでのエクスペリエンスは、iOS の Safari でも同様のことが随分昔からできていました。とはいえ、ホーム画面にアイコンを追加してもらうには、ユーザーの能動的なアクションが必要で、最低でも 2 回のタップを伴うため、決してよく使われる機能ではありません。ホーム画面に追加することを促すバナーをページ上に表示しているサイトも珍しくはありません。

そこで Chrome では、M42 からこの「ホーム画面に追加」をブラウザが自動的に促してくれる機能を追加しました。


Chrome にこのバナーを表示させるためには、いくつかの条件があります。
  • ユーザーがウェブサイトを 1 週間以内に 2 回訪問
  • Web Manifest が存在する
  • Service Worker を使っている
  • HTTPS でサーブされている
※ これらの条件はあくまで現時点のものであり、今後変更される可能性があることにご注意ください。

参考 URL:

テーマカラー

Chrome では meta[name="theme-color"] を使うことで URL バーの色を変更することができます。色は hex のカラー コードを content 属性で指定します。

<meta name="theme-color" content="#db5945">

さらに Android 5.0 以降であれば、最近利用したアプリ一覧を表示した際の表示もここで指定した色で表示されるようになります。

参考 URL はこちら:

スピード・オフライン

ウェブ アプリも、ネイティブ アプリのようにリソースをローカルに持たせることで、起動速度を向上することができます。作り方によってはオフラインでも動作させることができるはずです。

ご存知のとおり、これはウェブにとって初めての試みではありません。Application Cache はこれを実現しようとした最初の標準仕様でしたが、様々な問題点が指摘されるなど、現在も広く使われている機能とは言えません。

そこで代替技術として現在注目を浴びているのが、Service Worker です。

Service Worker とは

Service Worker はオフライン技術を可能にするためだけのものではありません。Service Worker とは、ひとことで言うならこうです:

ユーザーに見えるウェブページの裏側で動かせるイベント駆動の JavaScript 環境

ウェブページで Service Worker を登録することで、様々なイベントを受け取れるようになり、それに伴い任意の動作をバックグラウンドで行えるようになります。例えば
  • ウェブページのリクエストを横取りして、ローカル プロキシのように動作。
  • ウェブページが開いていなくても、イベントを受け取り、スクリプトを実行。

Service Worker バックグラウンドで動作すると言っても、常時動いているわけではありません。イベント駆動になっていますので、各種イベントが発生するのに伴い、様々な短い処理を行うことができます。想定されているユースケースは様々です:
  • ローカル プロキシ
  • オフライン キャッシュ
  • プッシュ通知
  • バックグラウンド同期
  • ジオ フェンシング
fetch イベントを使うと、ドキュメントから発生したあらゆるリクエストをイベントとして受け取り、ローカル プロキシとして動作させることができます。これを使えば、例えば次のようなこともできます:
  • 画面サイズを元に動的に URL を書き換えることで、レスポンシブにサイズの異なる画像を取得する
  • クエリに応じてテスト用のスタブ データを返す
  • JavaScript の依存関係を動的に解決し、必要なリソースを CDN などから取ってくる(ハッカソンで出たアイディア)
なお、Service Worker は非常に強力な機能のため、HTTPS でのみ動作可能(デバッグのため localhost は例外)である点に注意してください。

サンプル コード

コードを見るのが何よりもイメージが湧きやすいと思いますので、簡単な例を示します。

navigator.serviceWorker.register('/sw.js')
  .then(function(registration) {
      // Success!
  }).catch(function(error) {
      // Error...
  });

このコードでは、index.html に存在する JavaScript から Service Worker を登録しています。
登録が成功すると、以後ブラウザのバックグラウンドで Service Worker の JavaScript(この場合 sw.js)が動き続けることになります。

Cache API

この Service Worker 上で fetch イベントを取り、ブラウザからサーバーにリクエストが発行される度に、あらかじめキャッシュしてあるリソースを返すことで、ウェブサイトを高速にしたり、オフラインで利用できるようすることができます。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

このコード例では、リクエストに対し、あらかじめキャッシュしてあるリソースをそのまま返しています。ページの本体である index.html ですらオフラインで動作できるのが Service Worker の強力な点です。


なお、Service Worker によるキャッシュ機能はウェブページそのものとは切り離されているため、非対応ブラウザで実行しても、単純に Service Worker 部が動作しないだけで、ページ自体には問題が発生しにくいという特徴があります。ブラウザ互換性をあまり気にしなくても、対応ブラウザにだけ付加的にメリットを提供できるのも ServiceWorker の魅力の一つです。

参考 URL:

エンゲージメント

モバイルプラットフォームでサービスを提供する方たちがウェブではなくネイティブを選ぶ理由のひとつに、プッシュ通知が挙げられます。メールでは見逃される可能性のあるメッセージを、肌身離さず持っている携帯電話の一等地とも言える場所に届けられるのですから、モバイルのプッシュ通知は大変強力です。
では、これがウェブで実現できたらどうでしょう?それを可能にするのが Service Worker を使ったプッシュ通知機能です。

こちらのデモでは "Enable Push Notifications" のスイッチを入れると同時に、パーミッションを求めるダイアログが表示され、許可をするとそれ以降プッシュ通知を受信することが可能になります。自分で "SEND A PUSH TO GCM VIA XHR" をクリックして動作を確認してみてください。

仕組みはこうです:
  1. ユーザーがサービスにアクセスし、サイトの最新情報を購読するボタンをクリック
  2. 許可を得るダイアログが表示され、ユーザーが許可します。
  3. 仕様上は HTTP Push と呼ばれるプロトコルを使って、サーバーからプッシュが行われます。
  4. しかし、接続するサーバーが多くなればなるほど、張らなければならないコネクション数が多くなってしまうため、Chrome では Android で既に使われている Google Cloud Messaging というサービスを利用することで、コネクションを束ねます。
  5. これにより、端末に必要なコネクションは GCM とのみとなり、サーバーは GCM サーバーに HTTP POST を使ってメッセージを送信するだけで、プッシュが可能になります。
Chrome では現状このような実装になっていますが、Service Worker はオープンスタンダードですので、将来的に Chrome もそれに合わせた挙動になるよう変更されていく予定です。

購読時のサンプル コード:

navigator.serviceWorker.ready().then(function(sw) {
  sw.pushManager.subscribe().then(function(sub) {
    // After permission is granted
    sub.subscriptionId; // registration Id
    sub.endpoint;       // GCM URL
  });
});

ウェブサイトがプッシュ通知の購読を行います。その際、ユーザーに同意を求め、許可された場合のみ、GCM と通信を行い、subscription id を取得します。そしてその subscription id をサーバーに送ります。

プッシュ受信時のサンプル コード:

self.addEventListener('push', function(event) {
  event.waitUntil(
    self.registration.showNotification(title, {
      body: body,
      icon: icon,
      tag:  tag
    }
  });
});

サーバーはその subscription id を使って GCM に Post メッセージを送ることで、プッシュ通知を行います。
Service Worker はプッシュ通知をイベントで受け取り、Notifications API を使って通知を表示します。

参考 URL:

Chrome におけるプッシュ通知の現時点での制約

Chrome のバージョン 42 からプッシュ通知が使えると書きましたが、この段階では未実装機能の制約がとても多いのが現状です。ネイティブアプリ並みのプッシュ通知を行いたい場合は、もうしばらく待つ必要があります。
  • Push メッセージにデータを乗せられない
  • Push されたら通知を出さなければならない
  • Notification にデータを乗せられない
とはいえ、プッシュ通知は大変強力な機能ですので、実際に試し、来るべき未来に備えることをオススメします。

SSL について

既にお気付きの方も多いと思いますが、Service Worker の強力な機能の多くは、ウェブサイトが HTTPS であることを前提としています。
HTTPS は、暗号化や証明書といった用途だけでなく、「ホーム画面に追加」のおおすすめや、Service Worker を使うこと、プッシュ通知を使うなどする際に、必須条件になっています。
  • 「ホーム画面に追加」のおすすめ
  • Service Worker
  • プッシュ通知
  • 暗号化
  • 証明書
この機会にサイト全体を HTTPS にすることをぜひご検討ください。

まとめ

Web Manifest と theme-color を使うことで、あなたのモバイル ウェブサイトは今すぐエクスペリエンスを向上することができます。また、Service Worker の Cache API を使えば、既存の機能を損なうことなく、スピードの改善・オフライン対応することができます。将来に備え、HTTPS やプッシュ通知の導入もご検討ください。

Posted by Eiji Kitamura - Developer Relations Team