この記事は機械翻訳されたものです。

Windows Phone

Windows Phone のための廃棄と同期の真髄 (機械翻訳)

Ben Day

コード サンプルのダウンロード

アプリケーションを今まで書かれた、ほぼ終了するいるとすぐに、あなたはそれでしたが書かれていなかった希望か。 それは何かは、アーキテクチャにかなり右ではないその勘です。 単純なする必要があります変更不可能に感じるまたは少なくとも彼らが必要以上に長くかかります。 して、バグがあります。 ああ、バグです ! あなたはまともなプログラマーをしています。 とても多くのバグが何かを書く管理方法だろうか?

聞き覚えのある表現だと感じる場合は、 私の最初の Windows Phone アプリケーションを書いたとき、それ私に NPR のリスナー起こった。 NPR のリスナー協議ナショナル パブリック ラジオ Web サービス (npr.org/api/index.php) そのプログラム、しすることができます、使用可能なストーリーの一覧を取得するユーザー、Windows Phone デバイス上のそれらの物語を聞きます。 ときに最初にそれを書いた、Silverlight 開発の多くをやっていた、私も方法に幸せだった私の知識とスキル Windows Phone に移植。 かなり迅速に行わ最初のバージョンを得たし、それが市場の認証プロセスに提出します。 私は「簡単だったまあ、」考えていた全体の時間して私は認証に失敗します。 失敗した場合はここにあります。

手順 1: アプリケーションを実行します。

手順 2: あなたの携帯電話のメイン ページに移動するには [スタート] ボタンを押します。

手順 3: あなたのアプリケーションに戻るには [戻る] ボタンを押します。

[戻る] ボタンを押すと、アプリケーションはエラーなしを再開する必要があり、理想的には、ユーザー右画面でここで彼はあなたのアプリケーションを終了した戻す必要があります。 私の場合、("すべてのものと見なされる」など)、テスター ナビゲート ナショナル パブリック ラジオのプログラムに、現在のストーリーのいずれかにクリックし、ホーム画面にデバイスに移動するには [スタート] ボタンを押します。 テスター私のアプリケーションに戻るには [戻る] ボタンを押したときに、アプリケーションはバックアップして来た、それが NullReferenceExceptions の祭りだった。 これは問題です。

今、私はあなた私は私の XAML ベースのアプリケーションの設計方法について少し知ってもらいましょう。 私は、モデル-ビュー-ビューモデル パターンのすべてについては、XAML ページとアプリケーションのロジックのほぼ狂信的な分離を目指します。 あるかどうかは、codebehinds 内のコードになるだろう (※. xaml.cs) 私のページが良く、非常に良い理由。 多くのこれはユニット テストの容易性の病理学的に近くの必要は私によって駆動されます。 アプリケーションが動作しているとき知っているヘルプと、もっと重要なことは、彼ら簡単に、コードをリファクタリングし、アプリケーションの動作を変更するための単体テストが不可欠です。

もし私があんなに単体テストについて、だから、なぜ私はすべてのそれらの NullReferenceExceptions を得るか? この問題は、Silverlight アプリケーションのような私の Windows Phone アプリケーションを書いたことです。 確かに、Windows Phone は、Silverlight が Windows Phone アプリケーションや Silverlight アプリケーションのライフ サイクルは完全に異なって。 Silverlight では、ユーザーがアプリケーションを開くと、彼女が行われるまでに app を閉めるとやり取りします。 Windows Phone で対照的に、ユーザー アプリケーションを開きます、動作それにし、前後にバウンス — OS またはその他のアプリケーションに — いつでも彼女が望んでいます。 彼女はあなたのアプリケーションから離れて移動すると、アプリケーションは非アクティブ化、または「トゥームス トーン」です。あなたのアプリケーションが廃棄されたされているときは、そのナビゲーション「戻るスタック」は、もはや実行しているが、です — 彼らは訪れていた順序でアプリケーションのページに — デバイスでまだ利用可能です。

あなた Windows Phone デバイスにアプリケーションの番号を介して移動して繰り返しそれらのアプリケーションを逆の順序で戻るに戻るボタンを押してできること気づいているかもしれません。 それはアクションでは、ナビゲーション バック スタックとを別のアプリケーションに行くたびにそのアプリケーションは永続化された廃棄データから再アクティブ化されました。 それが OS から通知アプリケーションは廃棄状態になると、取得非アクティブ化して後再アクティブ化することができますので、アプリケーションの状態を保存する必要があります。 図 1 のアクティブ化とアプリケーション App.xaml.cs 内の非アクティブ化のためいくつかの簡単なコードを示しています。

図 1 単純な墓石実装 App.xaml.cs 内

// Code to execute when the application is deactivated (sent to background).
// This code will not execute when the application is closing.
private void Application_Deactivated(object sender, DeactivatedEventArgs e)
{
  // Tombstone your application.
IDictionary<string, object> stateCollection =
    PhoneApplicationService.Current.State;
  stateCollection.Add("VALUE_1", "the value");
}
// Code to execute when the application is activated
// (brought to foreground).
// This code will not execute when the application is first launched.
private void Application_Activated(object sender, ActivatedEventArgs e)
{
  // Un-tombstone your application.
IDictionary<string, object> stateCollection =
    PhoneApplicationService.Current.State;
  var value = stateCollection["VALUE_1"];
}

私の通常の問題は、計画の完全な欠乏によって引き起こされた — とコーディング — これらのトゥームス トーンのイベントを処理します。 それに加え私包括的なリッチで複雑なビューモデルの実装は、災害のレシピでした。 ユーザーは、アプリケーションを再入力するには [戻る] ボタンをクリックするときの動作について考えます。 そのユーザーいくつかのスタート ページでは、代わりにあなたのアプリケーションで彼を訪問した最後のページに着陸する終了しません。 Windows Phone テスターの場合ユーザー私のアプリケーションを再アクティブ化すると、彼女は途中でアプリケーションを入力と UI、ビューモデルは、移入され、その画面をサポートできませんでした。 ビューモデル廃棄イベントへの応答ではなかったので、ほとんどすべてのオブジェクト参照が null です。 おっと。 私は私でしたその場合、単体テストをしませんでしたか? (Kaboom !)

ここのレッスンは、前方と後方の UI と移動するためにビューモデルを計画する必要があります。

事実の後のトゥームス トーンの追加

図 2 私の元のアプリケーションの構造を示します。 私のアプリケーションが認定を通過するために、私はその開始/戻るボタンのケースを処理する必要。 私は Windows Phone プロジェクト (Benday.Npr.Phone) のいずれかを実装するトゥームス トーン可能性がまたは私のビューモデル (Benday.Npr.Presentation) に無理できます。 両方は、いくつかの不快な建築の妥協を関与。 Benday.Npr.Phone プロジェクトにロジックを追加する場合は、私の UI はあまり私のビューモデルのしくみについて知っているでしょう。 ビューモデル プロジェクトにロジックを追加する場合は、Benday.Npr.Presentation から、Microsoft.Phone.Shell 名前空間で廃棄 (tombstone) 値のディクショナリ (PhoneApplicationService.Current.State) へのアクセスを取得する Microsoft.Phone.dll への参照を追加する必要があると思います。 それは私のビューモデル プロジェクトで不要な実装の詳細を汚染、関心の分離 (SoC) の原則の違反となります。

The Structure of the Application
図 2 アプリケーションの構造

私の最終的な選択は電話プロジェクトにロジックを置くが、また私のビューモデルに廃棄 (tombstone) 値のディクショナリを置くことができる XML 文字列にシリアル化する方法を知っているいくつかのクラスを作成することでした。 このアプローチは私まだ私単一責任の原則を光栄クリーンなコードを与えている間プレゼンテーション プロジェクトから Microsoft.Phone.Shell への参照を避けるために許可。 これらのクラスに命名 ※ ViewModelSerializer。 図 3 StoryListViewModel のインスタンスを XML に有効にするのに必要なコードの一部を示します。

図 3 のコードで XML に IStoryListViewModel を有効にする StoryListViewModelSerializer.cs

private void Serialize(IStoryListViewModel fromValue)
{
  var document = XmlUtility.StringToXDocument("<stories />");
  WriteToDocument(document, fromValue);
  // Write the XML to the tombstone dictionary.
SetStateValue(SERIALIZATION_KEY_STORY_LIST, document.ToString());
}
private void WriteToDocument(System.Xml.Linq.XDocument document,
  IStoryListViewModel storyList)
{
  var root = document.Root;
  root.SetElementValue("Id", storyList.Id);
  root.SetElementValue("Title", storyList.Title);
  root.SetElementValue("UrlToHtml", storyList.UrlToHtml);
  var storySerializer = new StoryViewModelSerializer();
  foreach (var fromValue in storyList.Stories)
  {
    root.Add(storySerializer.SerializeToElement(fromValue));
  }
}

書かれたこれらのシリアライザーを持っていた一度は、現在表示されている画面に基づいてこのシリアル化トリガー App.xaml.cs にロジックを追加ため (を参照してください図 4)。

図 4 ビューモデル シリアライザー App.xaml.cs のトリガー

private void Application_Deactivated(object sender, 
  DeactivatedEventArgs e)
{
  ViewModelSerializerBase.ClearState();
  if (IsDisplayingStory() == true)
  {
    new StoryListViewModelSerializer().Serialize();
    new StoryViewModelSerializer().Serialize();
    ViewModelSerializerBase.SetResumeActionToStory();
  }
  else if (IsDisplayingProgram() == true)
  {
    new StoryListViewModelSerializer().Serialize();
    new ProgramViewModelSerializer().Serialize();
    ViewModelSerializerBase.SetResumeActionToProgram();
  }
  else if (IsDisplayingHourlyNews() == true)
  {
    new StoryListViewModelSerializer().Serialize();
    ViewModelSerializerBase.SetResumeActionToHourlyNews();
  }               
}

私は最終的にそれが仕事だし、認定アプリケーションを得たが、残念ながら、コード、ゆっくり醜い、脆性とバギーでした。 私がやったがあります最後に 1 つの巨大な廃棄イベントを行うことではなく、それ以下の保存、それ自体としてそれに続くだろうので、それを構築するために必要な状態走ったいたので、私のビューモデルを設計しました。 どのようにすればよいですか?

非同期プログラミングのコントロール フリーク学校

だから、ここはあなたのための質問です。「コントロールフ リーク」傾向はありますか? トラブルを解雇をしますか。 明白な真理を無視し、意志の力によって明確に日、直接目で見つめている現実を無視する方法の問題を解決するには、選択を行うか? うん. それは NPR のリスナーの最初のバージョンでの非同期呼び出しの方法です。 具体的には、それはどのように私は、アプリケーションの最初のバージョンでの非同期ネットワー キング接近です。

Silverlight では、ネットワークのすべての呼び出しを非同期にする必要があります。 コードは、ネットワーク呼び出しを開始し、すぐに返します。 結果 (または例外) は、いつか後で非同期コールバック経由で配信されます。 これは、ロジックは常にネットワークに 2 つの部分で構成されて ことを意味します — 発信の呼び出しと戻り値の呼び出し。 この構造体の結果である、それはネットワーク呼び出しの結果に依存している任意のメソッドは、値を返すことはできませんし、void を返す必要がありますの Silverlight の汚い小さな秘密です。 これは副作用があります。ネットワーク呼び出しの結果に依存している別のメソッドを呼び出す任意のメソッドは void を返す必要があります。 伝統的なリポジトリは、サービス層とアダプターなどの n 層設計パターンの実装のメソッド呼び出しからの戻り値に大きく依存するので想像するかもしれないように、階層型アーキテクチャでは絶対に残忍なすることができます。

私のソリューションは全て <T> と呼ばれるクラスです。 (に示すように図5) は、ネットワーク呼び出し要求メソッドと、呼び出しの結果を処理し、有用な値を返すコードのための方法を提供します、メソッド間の接着剤として機能します。 図 6 いくつかのレポを示します­により Windows 通信基盤 (WCF) サービスを呼び出すし、ポップを返すパターン ロジック­u­IPerson の大雪のインスタンス。 そのコードを使用して、LoadById (全て <IPerson>, int) を呼び出し、最終的に IPerson の人口がインスタンスを受け取るとき client_LoadBy­IdCompleted (LoadByIdCompleted オブジェクト­EventArgs) 通知する方法の 1 つを呼び出します。 基本的に戻り値を使用できる場合、何をしたことのようなコードを作成できます。 (全ての <T> の詳細を参照してください bit.ly/Q6dqIv.)

ReturnResult
図 5 全て <T>

図 6 全て <T> を使用して ネットワーク呼び出しを開始および完了のイベントからの戻り値に

public void LoadById(ReturnResult<IPerson> callback, int id)
{
  // Create an instance of a WCF service proxy.
var client = new PersonService.PersonServiceClient();
  // Subscribe to the "completed" event for the service method.
client.LoadByIdCompleted +=
    new EventHandler<PersonService.LoadByIdCompletedEventArgs>(
      client_LoadByIdCompleted);
  // Call the service method.
client.LoadByIdAsync(id, callback);
}
void client_LoadByIdCompleted(object sender,
  PersonService.LoadByIdCompletedEventArgs e)
{
  var callback = e.UserState as ReturnResult<IPerson>;
  if (e.Error != null)
  {
    // Pass the WCF exception to the original caller.
callback.Notify(e.Error);
  }
  else
  {
    PersonService.PersonDto personReturnedByService = e.Result;
    var returnValue = new Person();
    var adapter = new PersonModelToServiceDtoAdapter();
    adapter.Adapt(personReturnedByService, returnValue);
    // Pass the populated model to the original caller.
callback.Notify(returnValue);   
  }           
}

NPR のリスナーの最初のバージョンを書き上げたときに、私はすぐにアプリケーションが遅い (または少なくとも遅くなること登場) だったこと考え出した私はキャッシュを使用していないので。 アプリ内で本当に必要な NPR の Web サービスを呼び出すプログラムで特定のストーリーの一覧を取得し、その画面を描画するために必要なたびに、サービスに戻ることを持っていなかったので、そのデータをキャッシュする方法だった。 非同期呼び出しが存在していないふりをしようとしていたため機能を追加する、しかし、かなり困難であった。 基本的には、コントロールフ リークされているし、私のアプリケーションの本質的に非同期の構造を否定しようと、私は私のオプションを制限しました。 プラットフォームの戦いされしたがって私のアプリケーション アーキテクチャを contorting します。

同期アプリケーションで物事の UI が起こるを開始し、スタック アンワインドとしてデータを返す、アプリケーションの層を介して制御の流れを渡します。 すべてはどこで作業を開始、データの処理、バック スタックに戻り値が返されます、単一呼び出しスタック内に発生します。 非同期のアプリケーションでは、プロセスはすべて緩く接続されている 4 つの呼び出しのような詳細です。UI は何かが起こる要求; いくつかの処理は、起こるかもしれない; 処理が起こるか、UI イベントにサブスクライブしている場合は、処理 UI アクションが完了したことを通知します。 UI 表示非同期アクションからのデータを更新します。

既に自分自身についてどのようにハード我々 非同期前に、の日にいたいくつかの若い whippersnapper 講義画像できお待ちしております。 "私の日に私たち自身の非同期ネットワーク ロジックとコールバックを管理する. 残酷だったし、我々 はそれが好き ! 今私のローンを得る!"まあ、実際には、そのように好きではないです。 それが残忍だった。

別のレッスンはここにあります。プラットフォームの基盤となるアーキテクチャを戦う常に問題を引き起こします。

分離ストレージを使用してアプリケーションを書き直す

私は最初 Windows Phone 7 のためのアプリケーションを書いて、Windows Phone 7.1 ではのみマイナー アップ デートをしました。 オーディオ ストリームにその全体の使命であるアプリケーションでは、それは常にユーザーが他のアプリケーションを参照しながら音声を聞くことができませんでした失望していた。 Windows Phone 7.5 が出てきたとき、新しいバック グラウンドのストリーミング機能を活用したいと思います。 また、アプリケーションを高速化し、多くの不要な Web 何らかのローカル データ キャッシュを追加することによってサービスを呼び出すを排除たかった。 これらの機能の実装について考え始めたように、しかし、制限と私のトゥームス トーン、ビューモデル、および非同期の実装の脆性より明らかになった。 それは私の以前の過ちを修正し、アプリケーションを完全に書き換え時間だった。

アプリケーションの以前のバージョンで私のレッスンを学んだこと、私は私は「廃棄 (tombstone) の能力」のための設計を開始し、また完全にアプリケーションの非同期の性質を受け入れるつもりだったことを決めた。 ローカル データ キャッシュを追加したかったので、私は分離ストレージを使用してに探し始めた。 分離ストレージは、アプリケーションをすることができます読み取りおよび書き込みデータをデバイス上の場所です。 それはあらゆる通常の .NET アプリケーションでファイル システムの作業に似ています。

分離ストレージのキャッシュおよび簡易ネットワーク操作

分離ストレージの巨大な利点は、ネットワーク呼び出しとは異なり、これらの呼び出しを非同期にする必要がないことです。 つまり、戻り値に依存しているより従来のアーキテクチャを使用することができます。 私はこれを考え出したと、非同期の同期することができますからのものでなければならない動作を分離する方法について考えるを開始しました。 ネットワーク呼び出しは非同期でなければなりません。 分離ストレージの呼び出しを同期することができます。 だからすべての解析を行う前にどのような場合は常に、ネットワークの結果を書き込むを分離ストレージに呼び出します? これは私が同期的にデータを読み込むことができます、私は安価で簡単にローカル データ キャッシュを行う方法を与えます。 分離ストレージは、私が一度に 2 つの問題を解決するのに役立ちます。

どのように彼らが大まかに関連する手順の 1 つだけ大きな同期ステップ代わりのシリーズでだという事実を受け入れ私のネットワーク呼び出しを行うの再加工を開始しました。 たとえば、NPR プログラムで特定のストーリーの一覧を取得したい場合は、ここで私は何 (を参照してください図 7)。

  1. ビューモデルは、StoryRepository 上の StoryListRefreshed イベントにサブスクライブします。
  2. ビューモデルの話一覧現在のプログラムの更新を要求する StoryRepository を呼び出します。 この呼び出しはすぐに完了する、void を返します。
  3. StoryRepository NPR REST Web サービス プログラムの記事のリストを取得する、非同期のネットワーク呼び出しを発行します。
  4. いくつかの時点では、コールバック メソッドがトリガーされ、StoryRepository は、サービスからデータをアクセス今があります。 データは XML としてのサービスから戻ってくるし、これをビューモデルに返される得る人口オブジェクトに回すよりも、StoryRepository、すぐに XML 分離ストレージに書き込みます。
  5. StoryRepository は、StoryListRefreshed イベントがトリガーされます。
  6. ビューモデルは StoryListRefreshed イベントを受信し、物語の最新のリストを得るために GetStories を呼び出します。 GetStories 分離ストレージからキャッシュされたストーリー リスト XML を読み取り、ビューモデルはニーズ、人口がオブジェクトを返すオブジェクトに変換します。 分離ストレージからの読み取りは同期呼び出しであるためこのメソッドは人口がオブジェクトを返すことができます。

Sequence Diagram for Refreshing and Loading the Story List
図 7 シーケンス図をリフレッシュ ・ ストーリー リストの読み込み

ここでの重要なポイントは、RefreshStories メソッドのデータが返されないことです。 それはちょうど物語のキャッシュされたデータの更新を要求します。 GetStories メソッドは、現在キャッシュされている XML データを受け取り、それをひずみオブジェクトに変換します。 GetStories は、任意のサービスを呼び出すことがあるないので、それすぐに話一覧画面を設定し、アプリケーションが最初のバージョンよりもずっと速いようですので非常に高速です。 キャッシュされたデータがない場合、GetStories は単にひずみオブジェクトの空のリストを返します。 ここでは、IStoryRepository インターフェイスです。

public interface IStoryRepository
{
  event EventHandler<StoryListRefreshedEventArgs> StoryListRefreshed;
  IStoryList GetStories(string programId);
  void RefreshStories(string programId);
    ...
}

このインターフェイスの背後にある論理の非表示、追加のポイントはそれビューモデルできれいなコードになります分離ストレージとサービス ロジックからビューモデルの開発努力です。 この分離コードは、単体テストをより簡単より簡単に維持するためにします。

分離ストレージの連続トゥームス トーン

私のトゥームス トーンの実装、アプリケーションの最初のバージョンでは、ビューモデルを取り、PhoneApplicationService.Current.State 携帯電話の廃棄 (tombstone) 値ディクショナリに格納された XML に変換します。 XML のアイデアが好きだったけど、ビューモデルの永続性は、ビューモデル層のではなく、携帯電話のアプリの UI 層の責任だった好きではなかった。 UI 層がビューモデルの私の全体のセットを保持する墓石の Deactivate イベントまで待ったこともしなかった。 値のほんの一握りを実際に必要な永続化するには、アプリケーションが実行されている彼らは非常に徐々 として変更すると、ユーザーから画面へ移動します。 ユーザーがアプリを介して移動としてなぜ値分離ストレージに書くか? そのようにアプリは常に非アクティブ化する準備ができている、トゥームス トーンは大したことではないです。

また、アプリケーションの全体の状態を永続化するのではなく、なぜだけ現在選択されている値を各ページに保存? データは、それがデバイスに既に、する必要がありますので、アプリケーションのロジックを変更することがなくキャッシュからデータを簡単にリロードすることができますローカル キャッシュです。 これはバージョン 1 で数百人から多分 4 または 5 のバージョン 2 に保存する必要がある値の数を減らします。 それは心配するには、はるかに少ないデータであり、すべてがはるかに簡単です。

読み書き用または分離ストレージから永続化コードのロジックは、リポジトリ オブジェクトのシリーズでカプセル化されます。 ストーリー オブジェクトに関連するについては、対応する StoryRepository クラスがあります。 図 8 物語 Id を取り、XML ドキュメントにそれを回す、分離ストレージに保存するためのコードを示しています。

図 8 StoryRepository 現在のストーリーの Id を保存するロジック

public void SaveCurrentStoryId(string currentId)
{
  var doc = XmlUtility.StringToXDocument("<info />");
  if (currentId == null)
  {
    currentId = String.Empty;
  }
  else
  {
    currentId = currentId.Trim();
  }
  XmlUtility.SetChildElement(doc.Root, "CurrentStoryId", currentId);
  DataAccessUtility.SaveFile(DataAccessConstants.FilenameStoryInformation, doc);
}

永続ロジック リポジトリ オブジェクト内にラップ、ストレージを保持および検索ロジックは、任意のビューモデルのロジックから分離、ViewModel クラスから実装の詳細を非表示に。 図 9 物語の選択が変更されたときに現在のストーリーの Id を保存するためには、StoryListViewModel クラスの例を示します。

値を変更して図 9 StoryListViewModel 現在のストーリーの Id を保存します

void m_Stories_OnItemSelected(object sender, EventArgs e)
{
  HandleStorySelected();
}
private void HandleStorySelected()
{
  if (Stories.SelectedItem == null)
  {
    CurrentStoryId = null;
    StoryRepositoryInstance.SaveCurrentStoryId(null);
  }
  else
  {
    CurrentStoryId = Stories.SelectedItem.Id;
    StoryRepositoryInstance.SaveCurrentStoryId(CurrentStoryId);
  }
}

そしてここで、StoryListViewModel を再作成する必要があるときプロセスを反転 StoryListViewModel Load メソッド自体ディスクから。

public void Load()
{
  // Get the current story Id.
CurrentStoryId = StoryRepositoryInstance.GetCurrentStoryId();
  ...
var stories = StoryRepositoryInstance.GetStories(CurrentProgramId);
  Populate(stories);
}

事前に計画します。

この記事では、いくつかのアーキテクチャの決定と NPR のリスナー私の最初の Windows Phone アプリケーションを作った間違いを歩いてきた。 トゥームス トーンを計画し、受け入れることを覚えている — と戦うのではなく — Windows Phone アプリケーションで非同期。 両方のために、コードを見てしたい場合前に、と後の NPR のリスナーのバージョンからダウンロードすることができます archive.msdn.microsoft.com/mag201209WP7

Benjamin Dayのコンサルタントおよびマイクロソフトの開発ツールを使用して Visual Studio Team Foundation Server、スクラム、Windows Azure に重点をソフトウェア開発のベストプラクティスの専門のトレーナーです。 彼は、Microsoft Visual Studio ALM MVP、認定スクラム トレーナー経由です Scrum.org、および TechEd、DevTeach 視覚スタジオ ライブなどのカンファレンスで講演 !ソフトウェアを開発していない場合は、一日を実行して行くし、チーズ、ハム、シャンパンの彼の愛をバランスをとるためにカヤックに知られています。彼は経由で連絡することができます benday.com

この記事のレビュー、次技術専門家のおかげで。Jerri チウとデビッド ・ スター