HTML5 の AppCache と IndexedDB を使ったオフライン エクスペリエンスの構築
本記事は、マイクロソフト本社の IE チームのブログ から記事を抜粋し、翻訳したものです。
【元記事】Building Offline Experiences with HTML5 AppCache and IndexedDB (2011/9/27 9:14 AM)
ユーザーはネットワークを利用できないときでも、Web サイトやアプリケーションが快適に動作することを望んでいます。データがますますクラウド上に保存されるようになった今、開発者は、デバイスがネットワークから切断されている場合や電波の届かない場所にある場合などの接続不可の環境にあってもデータにアクセスすることができる、スムーズなエクスペリエンスを実現することを求めています。
この記事では、HTML5 の次の機能を使用して、正常に動作するオフライン サイトやアプリケーションを作成する方法を説明します。
- AppCache: ファイルのリソースをローカルに保存し、それらを URL としてオフラインでアクセスする
- IndexedDB: 構造化したデータをローカルに保存し、ユーザーによるアクセスと照会を可能にする
- DOM ストレージ: 少量のテキスト情報をローカルに保存する
- オフライン イベント: ネットワークに接続しているかどうかを検出する
例: あらゆる場所からのアクセスを可能にするオフライン サポート
たとえば、お気に入りの料理サイトのレシピを印刷して買い物に出かけたにもかかわらず、スーパーに行ってみると、必要な材料がいくつかないことに気付いたとします。
もし先ほど自宅でこのレシピのサイトをモバイル PC で閲覧し、そのデータの一部が自動的にダウンロードされていて、それをオフラインで利用することができたら、と想像してみてください。これが可能なら、モバイル PC をスーパーに持って行き、先ほどのサイトにアクセスすれば、その場で新しいレシピを検索することができます。この一番のメリットは、 ネットワークに接続しなくてもよいことです。必要なときに必要な場所で利用できれば、利用者にとってそのサイトの価値はとても高くなります。
レシピのサイトで「 cake 」という単語をオフライン検索した結果
開発者は、AppCache、IndexedDB、DOM ストレージ、WebSocket (または XHR) などのオフライン テクノロジを組み合わせて、こうしたタイプのシナリオを可能にすることができます。個別のテクノロジを解説する前に、これらのメリットについて説明しておきましょう。
Metro スタイル アプリや Web サイトでオフライン テクノロジを使用すると、接続のエラーに対処することができます。
たとえば、ユーザーがフォームに入力しているときにネットワーク接続が切断された場合、その Web サイトまたは Metro スタイル アプリはどのように動作すべきでしょうか。接続にとらわれない開発の理念に従えば、ネットワーク接続の有無に関わらず、アプリは正しく機能し続けます。
さらに高度なシナリオでは、アプリケーションが完全にオフラインの場合でも、ユーザーは Web サイトやアプリ内で新しいコンテンツを作成したり、新しいデータを保存したりすることができます。
たとえるなら、Outlook Web Access (OWA)、Hotmail、GMail が、現在の Outlook のように、オフラインでシームレスに動作するようになるというイメージです。
オフライン テクノロジでは、キャッシュされたリソースをローカルで提供したり、将来的に必要になる情報を事前キャッシュしたり、クラウド (またはネットワーク) の処理能力をクライアント デバイスにシフトしたりすることにより、パフォーマンスを全体的に向上させることもできます。
より多くの情報をローカルにキャッシュし、ローカルで検索し、ローカルで計算できるようになれば、サーバーから取得が必要なリソースは減少し、ユーザー エクスペリエンスはさらに高速になります。
Metro スタイル アプリをオフラインで実行したいという要望は、Web サイトに対する要望を上回っています。Metro スタイル アプリは、ストアから購入した独立パッケージを使用して配置するため、ユーザーは何らかのオフライン機能 (ゲーム、書籍、レシピなど) が含まれていることを期待します。仮にそれらのアプリで、新しいコンテンツを作成したり、新しいコンテンツにアクセスしたりできないとしても、以前のコンテンツ (連絡先、会議、フィード、マガジンなど) は表示できる必要があります。
AppCache を使用してファイル リソースをローカルにキャッシュする
AppCache を使用すると、ダウンロードしたファイル リソースについて寿命の長いローカル キャッシュを作成できます。このリソースにはオフライン中にアクセス可能ですが、パフォーマンスを上げるために、オンライン中に使用することもできます。
たとえば、3 歳の子供がノート PC を使って、ホーム ネットワークからインタラクティブな Web ゲーム (KidsBook) をダウンロードするとします。このとき、アプリケーションのリソースがローカルに保存されていれば、この子は車の中 (ネットワークに接続していないとき) でもゲームをすることができます。
KidsBook が AppCache を使用して実装されている場合は、必要なリソース (JavaScript、HTML、CSS、オーディオ、ビデオなど) があらかじめキャッシュされるため、後でネットワークから切断されても、ゲームを最初にダウンロードしたときと同じように遊べるようになります。このため、デバイスがネットワークに接続されていなくても、この子はゲームを楽しむことができるのです。
インタラクティブな Web ゲームをオフラインで実行する方法については、IE Test Drive サイトのサンプル KidsBook をご覧ください。
AppCache では、Web サイトからコンテンツをキャッシュするために、マニフェスト ファイルを使用してリソースの URI を指定します。ブラウザーに Web ページが表示された後、バックグラウンドでキャッシュが行われます。そこで、マニフェスト ファイルに定義されているリソースがダウンロードされます。
これによって、1 つのトランザクション内の 1 つの単位としてリソースがローカル マシンにダウンロードされ、ローカル キャッシュを作成することが保証されます。1 つでもリソースのダウンロードに失敗した場合、キャッシュは作成されません。
キャッシュに保存されているコンテンツを更新するには、サーバー上のマニフェスト ファイルを更新します。ユーザーが次にそのサイトにアクセスすると、ブラウザーがサーバー上のマニフェスト ファイルと最後にキャッシュされたマニフェスト ファイルのコピーとを比較します。キャッシュされたマニフェストのコピーとサーバー上のコピーが異なる場合、更新されたマニフェスト ファイルに定義されているコンテンツを使用して、キャッシュの新しいバージョンが作成されます。
また AppCache を使用すると、Internet Explorer や Metro スタイル アプリから一般的な URL を使用して、キャッシュされたリソースにオフラインでアクセスできるようになります。そのため、ネットワークに接続していないときでも、ブラウザー ウィンドウに URL を入力してその情報にアクセスできます。さらに、オフラインのページは、ローカルのキャッシュ情報を使用して URI を解決できます。コードのサンプルについては、IE10 Developer Guide の「HTML5 Application Cache ("AppCache") 」セクションをご覧ください。
総合的に判断して、AppCache は HTTP のキャッシュより優れています。HTTP のキャッシュでは、インターネット一時ファイル (Temporary Internet Files: TIF) がクリアされた後に、キャッシュされたリソースが利用できるとは保証されていません。また HTTP のキャッシュでは、オフライン中に正しく URL を解決できません。ただし、キャッシュされるリソースの有効期間を指定することにより、HTTP のキャッシュを使用して AppCache の動作を最適化することができます。これによって、ローカル キャッシュの新しいバージョンが作られるときに、リソースを Web からダウンロードするのか、キャッシュからコピーするのかを決定します。
Metro スタイル アプリでは、iframe がアクセスする Web リソースをローカルにキャッシュして、オフラインでコンテンツにアクセスできるようになるという点で、AppCache が役立ちます。
IndexedDB を使用して大規模な構造化データをローカルにキャッシュする
IndexedDB は、JavaScript オブジェクトをローカル マシンに保存して、オブジェクトに対する高速なインデックス処理と検索を可能にするローカル データベースです。先ほど紹介したレシピのサイトには、親サイトから抽出された 16 のレシピを含むデータベースが組み込まれています。たとえば、RSS フィードや WebSocket、XHR 接続を使用してこのデータベースを定期的に更新することができれば、ユーザーはネットワークに接続していなくても、最新のレシピにアクセスすることができます。
IndexedDB は JavaScript オブジェクトを直接操作したり、インデックス処理することができます。IndexedDB でローカルに情報を検索する方法には、クラウドを常時検索しなくて済むため、計算コストを削減できるというメリットがあります。ただしそのためには、ローカル システムにキャッシュされるデータの適合性を維持できることが前提となります。
IndexedDB によってアクセス可能なローカル マシンに保存されているレシピのリスト
IndexedDB は ISAM データベースの概念に基づいて作成されたテクノロジです。多くの Web プラットフォーム テクノロジと同様、このテクノロジは、その上に構築されるさまざまなライブラリ抽象化層から利用可能な低レベル API を提供するように設計されています。次の表では、IndexedDB の開発概念と、一般的なリレーショナル モデルにおける類似の概念とを比較しています。
概念 |
リレーショナルデータベース |
IndexedDB |
データベース |
データベース |
データベース |
テーブル |
テーブルは列と行で構成される |
objectStore は Javascript オブジェクトとキーで構成される |
照会メカニズム、Join、フィルター |
SQL |
カーソル API、キー範囲 API、アプリケーション コード |
トランザクション タイプとロック |
READ_WRITE トランザクションでデータベース、テーブル、または行にロックが起きる場合がある |
VERSION_CHANGE トランザクションでデータベースに、また、READ_ONLY および READ_WRITE トランザクションで objectStore にロックが起きる場合がある。オブジェクト レベルのロックはない |
トランザクションのコミット |
トランザクションの作成は明示的。既定では、commit を呼び出さない限りロールバックする |
トランザクションの作成は明示的。既定では、abort を呼び出すか、キャッチされない例外がある場合を除き、コミットする |
プロパティ参照 |
SQL |
オブジェクトのプロパティを直接照会するにはインデックスが必要 |
レコード/データ |
正規化形式の単一値プロパティ |
非正規化形式で、複数値プロパティが可能 |
IndexedDB を使用する場合は、オブジェクト ストア (連絡先、電子メール、会議など) を含むデータベースを作成します。これらのオブジェクト ストアは、アプリケーションが必要とする JavaScript オブジェクト (連絡先の場合: 姓、名、住所など) を格納します。各 JavaScript オブジェクトには、keyPath からアクセス可能な一意識別子が付けられます。また、オブジェクト ストアには、データセット (電子メールの場合: 件名、日付など) を照会する際に使用するプロパティのインデックスが含まれます。インデックスやオブジェクト ストアに KeyRanges を使用することにより、フィルターを適用して、結果セットを編成したり削減することができます。
次のコード スニペットは、"Library" データベースから本のレコードを読み取る方法を示しています。
var oRequestDB = window.indexedDB.open("Library");
oRequestDB.onsuccess = function (event) {
db1 = oRequestDB.result;
if (db1.version == 1) {
txn = db1.transaction(["Books"], IDBTransaction.READ_ONLY);
var objStoreReq = txn.objectStore("Books");
var request = objStoreReq.get("Book0");
request.onsuccess = processGet;
}
};
オブジェクト ストアに保管されている情報には、transaction のコンテキストでいつでも読み取りや書き込みのためのアクセスが可能です。トランザクションには、次の 3 つのタイプがあります。
- VERSION_CHANGE: オブジェクト ストアやインデックスの作成および更新に使用します。VERSION_CHANGE トランザクションはデータベース全体をロックし、並行操作をできなくするため、データベース レコードの読み取りや書き込みには推奨できません。
- READ_WRITE: オブジェクト ストアに格納されているレコードの追加、読み取り、修正、削除を可能にします。
- READ_ONLY: オブジェクト ストアに格納されているレコードの読み取りを可能にします。
IndexedDB の非同期 API モデルは、XHR など多くの Web API でサポートされている要求/応答モデルを活用します。要求はローカルの IndexedDB プロセスに送信され、その結果はクライアントの onsuccess または onerror イベント ハンドラーで処理されます。また、トランザクションをコミットする明示的なメカニズムはありません。サーバーに保留中の要求がなくなり、クライアントに保留中の結果がなくなると、トランザクションがコミットされます。さらに、例外イベントとエラー イベントの処理はアプリケーションに任されています。多くの場合、例外イベントまたはエラー イベントが処理されないと、トランザクションが中止されます。
要約すると、IndexedDB は、インデックスを使ってデータ オブジェクトを照会するために最適化されたメカニズムです。IndexedDB は、カーソルを使って大量の関連データにアクセスしたり、KeyRange オブジェクトを使ってデータをフィルター処理するための API をサイトに提供します。高速な検索とオフライン データ アクセスを実現するためには、すべてのユーザー レコードを含む "マスター" データベースをクラウド上に用意したうえで、レコードのサブセットを含むローカルの IndexedDB データベースを用意するというパターンを、開発者の皆さんにお勧めします。
DOM ストレージとオンライン/オフライン イベントを使用して少量のテキスト データをローカルに保存する
サイトでは、DOM ストレージを使って少量のテキスト データを処理したり、接続イベントを使って接続状態の悪化を検出したりすることができます。たとえば、これらのテクノロジを使ってオフライン中にユーザーのスコアを追跡できるゲームがあるとします。このゲームがネットワークに接続していないときにハイ スコアが出た場合、Web ページがハングしたり、クラッシュする可能性はあるのでしょうか。
このデータは基本的にテキスト形式なので、大きなスペースを必要としません。そのため、DOM ストレージを使用して、ネットワークに接続することなく情報をローカルに保存できます。保存した情報は、後でネットワーク接続が可能になったときにアップロードできます。DOM ストレージは Cookie よりも大きなデータをサポートし、データのエンコードも必要ありません。さらに、DOM ストレージは要求ごとにデータをサーバーに送信せず、ドメイン アクセスまたはセッション アクセスにスコープを設定できます。
このテクノロジは、windows.localStorage オブジェクトにアクセスするだけで使用できます。このオブジェクトの中で、名前/値のペアをプロパティの形式でチェックまたは追加できます。次のコード スニペットは、localStorage を使用してゲームのスコアをローカルに保存する方法を示しています。
<script type="text/javascript">
var lStorage;
function init() {
if (window.localStorage) {
lStorage = window.localStorage;
if (typeof lStorage.score == 'undefined')
lStorage.score = 0;
document.getElementById('score').innerHTML = lStorage.score;
}
}
function save() {
if (lStorage) {
lStorage.score = getGameScore();
}
}
</script>
...
<body onload="init();">
<p>
Your last score was: <span id="score">last score insert in init()</span>
</p>
</body>
さらに、offline/online イベントによってネットワーク アクセスの回復が検出されると、サーバーにデータをプッシュすることができます。たとえば、オンライン状態が検出されると、WebSocket や XHR を使用して、サーバーのコンテンツでデータベースを更新できます。
オンライン状態を検出するには、navigator.onLine プロパティの状態をチェックするだけです。次のコードは、オンライン イベントとオフライン イベントを記録する方法を示しています。
function reportConnectionEvent(e) {
if (!e) e = window.event;
if ('online' == e.type) {
alert('The browser is ONLINE.');
}
else if ('offline' == e.type) {
alert('The browser is OFFLINE.');
}
}
window.onload = function () {
status = navigator.onLine; //retrieve connectivity status
document.body.ononline = reportConnectionEvent;
document.body.onoffline = reportConnectionEvent;
}
Metro スタイル アプリでは、Windows.ApplicationData という API も利用できます。この API では、ローカルに保存できるデータのタイプが増え、複数のマシン上でデータのローミングも可能です。
アプリケーションや Web サイトを設計する際に重要なポイントは、接続が失われる状況は予測できないので、そうした状況にもスムーズに対応できるよう心がける必要があるということです。情報をクラウドに送信する前にローカルで保存するというデータ パターンを実装することにより、不安定なネットワーク接続に対処することができます。
WebSocket と XHR を使用してローカル データを更新する
あるシナリオでは、どのデバイスからも簡単にアクセスできるように、顧客のデータが常にクラウド上に配置されます。そのため、キャッシュされたデータが常に適合していること、および最新の状態であることを保証できなければなりません。これには、クラウドとアプリケーションの間にデータを同期させるためのチャネルを作る必要があります。WebSocket と XHR を活用することで、この同期を実現できます。データを転送可能なフォーマット (XML、JSON など) にパッケージ化し、XHR または WebSocket を使用してそれらのリソースをクライアントに転送し、最後に XML または JSON パーサーを使用して IndexedDB データベースに格納する JavaScript オブジェクトを作成します。この方法は、DOM ストレージに保存されている情報をサーバーにアップロードするためにも使用できます。
まとめ
ネットワーク接続は常に安定しているとは限りませんが、アプリケーションには常に安定していることが求められます。ネットワーク障害を見越したオフライン テクノロジを使用すると、多くの利用者のシナリオや状況において、アプリケーションの価値を高めることができます。また、サイトや Metro スタイル アプリをオフラインでも正常に動作するようにしておくことは、差別化を図るための絶好のチャンスでもあり、これによって有用性が高まることで、サービス提供の機会も拡大します。ここでご紹介したさまざまなオフライン テクノロジ (AppCache、IndexedDB、DOM ストレージなど) を活用して、最大限の情報をローカルにキャッシュしてください。
詳細については、BUILD のプレゼンテーション「Building offline access in Metro style apps and Web sites using HTML5 ( 英語 ) 」をご覧ください。こちらでは、オフライン シナリオを処理するための File API の役割についても説明しています。
—Israel Hilerio、Internet Explorer 主任プログラム マネージャー