次の方法で共有


Windows ランタイム

Windows ストア アプリと Windows Phone ストア アプリ間のローミング データ

Tony Champion

10 年前のデスクトップ アプリと比べると、最近のアプリで使用されている標準はまったく異なり、アプリに期待することも違っています。こうしたアプリへの期待の 1 つは、複数のデバイスで同じアプリを実行して、データを共有することです。ユーザーがデスクトップにもノート PC にも同じアプリをインストールしていれば、どちらでも同じ構成が保持され、同じデータで作業できることを期待します。複数のデバイスで同じアプリを利用できるようになれば、さらにデバイス間でデータを共有できることも期待します。

データ駆動型のアプリの世界では、主にバックエンド データベースを利用してこのような期待に応えています。複数のデバイス上のアプリは、同じリモート データベースにクエリして、同じデータにアクセスできます。ただし、リモート データベースを用意してアプリをサポートすると、アーキテクチャ、開発、保守の面で膨大なオーバーヘッドが加わります。単純にリモート データベースを必要としないアプリもあります。データベースが必要だとしても、複数の目的に合わせて設計されたデータベースでアプリ固有の情報をサポートすることは望みません。

Windows ストア アプリおよび Windows Phone ストア アプリでは、ローミング データという考え方を用いてこのニーズに応えます。各アプリは、ユーザー別に割り当てられた少量のクラウド ストレージを自動的に受け取ります。ユーザーがアプリに関する情報をこのクラウド ストレージに保存すると、同じアプリの複数のインストールでこの情報を共有できるようになります。Windows Phone ストア アプリと Windows ストア アプリではさらに進んで、異なるデバイスで実行されているアプリ間でもデータを共有できるようになります。ここでは、開発するアプリでローミング データを使用する場面と、デバイス間で効果的に使用する方法を見ていきます。

今回は、ユニバーサル アプリを使用してデバイス間でローミング データを共有するデモを行います、ただし、この手法は Windows Phone ストア アプリでも Windows ストア アプリでも個別に使用できます。ローミング データは、HTML ベースのアプリや XAML ベースのアプリでも同様に機能します。

ローミング データの使用

アプリでローミング データを使用するメリットの 1 つは、構成や他の設定を必要としないで自動的に利用可能になることです。入手可能なローミング データ API を使用するだけで、ローミング データを利用できます。

ローミングする情報: 多くの開発者が最初に考えるのは「ローミングの設定に適したデータの種類は何か」という疑問です。ローミングできるデータのサイズには厳しい制限があります。したがって、綿密な計画が必要です。

ローミングする最も一般的な情報は、ユーザー定義の基本設定や設定 (色、フォント、オプションなど) です。このような設定を Windows ストア アプリと Windows Phone ストア アプリで共有すると別の意味を帯びる設定もありますが、異なるプラットフォームに同じエクスペリエンスを提供できればアプリの使い勝手は大幅に向上します。

開発中のアプリ内での現在のナビゲーションを強力な機能に変える余地があります。ユーザーが Windows Phone でレポートを操作し、その後職場のコンピューターにログインして Windows ストア バージョンのアプリで Windows Phone で操作したレポートに直接移動できるようにしてみてはどうでしょう。ユーザーがデスクトップでビデオを見ている場合に、Windows Phone バージョンでも同じビデオに移動してみてはどうでしょう。

一時データもローミングの候補の 1つです。ユーザーがノート PC で電子メールを途中まで書き、デスクトップに移動しても同じ下書きから作業を続けられるようにすればどうでしょう。

通常、大きなデータセットはローミング データの候補になりません。ただし、ローミング データのデータ セットにキーを設定して共有すれば、そのキーに基づいて大きなデータセットを移動先のクライアントに読み込むことができます。

考えればきりがありませんが、目標は同じです。ローミング データによって、ユーザーはアプリといつもつながっていると感じられるようになります。

ローミング データの有効化: デバイス間でデータを正しく同期するため、アプリにはいくつかの前提条件があります。まず、ユーザーは Microsoft アカウントを使用してデバイスにログオンする必要があります。ローミングの設定は、アプリと Microsoft ユーザー アカウントに関連付けられます。ユーザーが Microsoft アカウントを使用していなければ、そのデータを正しく利用できません。

次に、ユーザーはデバイスのローミング データの機能を無効にしていないことが重要です。ユーザーが手動でローミング データの機能を無効にできる場合や、システム管理者によってデバイスのポリシーが適用されている場合があります。ローミング データの機能が無効になっていると、データは同期されません。

ローミング データの機能がデバイスで無効になっている場合でも、ローカルであればデータを利用できます。そのため、アプリでは、ローミング データを同期し、異なるワークフローを使用しているかどうかをチェックすることを考える必要はありません。

API について

ローミング データ用の API は、Windows.Storage.ApplicationData オブジェクトにあります。各アプリは、ApplicationData のインスタンスを 1 つ保持します。このインスタンスは、次のように静的プロパティ Current を使用して参照できます。

var appData = Windows.Storage.ApplicationData.Current;

API には、ローミング データを強制的に同期するメカニズムはありません。同期のプロセスは、デバイス自体が管理することになっています。

開発者は 2 種類のローミング データを使用します。1 つは、ApplicationData の RoamingSettings プロパティです。このプロパティは、キーと値のペアを管理する ApplicationDataContainer です。設定は RoamingSettings.Values プロパティで管理され、文字列をインデックスとする配列としてアクセスできます。キーは最大 255 文字の英数字文字列にすることができます。値にはオブジェクトを指定できます。ただし、そのオブジェクトはサポート対象の Windows ランタイム データ型でなければなりません。つまり、カスタマイズしたオブジェクトをローミングの設定に保存することはできません。

ローミングの設定には、Values プロパティのインデックスを指定してアクセスします。設定を追加または更新するには、キーをインデックスにして Values プロパティを新しい値に変更します。設定を削除するには、Values.Remove メソッドを使用します。以下のコードは、ローミング設定の作成、読み取り、および削除を行う例を示しています。

var roamingSettings = ApplicationData.Current.RoamingSettings;
// Create setting
roamingSettings.Values["setting1"] = "My First Setting";
// Read setting
var settings1 = roamingSettings.Values["setting1"];
// Remove setting
roamingSettings.Values.Remove("setting1");
// Clear all settings
roamingSettings.Values.Clear();

単純な Windows ランタイム データ型で値を格納するだけで十分な場合もありますが、多くの場合は、オブジェクト全体を格納する方が適しています。ローミング データにクラスを保存する方法はいくつかありますが、ApplicationDataCompositeValue を使用すれば、複雑なオブジェクトを RoamingSettings に保存することができます。

ApplicationDataCompositeValue は、一緒に保存されるキーと値のペアのコレクションです。これは、複数の項目をグループにして 1 つの単位として同期した状態に保つ優れた方法です。以下のコードは、ApplicationDataCompositeValue を作成して RoamingSettings に追加する例を示しています。

var compositeValue = new ApplicationDataCompositeValue();
compositeValue["firstName"] = "Tony";
compositeValue["lastName"] = "Champion";
compositeValue["age"] = 38;
roamingSettings.Values["personalInfo"] = compositeValue;

このアプローチの 1 つの問題点は、複雑なオブジェクトを ApplicationDataCompositeValue に格納および取得するメカニズムがないことです。これを解決する 1 つのアプローチとして、クラスにヘルパー関数を作成し、複雑な構造を自動的に変換します。図 1 は、ToCompositeSetting と FromCompositeSetting という 2 つの静的メソッドを備えた Person クラスを示しています。この 2 つのメソッドは、先ほどの例で保存したデータを Person オブジェクトに変換します。つまり、かなり単純なデータ バインドのようなことを行います。

図 1 ApplicationDataCompositeValue から Person クラスへの変換

public class Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public int Age { get; set; }
  public static Person 
    FromComposite(ApplicationDataCompositeValue composite)
  {
    return new Person()
    {
      FirstName = (string)composite["firstName"],
      LastName = (string)composite["lastName"],
      Age = (int)composite["age"]
    };
  }
  public static ApplicationDataCompositeValue ToComposite(Person person)
  {
    var composite = new ApplicationDataCompositeValue();
    composite["firstName"] = person.FirstName;
    composite["lastName"] = person.LastName;
    composite["age"] = person.Age;
    return composite;
  }
}

図 2 は、新しい Person クラスを使用して RoamingSettings の個人情報を取得および保存します。

図 2 RoamingSettings の個人情報を取得と保存

var person = new Person()
{
  FirstName = "Tony",
  LastName = "Champion",
  Age = 38
};
roamingSettings.Values["personalInfo"] = Person.ToComposite(person);
if (roamingSettings.Values.ContainsKey("personalInfo"))
{
  var composite =
    (ApplicationDataCompositeValue)roamingSettings.Values["personalInfo"];
  var roamingPerson = Person.FromComposite(composite);
}

ローミングの設定には、即座に同期する必要があるデータに使用できる特殊なキーがあります。いずれかの設定に HighPriority を追加すると、その設定は可能な限り早期に同期されます。電子書籍の現在のページ番号、一時停止したビデオ フレームなど、デバイスどうしを繋ぐエクスペリエンスを提供する場合に便利です。たとえば、ユーザーが Windows ストア アプリで映画を見ているのであれば、次のように映画の現在の再生位置がわかるデータを Windows Phone ストア アプリに提供してもよいでしょう。

var roamingSettings = ApplicationData.Current.RoamingSettings;
var composite = new ApplicationDataCompositeValue();
composite["movieId"] = myVidPlayer.MovieId;
composite["position"] = myVidPlayer.CurrentTime;
roamingSettings.Values["HighPriority"] = composite;

もう 1 つのローミング データの種類はファイルです。ApplicationData オブジェクトには RoamingFolder プロパティがあります。このプロパティは、アプリからファイルへの同期読み取りや同期書き込みを可能にする StorageFolder インスタンスを返します。

事実上すべての種類のファイルを RoamingFolder に追加できます。ただし、ファイル名には従うべき仕様があります。まず、ファイル名と拡張子を併せた最大文字数は 256 文字です。ファイル名の先頭にはスペースを含めることができません。また、あるグループの Unicode 文字が許可されません。

.zip や .cab など、フォルダーのように動作する種類のファイルは許可されません。要件を満たさないファイルを RoamingFolder に追加すると、同期はされませんが、ローカルにアクセスすることはできます。

RoamingFolder の用途の 1 つは、複雑なオブジェクトを保存することです。先ほどと同じ Person オブジェクトを受け取ってシリアル化してから、RoamingFolder のファイルに書き込む例を以下に示します。

var roamingFolder = ApplicationData.Current.RoamingFolder;
// Create file and open a stream to write to
StorageFile personFile = await roamingFolder.CreateFileAsync(
  "personInfo.txt", CreationCollisionOption.ReplaceExisting);
var writeStream = await personFile.OpenStreamForWriteAsync();
// JSON serialize object
var serializer = new DataContractJsonSerializer(typeof(Person));
serializer.WriteObject(writeStream, person);
// Flush the stream
await writeStream.FlushAsync();

サイズを考えて、XML に対して JSON シリアル化を使用しています。ローミング データにはサイズの制約があるため、すべてのバイトを計算に入れます。

反対のロジックを使用して RoamingFolder からオブジェクトを取得します。以下のコードは、同じファイルを読み取って Person オブジェクトを返すデモを示しています。

// Create file and open a stream to write to
var readStream = await 
  roamingFolder.OpenStreamForReadAsync("personInfo.txt");
// JSON deserialize object
var serializer = new DataContractJsonSerializer(typeof(Person));
var roamingPerson = (Person)serializer.ReadObject(readStream);

これでデバイス間で同期したローミング データの読み取りと書き込みができるようになりますが、ローミング データが更新されるタイミングを把握するにはどうすればよいでしょう。これは、ApplicationData の DataChanged イベントで処理します。デバイスで新しいローミング データを受け取るときは必ず DataChanged イベントが発生し、更新された ApplicationData オブジェクトが渡されます。これにより、データが変更されたときにアプリを調整できるようになります。デバイスからデータがプッシュされるタイミングを把握できるイベントはありません。以下のコードは、DataChanged イベントをリッスンする方法の例を示しています。

private void HandleEvents()
{
  ApplicationData.Current.DataChanged += Current_DataChanged;
}
void Current_DataChanged(ApplicationData sender, object args)
{
  // Update app with new settings
}

アプリで DataChanged イベントを利用している場合、ApplicationData の SignalDataChanged メソッドが役に立ちます。ローミング データをローカルで更新するときは必ずこのメソッドを呼び出して、DataChanged イベントを発生させ、必要な更新ハンドラーが実行されるようにします。

// Update settings locally and raise DataChanged event
roamingSettings.Values["favoriteColor"] = "Black";
ApplicationData.Current.SignalDataChanged();

ローミングを試みるデータ量を絶えず管理することが重要です。各デバイスは、デバイス間で同期できる最大データ量が決まっていて、現状では 100 KB です。問題になりそうなのは、これが融通の利かない制限になっている点です。同期を試みるデータの総量が制限を超えると、デバイス間でまったく同期されなくなります。ApplicationData クラスには、同期できるデータの総量を KB 単位で返す RoamingStorageQuota プロパティがあります。ただし、ApplicationData には、現在使用中のデータ量を判断するメカニズムがないので、現状ではデータ量を管理するのは開発者の役割です。

アプリのバージョンが新しくなると、設定が新しくなったり、以前のバージョンとは設定が変わったり、以前のバージョンのデータが必要なくなることもあります。この問題に対処するため、Windows ランタイムはローミング データのバージョンを開発者が管理できるようにします。現在のローミング データのバージョンは、アプリが ApplicationData.Current.Version に設定します。このバージョン番号は、アプリのバージョン番号とは無関係で、既定値はゼロに設定されています。アプリは、このバージョン番号に一致するデータを同期します。これにより、以前のバージョンに影響を与えることなく、新しいデータの構造を作成できるようになります。

ApplicationData.SetVersionAsync メソッドを使用してバージョンを変更できます。このメソッドは、新しいバージョン番号と ApplicationDataSetVersionHandler という 2 つのパラメーターがあり、新しいバージョンに基づいてアプリに変更を加えるために必要なコードを記述できるようにしています。

ハンドラーには SetVersionRequest パラメーターが 1 つあります。ハンドラーでは、CurrentVersion プロパティを使って現在のバージョンを確認し、DesiredVersion プロパティで新しいバージョンを確認します。アプリは、この 2 つの値を使用して、反復アプローチで移行を処理します。また、GetDeferralMethod もあり、移行が完了するまでスレッドを開いたままにできるようにします。ファイルの読み取りや書き込みなどに非同期の呼び出しを行う場合は、バージョン変更のプロセスを完了する前に、これらの関数を実行できます。図 3 は新しいバージョンに移行する方法を示しています。

図 3 ローミング データのバージョンの更新

void SetVersionHandler(SetVersionRequest request)
{
  SetVersionDeferral deferral = request.GetDeferral();
  if (request.CurrentVersion < 1)
  {
    // Handle upgrade from 0 to 1
  }
  if (request.CurrentVersion < 2)
  {
    // Handle upgrade from 1 to 2
  }
  deferral.Complete();
}
async void SetVersion()
{
  var appData = ApplicationData.Current;
  if (appData.Version < 2)
  {
    await appData.SetVersionAsync(
      2, new ApplicationDataSetVersionHandler(SetVersionHandler));
  }
}

Windows ストア アプリと Windows Phone ストア アプリの間の共有

これで Windows ストア アプリと Windows Phone ストア アプリの両方にローミングの設定が実装されるので、次の論理的な手順では、対になる 2 つのアプリでローミング データを共有できるようにします。

技術的には、Windows Phone ストア アプリと Windows ストア アプリ間でローミング データを共有できるようにするためには、同じパッケージ ファミリ名 (PFN) を割り当てなければなりません。ローミング データのフィールドは、パッケージ名に基づいて生成されます。そのため、2 つのアプリは同じ PFN を保持している必要があります。それぞれのストアでアプリと名前を関連付けることで、PFN が決まります。

どちらのストアのアプリでも、アプリを提出する最初の手順でアプリに名前を割り当てます。新しい名前を付けるか、アプリを既存の名前に関連付けます。既存の名前の一覧には、提出するストアで予約されている名前と、別のストアにあるアプリの一覧が両方表示されます。別のストアのアプリを選択すると、アプリがリンクされ、同じ名前が設定されて、アプリ間でローミング データを共有できるようになります。Windows ストア アプリと Windows Phone ストア アプリのリンクの意味については、bit.ly/10Pl2Xi を参照してください。

両ストアはまだ統合されていないため、アプリのリンクを行う操作がやや異なります。Windows ストア アプリを既に所持していれば、[アプリの情報] の Windows ストア アプリを選択することで、Windows Phone ストア アプリをリンクできます (図 4 参照)。また、[アプリの名前] で既存の Windows Phone ストア アプリと Windows ストア アプリをリンクすることもできます (図 5 参照)。

Windows Phone ストア アプリから Windows ストア アプリへのリンク
図 4 Windows Phone ストア アプリから Windows ストア アプリへのリンク

Windows ストア アプリから Windows Phone ストア アプリへのリンク
図 5 Windows ストア アプリから Windows Phone ストア アプリへのリンク

ストアでアプリを作成してリンクしたら、最後の手順として、Visual Studio で各アプリをこの名前に関連付けます。プライマリの Windows ストアおよび Windows Phone ストアのソリューションを右クリックし、[ストア]、[アプリケーションをストアと関連付ける] を順にクリックします。ウィザードの指示に従い、適切な名前を選択します。これにより、ストアに入力されている情報で Package.appxmanifest が更新されます。

これで、ローミング データをアプリ間で共有できるようになります。繰り返しになりますが、ローミングするデータのサイズを管理することが重要です。ストレージのクォータがプラットフォーム間で異なる場合、2 つのクォータのうち小さい方のクォータを制限として利用する必要があります。

ローミング データのデバッグ

ローミング設定のテストは簡単です。ローミング設定は融通の利かないソリューションです。ローミング データはデバイス間で同期されるか、同期されないかのどちらかしかありません。同期プロセスをテストする場合、デバイスをロックして、アプリがデータの同期を試みるようにします。同期が行われない場合は、以下の点を考えます。

  • データが同期されない最も一般的な原因は、アプリがローミング ストレージのクォータを超過していることです。合計サイズの制限を超過していると、アプリはローミング データの同期を試みません。
  • ファイルにデータを保存している場合は、すべてのファイル ハンドラーを閉じていることを確認します。ファイルが開いたままだと、そのファイルにロックがかかり同期されません。

まとめ

アプリでローミング データを使用すると、ユーザーに一貫したエクスペリエンスを提供し、アプリといつもつながっていると感じることができます。設定、基本設定、現在のアプリ状態を共有してのデバイス間を移行させることで、ユーザーはどのデバイスでも同じアプリを使っていると感じることができます。Windows ストア アプリと Windows Phone ストア アプリ間でデータを共有する機能を追加することで、このエクスペリエンスが強化され、さまざまなチャンスが広がります。


Tony Champion は、Champion DS の代表取締役であり、Microsoft MVP を取得しており、講演、ブログ、および著書で積極的にコミュニティに貢献しています。彼のブログは tonychampion.net (英語) です。彼の連絡先は tony@tonychampion.net (英語のみ) です。

この記事のレビューに協力してくれたマイクロソフト技術スタッフの Robert Green に心より感謝いたします。