気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
Windows Phoneで位置情報を利用するアプリケーションをマーケットに出品する場合、
満たさないといけない要件があります。
1つ1つ内容を確認しながら、現在開発中のアプリケーションの仕様を再検討していきたいと思います。

マーケットに出品するための要件は5つのカテゴリに分類され、それぞれのカテゴリの中には、
いろいろと細かい内容が記述されています。

Application Certification Requirements for Windows Phone

今回は、その中で位置情報に関連する内容を抜粋して、見ていきます。

まず、アプリケーションポリシーのカテゴリに記載されている内容からです。
アプリケーションポリシーでは、2.7の項に位置情報サービスを使用するアプリケーションについての
内容が記載されています。

2.7.1 Your application must determine location using the Microsoft Location Service API.
(位置情報は、「Microsoft Location Service API」を使用して決定する必要がある。)

位置情報を利用する際は、Microsoft Location Service APIを使用しなさいと言う内容ですね。
これについては、特に問題無いと思います。

2.7.2 The privacy policy of your application must inform users about how location data from the Location Service API is used and disclosed and the controls that users have over the use and sharing of location data. This can be hosted within or directly linked from the application.
(プライバシーポリシーの中で、位置情報がどのように利用/公開れるのかをユーザに知らせる必要がある。また、位置情報の使用と共有に関してユーザが可能な制御(操作)について、知らせる必要がある。お知らせの内容は、アプリケーション内のコンテンツでも、Webページでも良い。)

これについては、明示的な対応が必要ですね。プライバシーポリシーのページをユーザが
位置情報を利用するページに遷移する前に挟み込んで、上記の内容を表示する感じですかね。

2.7.3 Your application must provide in-application settings that allow the user to enable and disable your application's access to and use of location from the Location Service API.
(アプリケーション内の設定機能によって、ユーザが位置情報サービスの使用/未使用を切り替えることが出来るようにする必要がある。

2.7.2に記載されている内容とも関連していますが、設定機能を設けて、ユーザが位置情報サービスの使用/未使用を
設定できるようにしておきなさいということですね。設定ページを設けるか、Application Barに設定しておくかですかね。

2.7.4 If your application publishes or makes available location data obtained from the Location Service API to any other service or other person (including advertising networks), your application must implement a method to obtain opt-in consent. To "implement a method to obtain 'opt-in' consent," the application must:
(位置情報を他のサービスや、他者に公開する場合、ユーザに事前に許可を得る必要がある。許可を得る際には、以下の内容を案内する。)

(a) first describe how the location information will be used or shared;
(どのように位置情報が使用、共有されるか)

(b) obtain the user's express permission before publishing the location information as described; and
(情報を公開する前にユーザの明示的な許可が必要。)

(c) provide a mechanism through which the user can later opt out of having the location information published. Your application must periodically remind users or provide a visual indicator that location data is being sent to any other service or person.
(ユーザが情報提供の停止ができる機能を提供しなさい。また、ユーザに他のサービスや、他者に位置情報を共有していることを定期的に通知しなさい。)

これは、位置情報を他のサービス等に共有する場合に、対応しなければいけない内容です。
自分のアプリケーションで使用する範囲では対応が不要な内容ですね。

2.7.5 Your application must not override, circumvent, or suppress any Microsoft toast notification or prompts related to the Location Service API.
(Location Service APIに関するマイクロソフトの通知を変更したり、止めたりしてはいけない。)

これは、普通に開発していればやらないことなので、大丈夫だと思います。

2.7.6 Your application must not override or circumvent a user's choice to disable location services on the mobile device.
(位置情報の使用設定を変更してはいけない。)

OSの位置情報の使用設定を上書きしないでってことだと思います。通常これもやらないので、
大丈夫だと思います。(というか、出来るのかな。。)

2.7.7 Your application must request location and retain and use location data from the Location Service API only as necessary to deliver the location-aware features your application provides to users.
(位置情報に関連する機能をユーザに提供するためだけに、位置情報を取得しなさい。)

要するに、意味なく、または、ユーザが認識していない用途で使用するために位置情報を取得するなということですね。
ユーザに対しては、位置情報を使用する機能を全く提供していないのに、裏では、サーバに位置情報を送信しているような
ことはNGだということですね。

2.7.8 You and your application must adopt measures to protect against unauthorized access to, use or disclosure of location data received from the Location Service API.
(位置情報を不正なアクセス、使用、公開から保護する方法を採用する必要がある。)

他のサービスなどへの情報共有をしている場合は、気を付けないといけない項目かもしれませんが、していない場合は
特に気にする必要がない(?)気がします。

また、その他、Additional Requirements for Specific Application Types(特定のタイプのアプリケーションに対する要件)
カテゴリに以下の内容が記載されています。

Users have the ability to turn off the Location Service on the phone from the System Settings page. Location aware applications must remain responsive when the Location Service is turned off on the phone.
(ユーザがシステム設定で位置情報を使用不可にしても、アプリケーションは正常にレスポンスをユーザに返す必要がある。)

位置情報が使用できない状態でも、ハングアップしないようにしなさいということですね。
GeoCoordinateWatcherのStatusChangedイベントを正しく処理していれば回避可能です。

Recommendations(推奨)
Present a user-friendly message to indicate that location data is not available.
(位置情報が使用できない場合は、その旨をユーザに知らせなさい。

上記は推奨ですが、通常はこうしておいた方が良いですよね。

こうして見てみると追加で明示的に行わないといけないのは、プライバシーポリシーでの
位置情報の使用に関する記載と、位置情報の使用/未使用を変更するための設定機能を
設けるという2点ですね。

後はやらなけば良い内容と、通常はやっている内容になると思います。

(訳が間違っていたらごめんなさい。。)
スポンサーサイト
本日は、Windows Phoneのページ間でデータを受け渡しする方法をご紹介します。

現在、開発中のホテル検索のアプリは、検索結果一覧から、ホテルの詳細を表示するところまで進みました。



検索結果一覧から、ホテル詳細に遷移する際は、ホテルNoを一覧画面から詳細画面に受け渡す
必要がありましたが、以下のように、QueryStringを利用して行いました。

まず、受け渡す方の一覧画面の方のソースです。
    NavigationService.Navigate(
              new Uri(String.Format("/Pages/HotelDetailPage.xaml?hotelNo={0}",seletedInfo.HotelNo), UriKind.Relative)
    );


Windows Phoneのページ遷移はNavigationSeriveのNavigate()メソッドを使用して行いますが、
その際、遷移先ページの後ろにクエリストリングを付与することで、データを遷移先ページに
受け渡すことが出来ます。

受け側の詳細画面では、以下のようにしてパラメータを受取ります。

  protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  {
      String hotelNo;
      NavigationContext.QueryString.TryGetValue("hotelNo", out hotelNo);
      GetHotelDetailInfo();
  }

このようにQueryStringを使用する方法はとてもシンプルで良いのですが、
パラメータが増えた場合、ちょっと面倒です。

例えばあるクラスのプロパティの内容を全て引渡しする場合、一度それらを
パラメータとして、QueryStringに展開する必要があります。

このようなケースに使用できる方法として、Windows Phoneアプリケーション(Siverlightアプリケーション)を
カプセル化している、System.Windows.Applicationクラスを使用する方法があります。

Applicationクラスの静的プロパティ「Current」には、現在のアプリケーションのApplicationインスタンスが
格納されています。このプロパティで取得できるインスタンスのクラスは、「App.xaml.cs」で定義されている
「App」クラスのインスタンスです。

従って、Appクラスにページ間で共有したい情報を定義しておけば、Application.Currentを通して、
そのインスタンスにアクセスし、そのプロパティを参照することが可能になります。

今回開発しているアプリでは、以下のようにホテルの詳細情報を管理するクラスの
プロパティをAppクラスに追加しました。

「App.xaml.cs」
   public HotelDetailInfo HotelDetailInfo { get; set; }

これらのプロパティには、以下のようにして、各ページからアクセスすることが可能です。

   (Application.Current as App).HotelDetailInfo = detailInfo;

共有するプロパティが増える場合は、Dictionaryなどを
Appクラスに定義しておいて、その中で管理する方法を採用した方が良いかもしれません。

今回は、前回の記事で、楽天のWeb APIから取得した施設情報をListBoxを使って表示するところを紹介します。

SilverlightのListBoxはXAMLの記述だけで、柔軟に表示する項目をカスタマイズできるとても便利な機能を持っています。
現時点でのアプリの画面はこんな感じになっています。



まず、PageのXAMLにListBoxを配置します。

  <ListBox x:Name="resultList" HorizontalAlignment="Stretch" Margin="12,0,12,0" >

そして、コード側で、ObservableCollectionクラスのオブジェクトをインスタンス変数として、
宣言し、ListBoxのItemsSourceに設定します。ObservableCollectionクラスは
リストの項目が追加、削除された時に通知を行なってくれるコレクションで、
これをListBoxにバインドしておけば、後から、要素の追加と削除を行なった際に
それがListBoxに反映されます。


   private ObservableCollection hotelList;

   public MainPage()
   {
      ・・・・・・・・・・・・・・・・・・・・・

      //ListBoxにバインドする
      hotelList = new ObservableCollection<HotelInfo>();
      resultList.ItemsSource = hotelList;
   }

ホテルの情報を管理するエンティティクラスの「HotelInfo」は以下のような項目を持っています。

  public class HotelInfo
  {
      //施設No
      public String HotelNo { get; set; }

      //施設名
      public String HotelName { get; set; }

      //アクセス
      public String Access { get; set; }

      //施設特徴
      public String HotelSpecial { get; set; }
  }

そして、ListBoxのItemTemplateをXAMLで定義します。

  <ListBox x:Name="resultList" HorizontalAlignment="Stretch" Grid.Row="2"
                  Margin="12,0,12,0" SelectionChanged="resultList_SelectionChanged">

     <!-- ListBoxの項目に対して、設定するスタイル -->
     <ListBox.ItemContainerStyle>
          <Style TargetType="ListBoxItem">
              <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
          </Style>
      </ListBox.ItemContainerStyle>

      <ListBox.ItemTemplate>
          <DataTemplate>
              <Border BorderBrush="Black" BorderThickness="0,0,0,1">
                  <StackPanel Orientation="Vertical">
                      <TextBlock TextWrapping="Wrap" FontSize="20" 
                                     Text="{Binding HotelName}" Foreground="Blue"/>
                      <TextBlock TextWrapping="Wrap" FontSize="18" 
                                     Text="{Binding HotelSpecial}" Foreground="Black"/>
                      <Grid>
                          <Grid.ColumnDefinitions>
                              <ColumnDefinition Width="20"/>
                              <ColumnDefinition/>
                          </Grid.ColumnDefinitions>
                          <Image Grid.Column="0" Margin="0,3,0,0" VerticalAlignment="Top" 
                                                   Height="16" Width="16" Source="Images/train.png"/>
                          <TextBlock Grid.Column="1" TextWrapping="Wrap" 
                                         FontSize="16" VerticalAlignment="Top" 
                                         Margin="3,0,0,0" Text="{Binding Access}" Foreground="Black"/>
                      </Grid>
                  </StackPanel>
              </Border>
          </DataTemplate>
      </ListBox.ItemTemplate>
  </ListBox>


まず、最初の重要な部分は、ItemContainerStyleで、項目全体の「HorizontalContentAlignment」を
「Stretch」に設定している部分です。これを記述しておかないと、ItemTemplate内部の要素で、
HorizontalContentAlignmentを設定しても、「Stretch」が反映されず、
項目毎に枠線などを表示する仕様にしていても、枠線が画面の途中で終わってしまったりします。

次に「ItemTemplate」に対して、DataTemplateを設定します。ここで各項目に表示する内容を記述します。
DataTemplate内で、マークアップ拡張「Binding」を使用すると、ListBoxにバインドされているコレクションの
各要素のプロパティを参照することが出来ます。

具体的には、以下のように記述することで、各HotelInfoクラスのインスタンスのHotelNameを参照することが出来ます。

  <TextBlock Text="{Binding HotelName}"/>

ListBoxに限った話では無いのですが、<image>と<TextBlock>を横に並べる際に
Gridを使用しています。通常、StackPanelを水平方向で使用すれば良いのですが、
その場合、TextBlockのTextWrappingに「Wrap(折り返し)」を設定しても、
それが効かなくなります。そのため、Gridを使用して横に並べています。

前回の記事で、位置情報の取得部分を紹介したので、今回は、楽天のWebAPIから周辺の施設情報を取得する部分を紹介します。

■Windows PhoneのWeb接続

Windows PhoneからのWeb接続は、「WebClient」を使用する方法が最もシンプルです。
検索ボタンのイベントハンドラで以下のように、記述しています。

[MailPage.xaml.cs]
   WebClient webClient = new WebClient();
   //ダウンロードが完了した時点で発生するイベント
   webClient.DownloadStringCompleted +=
       new DownloadStringCompletedEventHandler((Object downloadEventSender, DownloadStringCompletedEventArgs downloadEventArgs) =>
       {
           //結果のXMLを解析するメソッド
           SetHotelList(downloadEventArgs.Result);
       });
   //ダウンロードを非同期で開始する
   webClient.DownloadStringAsync(new Uri(requestUrl));

「Silverlight」のWebClientには同期型のダウンロードメソッドが提供されていないので、
Web接続は、必ず非同期で実施することになります。

個人的には、バックグラウンドスレッドを使えば、同期メソッドでも、非同期で使用できるので、メソッド自体は提供されても良いのでは思います。
非同期処理の場合、処理が入れ子になって、ソースが見づらくなる傾向があります。
別の方法として、Silverlightに標準装備されている「Reactive Extensions」を使用すると、シンプルに処理を記述することが可能になります。
これについては、別に記事にしたいと思っています。

ダウンロードの結果は、DownloadStringCompletedEventArgsのResultプロパティから取得することが可能です。

■Windows PhoneのXML解析

取得したXMLを解析し、HotelInfoをいうエンティティクラスのインスタンスに情報をセットし、
それをListBoxにバインドしているObservableCollectionに積めていきます。

SilverlightでXMLを解析する方法はいくつかの選択肢があります。
PCのSilverlightの場合、個人的に「XmlSerializer」を使用して、XMLをオブジェクトへデシリアライズ
する方法を好んでいるのですが、Windows Phoneの場合、使用できるリソースに制限があるため、
最も軽い方法を選択しようと「XmlReader」を使用することにしました。

参考:「XML解析のパフォーマンス比較」

  using (XmlReader xmlReader = XmlReader.Create(new StringReader(xml)))
  {
      HotelInfo hotelInfo = null;
      bool isHotelNode = false;
      String elementName = null;

      while (xmlReader.Read())
      {
          if (xmlReader.NodeType == XmlNodeType.Element)
          {
              if (xmlReader.Name.Equals("hotel"))
              {
                  isHotelNode = true;
                  hotelInfo = new HotelInfo();
              }
              else
              {
                  if (isHotelNode)
                  {
                      elementName = xmlReader.Name;
                  }
              }
          }
          else if (xmlReader.NodeType == XmlNodeType.Text)
          {
              if (isHotelNode)
              {
                  switch (elementName)
                  {
                      case "hotelNo" :
                          hotelInfo.HotelNo = xmlReader.Value;
                          break;
                      case "hotelName" :
                          hotelInfo.HotelName = xmlReader.Value;
                          break;
                      case "access" :
                          hotelInfo.Access = xmlReader.Value;
                          break;

                  }
              }
          }
          else if (xmlReader.NodeType == XmlNodeType.EndElement)
          {
              if (xmlReader.Name.Equals("hotel"))
              {
                  isHotelNode = false;
                  hotelList.Add(hotelInfo);
              }
          }
      }
  }

ソースはちょっと冗長になってしまっているので、ご参考までに。
今回、開発するアプリは、場所を指定された場所の近辺にある宿泊施設を検索し、その空き情報を表示するというアプリです。
宿泊施設の情報は、楽天のWebサービスから取得します。

まだ、開発を始めたばかりですが、ここまで開発した画面は以下のような感じです。



このアプリでは、検索する場所を携帯基地局やGPS等の位置情報サービスから取得する方法と
地図で指定する方法の2通りを想定しています。
本日は、Windows PhoneのSilverlight APIで、位置情報サービスを使って位置情報を取得する方法をご紹介します。

Windows Phoneの位置情報サービスは、GPSやWi-Fi、3Gアンテナなどから得られる位置情報にアクセスするためのAPIで、
「System.Device.Location」名前空間から提供されています。

「System.Device.Location」を利用するためには、まず、「System.Device」への参照を追加する必要があります。 そして、以下のように、using句を記述します。

[MailPage.xaml.cs]
   using System.Device.Location;

位置情報サービスにアクセスするためのProxyの役割を果たすのが「GeoCoordinateWatcher」というクラスです。
まず、この型をMainPageのインスタンス変数として宣言します。アプリケーションを通して、位置情報サービスを
使用する場合、アプリケーション中で単一のインスタンスになるように、Singletonパターンでラップするなど
考えた方が良さそうですが、一旦ページのインスタンスとして宣言しました。

[MailPage.xaml.cs]
   private GeoCoordinateWatcher watcher;

そして、ページのコンストラクタで、GeoCoordinateWatcherをインスタンス化します。

[MailPage.xaml.cs]
  public MainPage()
  {
      InitializeComponent();

      //位置情報サービスを初期化する
      watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);

      //PositionChangedイベントを受け取る感覚をメートル単位で指定
      watcher.MovementThreshold = 20;

      //イベントハンドラを登録する
      watcher.StatusChanged += new EventHandler(watcher_StatusChanged);
      watcher.PositionChanged += new EventHandler>(watcher_PositionChanged);

  }

コンストラクタに指定する「GeoPositionAccuracy」とは、位置情報サービスに求める正確性を設定する引数です。
「Default」と「High」から選択可能ですが、実際にどのデバイス(GPSなど)から情報を得るかを直接指定することは
できません。位置情報サービスが、現在アクセス可能なデバイスから要求に見合うものを選択します。
今回開発するアプリではそこまでの精度は必要ないので、「Default」設定にします。

GeoCoordinateWatcherは、デバイスの位置が移動すると、位置が変わったことをイベントで通知してくれますが、
その通知の発生感覚を調整するのが「MovementThreshold」です。

MovementThresholdには前回、位置情報の変更通知を受け取ってから、次回に受け取るまでの間の移動距離を
メートル単位で指定します。既定値は0で、少しでも位置が移動した場合、通知を受取ります。
場合によってはGPSのノイズで通知が発生するケースもあると、ドキュメントには記載されています。

また、値を小さく設定すると、その分、電力消費量は大きくなるので、20メートル以上を設定しておくことが
望ましいようです。

次にイベントハンドラを登録します。「StatusChanged」イベントは、位置情報サービスの状態が変化した時に 発生するイベントです。

[MailPage.xaml.cs]
  private void watcher_StatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
  {
      switch (e.Status)
      {
          //位置情報サービスが使用できない状態
          case GeoPositionStatus.Disabled:
              if (watcher.Permission == GeoPositionPermission.Denied)
              {
                  message.Text = "位置情報サービスが無効になっています。";
              }
              else
              {
                  message.Text = "位置情報サービスを使用できません。";
              }
              if (watcher.Position.Location.IsUnknown)
                  searchBtn.IsEnabled = false;
              break;
          //位置情報サービスが初期化中の状態
          case GeoPositionStatus.Initializing:
              message.Text = "位置情報サービスを準備しています。";
              break;
          //位置情報サービスが位置情報を取得できない状態
          case GeoPositionStatus.NoData:
              message.Text = "データが取得できません。";
              if (watcher.Position.Location.IsUnknown)
                  searchBtn.IsEnabled = false;
              break;
          //位置情報を取得できる状態
          case GeoPositionStatus.Ready:
              message.Text = "";
              searchBtn.IsEnabled = true;
              break;
      }
  }

位置情報サービスの状態は、ユーザの設定や端末の状態によって、変化します。
アプリケーション側では、その状態の変化を適切に処理する必要があります。
今回、開発しているアプリでは、位置情報サービスが使用可能な状態の場合のみ、
検索ボタンをタップできるように制御しています。最後に取得した位置情報がGeoCoordinateWatcherの
Positionプロパティに保存されるので、現在、位置情報サービスが取得できない状態でも
Positionプロパティに値が入っている場合は、ボタンをタップできるようにしています。

PositionChangedイベントは、位置情報の変更を受け取った際に、発生するイベントです。
今回のアプリでは、位置情報が変わる毎に処理する内容は無いので、特に処理はしていません。
(説明のためにイベントハンドラを登録しています。)

ここまで準備が完了したので、GeoCoordinateWatcherのStart()メソッドをコールし、位置情報の取得を開始します。
今のところ、この処理はMainPageの「OnNavigatedTo」で行なっています。

[MailPage.xaml.cs]
  protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
  {
      watcher.Start();
  }

検索ボタンがクリックされてから、位置情報の取得を開始する方法も考えられますが、
状況によっては初期化に時間がとられる可能性があるため、ページに遷移した時に
スタートするようにしました。

ここでは、詳しく述べませんが、位置情報サービスを使用するアプリをマーケットに
登録する場合、満たさなければならない要件がいくつかあります。その中の
ひとつに、ユーザに対して、位置情報サービスを使用することを通知し、
承諾を得るというものがあります。OnNavigatedToで、GeoCoordinateWatcherのStart()メソッドをコールしているので、
これをクリアするためには、このページに遷移する前に、承諾を得るためのページを挟み込む必要がありそうです。

最後にユーザが当ページから他のページに遷移する際に、位置情報サービスを停止する処理を記述しています。

[MailPage.xaml.cs]
  protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
  {
      watcher.Stop();
      searchBtn.IsEnabled = false;
  }
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。