次の方法で共有


Windows Phone 7 アプリケーション

Windows Azure と Windows Phone 7 を使用してデータ駆動型アプリケーションを構築する

Danilo Diaz

この 30 年の間に、コンピューター ハードウェア産業は爆発的な進化を遂げました。メインフレームからデスクトップ コンピューターやハンドヘルド デバイスに至るまで、ハードウェアは小型化してもますます強力になっています。このようにコンピューターの処理能力が絶えず増大しているため、開発者は少し甘い考えを持つようになってしまい、今では、作成するアプリケーションの実行場所となるすべてのデバイスに無限のコンピューター リソースがあることを期待するようになっています。多くの若い開発者は、コードのサイズと効率が重要な要素だった時代を経験していません。

開発における最新のトレンドは、スマートフォンの人気の高まりを利用することです。スマートフォン デバイス向けのコーディングを行う際、多くの開発者は、最近の電話はほんの数年前のデバイスと比べて非常に強力であるとはいえ、制約も受けるという事実に向き合う必要があります。こうした制約は、サイズ、プロセッサの処理能力、メモリ、および接続性に関連するものです。モバイル アプリケーションを作成する際は、優れたパフォーマンスと最適なユーザー エクスペリエンスを確実に実現するために、こうした制約にどのように対処すればよいかを理解する必要があります。

アプリケーション パフォーマンスが最適にならない理由には、開発者による設計上の判断ミスが直接の原因となるものが含まれる場合もあります。ですが、開発者が直接制御できない要素が理由に含まれる場合もあります。サードパーティ サービスが低速またはオフラインであること、モバイル ブロードバンド接続が中断されていること、または扱っているデータの性質 (ストリーミング メディア ファイル、大量のデータなど) によって、アプリケーションのパフォーマンスが低くなる場合があります。

原因が何であれ、アプリケーションのエンド ユーザーが感じるパフォーマンスは、すべてのソフトウェア開発者にとって最大の関心事の 1 つでしょう。この記事では、優れたユーザー エクスペリエンスを提供し、スムーズにスケール変更することができるような方法で、堅牢なデータ駆動型の Windows Phone 7 アプリケーションを設計するための考慮事項のいくつかについて概説します。

まずは、設計とコーディングに関するいくつかの選択肢について調べるために使用できるシナリオを準備しましょう。例として、ユーザーが選択したフライトに関する情報を提供する、架空の旅行情報アプリケーションを使用します。図 1 が示すように、このアプリケーションのメイン画面には、現在の天気やフライト状況などのいくつかのデータ要素が表示されます。アプリケーションが表現力を増し、よりデータ中心になるにつれて、開発は少し困難になっていき、コードが十分なレベルに達しない可能性のある領域が増えることがわかります。

image: The Flight Information Sample App

図 1 サンプルのフライト情報アプリケーション

UI スレッドのブロック

まずは、UI について考えてみましょう。デスクトップ向けにコーディングしているかのようにアプリケーションを設計することでパターンを間違えることはありがちなので、電話固有の UI の問題のいくつかについて考えてみましょう。

アプリケーションがユーザーの操作に期待どおりに反応しない場合、ユーザー エクスペリエンス全体に大きな影響を与える可能性があります。指をスキャナー上に滑らせるジェスチャ、タップ ジェスチャ、または縮小ジェスチャへの反応が遅いと、アプリケーションの全体的な魅力に悪影響を与えることがあります。ですが、この後で説明するように、これらは、未然に防いだり対処したりするのが非常に簡単な問題です。

ListBox について考えてみましょう。ItemTemplate に画像が含まれている場合、または ItemTemplate がフィードからデータを読み込んでいる場合、UI スレッドがブロックされ、要求や計算が完了するまで UI が一時停止する可能性が大いにあります。したがって、UI を開発する際のアプローチの 1 つは、長時間かかる計算 (WebRequest を含む) を UI スレッド以外の場所で実行するというものです。実際のところ、これはモバイル アプリケーションに限らず、すべてのアプリケーションにとって有効なアプローチです。

パフォーマンス上の問題をもたらす可能性があるもう 1 つの問題は、ListBox コントロールへの挿入を調整せずに多数の項目を ItemSource にバインドする場合に発生します。より適切なアプローチは、ObservableCollection をバインドし、20 ~ 30 ミリ秒ごとにいくつかの項目をコレクションに設定するというものです。こうすると、UI スレッドのロックが解除され、UI スレッドがユーザーの操作にすぐに反応するようになります。

今回のサンプル アプリケーションの場合、画面上で画像も大いに利用します。ListBox では、画像のデータを表示するために画像を実際にダウンロードする必要があります。これは問題ないように思えますが、この処理を UI スレッドで行うと、ユーザーはジェスチャによる入力を行うことができなくなります。バックグラウンド スレッドで画像を読み込むと、メモリ要件と UI スレッドの解放の両方の面で多くの問題が解決されると同時に、アプリケーションがより高速になります。

ユーザーに対して表示するものはすべて、レンダーされる必要があります。レンダリングを行うには、正しく表示するためのレイアウト、位置調整、および計算が必要です。UI により多くのレイヤーが追加されるにつれて、計算と全体的なレンダリングのコストは増大します。Silverlight では、既に UI を仮想化していますが、バインドされるデータは仮想化していません。つまり、10,000 個の項目を ListBox にバインドする必要がある場合、項目がレンダーされる前に Silverlight では 10,000 個の ListItem すべてをインスタンス化するということです。

データバインドされるものに注意し、バインドする項目をできるだけ少なく抑えるようにします。大量のデータバインド項目を処理する必要がある場合は、レンダリングをバックグラウンドで動的に処理することを検討します。もちろん、これはデスクトップ アプリケーションにも当てはまります。電話では、このような選択の影響がより大きいというだけです。

ValueConverter はカスタム コードを使用して定義され、実際の要素のレンダリングとレイアウトより前にレンダリングをあらかじめ決定しキャッシュすることはできないので、ValueConverter はレンダリングのパフォーマンスに非常に大きな影響を与えることがあります。

データを処理する

次は、Windows Phone 7 のデータ ストレージについて説明する必要があります。単刀直入に言うと、開発者が使用できるリレーショナル データベース エンジンはありません。Windows Phone 7 OS と共に SQL Server Compact (SQL CE) がインストールされますが、現在、開発者が使用できる API はありません。したがって、アプリケーション データ (今回の例では旅行情報) を格納するためのデータベースを作成しても、うまくいきません。

とはいえ、アプリケーションとデータをやり取りする方法にはさまざまな選択肢があります。一般的なアプローチの 1 つは、データの永続化のために Windows Azure などのクラウド サービスを使用するというものです。アプリケーションのサービス層を構築するために使用できるテクノロジはたくさんありますが、最も一般的なのは REST と SOAP です。多くの開発者にとっては SOAP が第 1 の候補ですが、REST の方が効率的で実装しやすいデータ要求方法を提供すると考えています。

アプリケーションにデータを提供し、次のような REST 表現を使用してアクセスできるメソッドをいくつか使用します。

/Trip/Create/PHL-BOS-SEA/xxxx/2010-04-10
/Flight/CheckStatus/US743

REST を使用すると、メッセージ形式に XML または JSON を使用することができます。

Web フロントエンドの観点から、ASP.NET MVC フレームワーク (asp.net/mvc) を選択しました。このフレームワークを使用すると、要求を処理し、カスタム ビューを使用して任意の種類のマークアップを返すことができるためです。

サンプル アプリケーションでは、旅行とフライトの両方の情報を処理する必要があるので、この情報を取得するために要求をインターセプトする FlightController と TripController を作成します (次のコード参照)。

// GET: /Flight/CheckStatus/US743
public ActionResult CheckStatusByFlight(
  string flightNumber) {
  return CheckStatus(flightNumber, DateTime.Now);
}

// GET: /Flight/RegisterInterest/US743/2010-04-12
public ActionResult CheckStatus(
  string flightNumber, DateTime date) {
  Flight f = new Flight(flightNumber, date);
  GetFlightStatus(f);
  return new XmlResultView<Flight>(f);
}

単純化されたアクセス方法を提供し、帯域幅を数バイト節約するため、日付が今日の場合は、今日の日付を明示的に指定せずにこのデータを取得するためのショートカット メソッドを設計してもよいでしょう。

キャッシュされたデータと永続的なデータ

フライト状況サービスは、アプリケーションの要素のうち、アプリケーションを作成している開発者が制御できない要素なので、パフォーマンス上の難題の一部となります。よくできたアプリケーションは非常に多くの要求を受信する場合があるので、キャッシュ戦略について考えることは重要です。

通常、フライトの出発時刻が近いほど、そのフライトの情報に対する要求の数が多くなることが予想されます。ほぼ同時に行われる要求の数が多いと、アプリケーションのパフォーマンスだけでなく、データの格納と操作にかかるコストにも影響することがあります。原則として、Windows Azure アプリケーションでは要求と応答の両方に基づいて帯域幅料金が発生します。また、フライト情報サービスではアクセス料金も発生する可能性があります。返されるデータの量は、アプリケーションに必要な量を超えないようにする必要があります。

Windows Azure Platform は、データ ストレージのさまざまな選択肢を提供します (テーブル、BLOB、およびキューから、SQL Azure を通じた、リレーショナル データベースのようなストレージに至るまで)。サンプル アプリケーションでは SQL Azure を使用することにしました。SQL Azure では、なじみのある SQL Server のプログラミング手法が使用されており、また、SQL Azure を使用すると、簡単に、キャッシュされたフライト データと永続的な旅行情報の両方を格納し、両方にアクセスすることができるためです。

図 2 は、今回 Entity Framework を使用して設計した単純なストレージ層を示しています。

image: Flight Data Storage Schema

図 2 フライト データのストレージ スキーマ

データを返す

サンプル アプリケーションでは、カスタム ビューを通じてデータをクライアントに返します。ASP.NET MVC を使用しているので、各ビューは ActionResult から派生し ExecuteResult を実装する必要があります。

前述のとおり、REST サービスを通じてフライト情報を XML または JSON で表現することができます。まずは XML オプションについて見てみましょう。XML を生成するためのシリアライザーには型が必要なので、ジェネリック クラスを作成します (図 3 参照)。

図 3 XML のシリアル化

public class XmlResultView<T> : ActionResult {
  object _model = null;
  public XmlResultView(object model) {
    this._model = model;
  }

  public override void ExecuteResult(ControllerContext context) {
    // Create where to write 
    MemoryStream mem = new MemoryStream();

    // Pack characters as compact as possible, 
    // remove the decl, do not indent.
    XmlWriterSettings settings = new XmlWriterSettings() { 
      Encoding = System.Text.Encoding.UTF8, 
      Indent = false, OmitXmlDeclaration = true };
    XmlWriter writer = XmlTextWriter.Create(mem, settings);
    
    // Create a type serializer
    XmlSerializer ser = new XmlSerializer(typeof(T));

    // Write the model to the stream
    ser.Serialize(writer, _model);

    context.HttpContext.Response.OutputStream.Write(
      mem.ToArray(), 0, (int)mem.Length);
  }
}

JSON を使用した場合も同じくらい簡単にデータを処理することができます。サンプル ソリューションの中で、JSON を使用した場合に XML の場合と異なるのは、ExecuteResult メソッドの内容だけです。JsonResult を使用すると、サービスから返す JSON 形式の値を次のようにたった数行のコードで生成することができます。

// Create the serializer
var result = new JsonResult();
// Enable the requests that originate from an HTTP GET
result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
// Set data to return
result.Data = _model;
// Write the data to the stream
result.ExecuteResult(context);

実際のデバイスへのデータの保存はどうでしょうか。ユーザーが旅行情報にアクセスする必要が生じるたびに、アプリケーションでサービスからデータを取得するのは適切ではありません。Windows Phone 7 にはリレーショナル データ ストアはありませんが、開発者は分離ストレージという機能を利用することはできます。これは Silverlight 4 の分離ストレージと同じように機能しますが、サイズ制限がありません。

電話上でデータの保存と取得を行うには、SaveData と GetSavedData という 2 つの主なメソッドが必要です。これらのメソッドをフライト情報アプリケーションでどのように使用するかを表す例を図 4 に示します。

図 4 ローカル データの保存と取得

public static IEnumerable<Trips> GetSavedData() {
  IEnumerable<Trips> trips = new List<Trips>();
  try {
    using (var store = 
      IsolatedStorageFile.GetUserStoreForApplication()) {
      string offlineData = 
        Path.Combine("TravelBuddy", "Offline");
      string offlineDataFile = 
        Path.Combine(offlineData, "offline.xml");

      IsolatedStorageFileStream dataFile = null;

      if (store.FileExists(offlineDataFile)) {
        dataFile = 
          store.OpenFile(offlineDataFile, FileMode.Open);
        DataContractSerializer ser = 
          new DataContractSerializer(
          typeof(IEnumerable<Trips>));

        // Deserialize the data and read it 
        trips = 
          (IEnumerable<Trips>)ser.ReadObject(dataFile);
        dataFile.Close();
      }
      else
        MessageBox.Show("No data available");
    }
  }

  catch (IsolatedStorageException) {
    // Fail gracefully
  }

  return trips;
}

public static void SaveOfflineData(IEnumerable<Trips> trip) {
  try {
    using (var store = 
      IsolatedStorageFile.GetUserStoreForApplication()) {

      // Create three directories in the root.
      store.CreateDirectory("TravelBuddy");

      // Create three subdirectories under MyApp1.
      string offlineData = 
        Path.Combine("TravelBuddy", "Offline");

      if (!store.DirectoryExists(offlineData))
        store.CreateDirectory(offlineData);

      string offlineDataFile = 
        Path.Combine(offlineData, "offline.xml");
      IsolatedStorageFileStream dataFile = 
        dataFile = store.OpenFile(offlineDataFile, 
        FileMode.OpenOrCreate);

      DataContractSerializer ser =
        new DataContractSerializer(typeof(IEnumerable<Trip>));
      ser.WriteObject(dataFile, trip);
                   
      dataFile.Close();
    }
  }

  catch (IsolatedStorageException) {
    // Fail gracefully
  }
}

ネットワーク障害に対処する

モバイル デバイスで使用されるネットワークの接続性はさまざまで、場所、混雑、またはユーザーによる手動での切断 (たとえば飛行機モードの場合など) が原因で接続がまったく利用できなくなることもあります。これを現実として受け入れなければなりません。モバイル アプリケーション開発者は、アプリケーションを構築する際にこれを考慮に入れる必要があります。

別の種類のネットワーク障害として、サービス層の障害があります。多くのモバイル アプリケーションは、サードパーティ サービスのデータを利用します。こうしたサービスは、サービス レベル アグリーメントを伴わない場合があります。その場合、アプリケーションはサービス プロバイダーに翻弄されてしまいます。つまり、アプリケーション開発者はアプリケーションを完全に制御することができず、停止に対処する準備をしておく必要があるということです。

ネットワーク障害の原因が何であれ、アプリケーションでは、できる限り最適なユーザー エクスペリエンスを提供する必要があります。どのような種類のネットワーク障害が発生した場合も、ある程度の機能を提供する必要があります。サンプルのフライト状況アプリケーションでは、これは、サーバー側、クライアント側のどちらからネットワーク接続が切断された場合でも、ユーザーができるだけ多くの情報にアクセスできるようにする必要があることを意味します。

これを実現する方法はたくさんあります。ここでは、可能なときにデータを取得する、データをローカルにキャッシュする、管理下にあるサーバーにデータをキャッシュするという 3 つの簡単な方法に的を絞ります。

プッシュ通知を使用する

ユーザーがアプリケーションに旅行情報を入力すると、その情報はクラウド サービスにアップロードされます。続いて、クラウド サービスは、フライトと天気のデータを提供するさまざまなサービスのポーリングを開始します。また、クラウド サービスは、データの経時的な変化 (フライト状況の変化、遅延を報告している空港など) を探します。

変化が見つかったら、できるだけ迅速かつ効率的にその情報をユーザーに知らせる必要があります。その方法の 1 つは、サービスが情報をクライアント アプリケーションにプッシュするというものです。これにより、ユーザーは、データが利用可能になるとすぐに、利用可能な最新のデータのセットにアクセスできるようになります。データはクライアントにプッシュされているので、ユーザーのネットワーク接続が切断されても、ユーザーはデータを利用することができます。

Windows Phone プッシュ通知を使用して Windows Azure サービスでこれを実現することができます。Windows Phone プッシュ通知機能は、監視サービス、マイクロソフト プッシュ通知サービス、およびメッセージ処理メソッドの 3 つのコンポーネントで構成されています。

監視サービスは、アプリケーション用の新しい情報を絶えず探すクラウド サービスです。これについては、後で詳しく説明します。

プッシュ通知サービスは、Windows Phone 7 デバイスにメッセージを中継するために使用されるマイクロソフトのホストされたサービスの一部です。このサービスは、すべての Windows Phone 7 アプリケーション開発者に提供されます。

メッセージ ハンドラー メソッドは、その名のとおり、単純に、プッシュ通知サービスからメッセージを受け取ります。

Windows Phone 7 には、通知の種類が既定で 3 つあります。タイル通知、プッシュ通知、およびトースト通知です。通知はユーザー エクスペリエンスの重要な一部なので、使い方を慎重に検討する必要があります。繰り返し発生する通知や煩わしい通知は、アプリケーションのパフォーマンスや、デバイス上で実行される他のアプリケーションのパフォーマンスを低下させることがあります。また、ユーザーをいら立たせてしまう可能性もあります。通知の送信頻度と、ユーザーの注意を引くイベントの種類を検討してください。

Windows Phone 7 では、通知はバッチ処理を通じて配信されるので、トランザクションはすぐには行われない可能性があります。通知がタイムリーに行われることは保証されず、通知をどのようにクライアントに配信するかについての決断はサービスによって下されます。サービスは、どれくらい迅速に電話にメッセージを配信できるかを判断するために最善を尽くします。

プッシュ通知のワークフローは次のとおりです。

  1. クライアント アプリケーションは、プッシュ通知サービスへのチャネル接続を要求します。
  2. プッシュ通知サービスは、チャネル URI を使用して応答します。
  3. クライアント アプリケーションは、監視サービスに、プッシュ通知サービスのチャネル URI およびペイロードと共にメッセージを送信します。
  4. 監視サービスは、情報の変化 (フライトのキャンセル、フライトの遅延、またはサンプル アプリケーションでの天気に関する警告) を検知すると、プッシュ通知サービスへのメッセージを終了します。
  5. プッシュ通知サービスは、メッセージを Windows Phone 7 デバイスに中継します。
  6. メッセージ ハンドラーは、デバイス上でメッセージを処理します。

データをローカルにキャッシュする

アプリケーションでデータを利用できるようにするもう 1 つの方法は、常に UI にある程度のデータが存在するように、データをローカルにキャッシュするというものです。その後、(可能であれば) バックグラウンドで他の方法を使用してローカル データを更新することができます。この方法の良い面は、バックグラウンドで情報の更新が非同期に行われる必要があっても、アプリケーションがすぐに読み込まれ、使用できるようになることです。

簡単に言うと、分離ストレージは、最新のデータのセットを保存するために使用されます。アプリケーションは、起動するとすぐに、ローカル分離ストレージ内の利用可能なデータを取得し、レンダーします。その一方で、アプリケーションは、更新された情報を確認するために Windows Azure サービスを呼び出します。新しい情報が見つかった場合、その情報はシリアル化され、デバイスに転送されます。そして、分離ストレージが更新されるので、更新された情報を使用して再び UI をレンダーすることになります。ユーザー エクスペリエンスをより良好にするためには、情報更新日時を UI 内で示すとよいでしょう。

なお、アプリケーションがモデル - ビュー - ビューモデル (MVVM: Model-View-ViewModel) 設計パターンを使用している場合、UI の更新は Silverlight のデータバインド機能を通じて自動的に行われることがあります。MVVM と Silverlight の詳細については、Robert McCarter の記事「モデル - ビュー - ビューモデル (MVVM: Model-View-ViewModel) の問題点とその解決策」(msdn.microsoft.com/magazine/ff798279) を参照してください。

管理下にあるサーバーにデータをキャッシュする

データが利用可能になったときにアプリケーションにデータを直接プッシュするという方法とデータをデバイスに格納するという方法の中間に位置する方法があります。サードパーティ サービスからデータを取得し、Windows Phone 7 アプリケーションから要求されるまでそのデータをクラウド アプリケーション内にキャッシュするというものです。

この手法を使用するには、アプリケーションに新しい抽象化の層が必要となります。要するに、ここでの目標は、サードパーティ サービスの依存関係をアプリケーションから取り除くことです。サービスでは、あらゆるサードパーティ サービスの依存関係のデータを取得し、キャッシュします。これにより、サードパーティ サービスが停止しても、少なくとも、デバイス上のアプリケーションに提供できるデータがある程度キャッシュ内に存在することになります。

このようなサービスは、簡単に複製したり、さまざまなサービスからデータを取得するように拡張したりすることができるので、1 つのベンダーやデータ ソースへの依存度が軽減され、ベンダーの変更が大幅に簡単になります。

Windows Azure でデータ中心のソリューションを構築する方法の詳細については、Kevin Hoffman および Nathan Dudek による記事「Windows Azure ストレージによるアプリケーション エンジンの強化」(msdn.microsoft.com/magazine/ee335721) を参照してください。また、Windows Phone 7 シナリオに直接、的を絞ったものではありませんが、Paul Stubb の記事「SharePoint 2010 向け Silverlight 4 Web パーツの作成」(msdn.microsoft.com/magazine/ff956224) は、Silverlight および Web サービスのためのデータバインドされた設計に関する優れた記事です。

監視サービス

前述のとおり、通知機能はサンプルのフライト状況アプリケーションの重要な一部です。この機能は実際のところ、アプリケーション内のいくつかの異なるサービスで構成されています。おそらくこのアプリケーションの実用性にとって最も重要なことですが、監視サービスは定期的にサードパーティのデータ サービスのポーリングを行い、フライトの遅延、空港での遅延、天気に関する警告などの情報をデバイスに中継します。

サンプル アプリケーションでは、監視サービスはフライトおよび空港コードの最新のリストを読み取り、この情報を使用して関連するデータを収集します。その後、この情報は、前述の /Flight/CheckStatus サービスで取得できるように SQL Azure データベースにキャッシュ エントリとして格納されます。サンプル アプリケーションの監視サービスは、Windows Azure Worker ロールを使用して実装されています。この Worker ロールにおける主な目標は、すべてのユーザーのフライト コレクションについて、フライトの遅延と空港の状況に関する状況情報を取得することです。更新情報の取得頻度は、予定されたフライト出発時刻が近づくにつれて高くなります。

このようなサービスの実装方法に関するアイデアについては、CodePlex の Azure パブリッシュ - サブスクライブ プロジェクト (azurepubsub.codeplex.com、英語) や、Joseph Fultz のブログ記事「Migrating Windows Service to Azure Worker Role: Image Conversion Example Using Storage」(Windows サービスを Azure Worker ロールに移行する: ストレージを使用した画像変換の例、bit.ly/aKY8iv、英語) を参照してください。

まとめ

この記事で、データ駆動型の Windows Phone 7 アプリケーションを設計する際に考慮する必要がある問題の概要をお伝えできていれば、さいわいです。UI の反応性、およびデータ ソースへのタイムリーなアクセスは、アプリケーションのユーザー エクスペリエンスが優れたものになるかどうかにかかわってきます。

もう少し掘り下げて考えるには、まずは Joshua Partlow の記事「Windows Phone 開発ツールの概要」(msdn.microsoft.com/magazine/gg232764) を参照してください。Jim Nakashima、Hani Atassi、および Danny Thorpe による記事「Visual Studio 2010 での Windows Azure アプリケーションの開発と配置」(msdn.microsoft.com/magazine/ee336122) も参照することをお勧めします。

Windows Azure と Windows Phone 7 開発を結び付けるには、Ramon Arjona の記事「Windows Phone とクラウドの紹介」(msdn.microsoft.com/magazine/ff872395) を参照してください。

Danilo Diaz は、マイクロソフトの米国中部大西洋岸地区のデベロッパー エバンジェリストです。開発者がマイクロソフトの製品と戦略を理解するための手助けをするのが彼の役割です。

Max Zilberman は、ニューヨーク市および米国中部大西洋岸地区のアーキテクト エバンジェリストです。マイクロソフトに入社する前は、一流の健康保険会社でさまざまなシニア テクニカルの地位に就いていました。

この記事のレビューに協力してくれた技術スタッフの Ramon Arjona に心より感謝いたします。