Windows2012. 12. 26. 16:25



App LifeCycle 

Metro app의 생명주기가 어떤 윈도우이벤트를 통해서 관리되어지는지에 대해 설명한다. suspend와 resume상태를 처리하는 방법과 Contract를 통해서 어떻게 윈도우8이 제공하는 UX를 활용하는지에 대해서도 설명한다.


Dealing with the Metro Application Life Cycle

App.xaml.cs에서 생명주기관련 이벤트를 다룬다. OS에서 이와 관련된 이벤트를 전달한다. 생명주기는 3가지 상태가 있다.

1. Activation

 앱 실행시의 상태이다. 모든것이 준비되면 메트로런타임은 컨텐츠를 로드하고 이 이벤트를 발생시킨다. 

2. Suspend

 메트로앱을 사용하지 않을 때이다. 메트로앱에는 종료버튼이 존재하지 않는다. 앱이 활성화되지 않은 상태를 말하며 아무 코드도 실행되지 않고 사용자와 인터렉션도 없는 상태이다.

3. Restored

 Suspend에서 복원된 상태이다. 앱은 다시 화면에 보여지고 앱도 resume된다. Suspended 앱이 반드시 restore되지는 않는다. 가령 메모리가 부족해지면 윈도우는 앱을 바로 종료한다.


Correcting the Visual Studio Event Code

Application객체의 Suspending과 Resuming에 이벤트를 추가함으로써 Suspend와 Restore이벤트의 처리가 가능하다.

Activation은 OnLaunched를 override함으로써 가능하다.

using MetroGrocer.Data;

using Windows.ApplicationModel;

using Windows.ApplicationModel.Activation;

using Windows.UI.Xaml;

using Windows.UI.Xaml.Controls;

namespace MetroGrocer {

  sealed partial class App : Application {

    private ViewModel viewModel;

    public App() {

      this.InitializeComponent();

      viewModel = new ViewModel();

// ...test data removed for brevity...

  this.Suspending += OnSuspending;

  this.Resuming += OnResuming;

}

    protected override void OnLaunched(LaunchActivatedEventArgs args) {

      // Create a Frame to act navigation context and navigate to the first page

      var rootFrame = new Frame();

      rootFrame.Navigate(typeof(Pages.MainPage), viewModel);

      // Place the frame in the current Window and ensure that it is active

      Window.Current.Content = rootFrame;

      Window.Current.Activate();

}

  private void OnResuming(object sender, object e) {

    viewModel.GroceryList[1].Name = "Resume";

}

  void OnSuspending(object sender, SuspendingEventArgs e) {

    viewModel.GroceryList[0].Name = "Suspend";

} }

}


Adding a Background Activity

Resuming과 Suspending이벤트의 응답을 처리하는 예제로써 백그라운드테스크를 사용하여 볼것이다. geolocation service를 이용할것인데 그러기 위해서 Location.cs를 다음과 같이 정의하였다.


The Location.cs File

using System;

using System.Net.Http;

using System.Threading.Tasks;

using Windows.Data.Json;

using Windows.Devices.Geolocation;

namespace MetroGrocer.Data {

  class Location {

    public static async Task<string> TrackLocation() {

      Geolocator geoloc = new Geolocator();

      Geoposition position = await geoloc.GetGeopositionAsync();

      HttpClient httpClient = new HttpClient();

      httpClient.BaseAddress = new Uri("http://nominatim.openstreetmap.org");

      HttpResponseMessage httpResult = await httpClient.GetAsync(

        String.Format("reverse?format=json&lat={0}&lon={1}",

        position.Coordinate.Latitude, position.Coordinate.Longitude));

      JsonObject jsonObject = JsonObject

        .Parse(await httpResult.Content.ReadAsStringAsync());

      return jsonObject.GetNamedObject("address")

        .GetNamedString("road") + DateTime.Now.ToString("‘ (‘HH:mm:ss')'");

} }

}

이 클래스는 윈도8에서 디바이스의 위치를 얻기 위해 위치정보기능을 사용하고 있다. Windows.Devices.Geolocation의 Geolocator클래스의 GetGeopositionAsync메서드를 호출하여 위치정보를 얻는다. 위치정보의 업데이트를 체크하려면 다른 방식을 사용해야 한다.

주) await키워드는 Task Parallel Library 기술이며 백그라운드 테스크를 생성하여 다루는 고급기술이다. C#4.5부터 지원하며 비동기태스크가 끝날때까지 기다리라는 뜻이다.

위치정보를 얻어온 후 reverse geocoding service요청을 만들어서 요청하면 위,경도좌표정보로 주소기반의 정보를 얻어준다. 이 정보는 JSON string이며 C# object로 파싱하여 거리정보를 읽을 수 있다. 리턴되는 값은 거리정보리스트와 타임스탬프정보를 문자열로 만들어 리턴한다.

여기서 OpenStreetMap geocoding service를 사용하였는데 이는 어카운트를 요구하지 않기 때문이다. 이는 어카운트를 요구하는 구글맵이나 빙맵을 사용할수도 있다는 것이다.


package.appxmanifest에서 location service를 사용한다고 명시적으로 체크해주어야 한다. Capabilityes탭에서 Location capability를 체크한 후 저장한다.



백그라운드테스크관련하여 수정된 App.xaml.cs파일


using System.Threading;

using System.Threading.Tasks;

using MetroGrocer.Data;

using Windows.ApplicationModel;

using Windows.ApplicationModel.Activation;

using Windows.UI.Core;

using Windows.UI.Xaml;

using Windows.UI.Xaml.Controls;

namespace MetroGrocer {

  sealed partial class App : Application {

    private ViewModel viewModel;

  private Task locationTask;

  private CancellationTokenSource locationTokenSource;

  private Frame rootFrame;

    public App() {

      this.InitializeComponent();

viewModel = new ViewModel();
// ...test data removed for brevity...

      this.Suspending += OnSuspending;

      this.Resuming += OnResuming;

    StartLocationTracking();

    }

    protected override void OnLaunched(LaunchActivatedEventArgs args) {

      // Create a Frame to act navigation context and navigate to the first page

      rootFrame = new Frame();

      rootFrame.Navigate(typeof(Pages.MainPage), viewModel);

      // Place the frame in the current Window and ensure that it is active

      Window.Current.Content = rootFrame;

      Window.Current.Activate();

}

    private void OnResuming(object sender, object e) {

  viewModel.Location = "Unknown";

  StartLocationTracking();

    }

    void OnSuspending(object sender, SuspendingEventArgs e) {

      StopLocationTracking();

      SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral();

      locationTask.Wait();

      deferral.Complete();

    }

    private void StartLocationTracking() {

      locationTokenSource = new CancellationTokenSource();

      CancellationToken token = locationTokenSource.Token;

      locationTask = new Task(async () => {

        while (!token.IsCancellationRequested) {

          string locationMsg = await Location.TrackLocation();

          rootFrame.Dispatcher.Invoke(CoreDispatcherPriority.Normal,

            (sender, context) => {

              viewModel.Location = locationMsg;

            }, this, locationMsg);

          token.WaitHandle.WaitOne(5000);

        }

});

      locationTask.Start();

    }

    private void StopLocationTracking() {

      locationTokenSource.Cancel();

}
}

}


두가지 수정된 동작이 있다.  StartLocationTracking과 StopLocationTracking이다.

이 함수에 대해서는 블랙박스오 여겨주길 바란다. TPL컨셉과 특성에 대해서 잘몰라서리..

중요한건 StartLocationTracking은 위치정보를 5초마다 수집하는것을 시작하는 메서드고 StopLocationTracking은 그 작업을 중단시킨다는 것이다.

여기서 중요한건 생명주기 이벤트와 어떻게 연결하였느냐이다.앱이 시작될때 또는 resume될때 StartLocationTracking을 호출하고 Suspending이벤트때 백그라운드테스크가 앱이 중단되기전에 끝내도록 해야 한다. 그러지 않으면 예외가 발생할 수 있는데 앱이 resume되었을때 잘못된 데이터에 접근할 수 있고 suspended상태에서 네트웍이 끊겼을 경우에 에러가 발생할 수 있다.

이런 문제들을 방지하기 위해 SuspendingEventArgs.SuspendingOperation.GetDeferral메서드가 있는데 이를 통해 아직 suspended가 될수 있는 상황이 아니고 좀더 기다려야 함을  윈도우런타임에 알릴수 있다. GetDeferrel메소드는 SuspendingDeferral 객체를 리턴하는데 Suspended로 변경할 수 있는 시점이 되면 Complete메소드를 호출한다.

deferrel은 suspending을 준비하기 위해 추가로 5초를 더 보장한다. 이는 별로 좋지않게 느껴질 수 있으나 시스템 리소스를 절약하는 면에서는 중요하다.



메트로의 UI control들은 하나로 설계된 스레드내에서 업데이트될것이다. 이는 Application객체에서 사용하는 스레드이다. 백그라운드테스크에서 view model데이터를 업데이트 한다면 그 이벤트는 생략될것이다. 그러면 정상적으로 데이터 바인딩도 일어나지 않을 것이고 그 결과는 예외를 발생시킬것이다.

Dispatcher를 통해서 정확한 스레드에 업데이트이벤트를 전달해야 한다. 하지만 Application class에는 유효한 Dispatcher가 없다. 이를 해결하기 위해 Frame control의 Dispatcher를 이용하였다. 이를 위해 rrotFrame을 로컬이 아닌 인스턴스변수로 선언한것이다.


...

rootFrame.Dispatcher.Invoke(CoreDispatcherPriority.Normal,

  (sender, context) => {

    viewModel.Location = locationMsg;

   }, this, locationMsg);

...



Posted by 삼스