Dateiübertragungen, Location Tracking und Neuheiten in WP 8.1

Windows Phone Apps im Hintergrund mit Background Agents [Teil 2]
Kommentare

Während es im ersten Teil dieser Artikelserie um die Ausführung von periodisch wiederkehrenden oder ressourcenintensiven Aufgaben bzw. um das Abspielen von Audiodaten im Hintergrund ging, soll es in diesem zweiten Teil um die Übertragung von Dateien und das Aufzeichnen von Positionsdaten im Hintergrund gehen, genauso wie um die mit Windows Phone 8.1 neu eingeführten BackGroundTasks. Die Beispielprojekte finden Sie zusammen mit denen aus Teil 1 hier.

>>> Und hier geht es zu Teil 1

Eine der immer wieder benötigten Funktionalitäten in einer mobilen App ist die Übertragung von Daten bzw. Dateien im Hintergrund. Bereits die Windows-Phone-7.5-Plattform hat hierfür den BackGroundTransferAgent bereitgestellt, einen speziellen Betriebssystemagenten. Er wird mithilfe des BackGroundTransferService und der Klasse BackGroundTransferRequest aus der Vordergrund-App heraus gesteuert. Dabei können Daten sowohl hoch- als auch heruntergeladen werden. Zunächst ist hierzu ein neuer BackGroundTransferRequest anzulegen. Diese Anforderung enthält unter anderem die Ressource, auf die zugegriffen werden soll als URL, die Methode (GET oder POST), mit der der Zugriff erfolgen soll und die Datei im IsolatedStorage, in die oder aus der übertragen werden soll. Hinweis: Es ist nicht möglich, direkt einen Stream zu übertragen, das funktioniert nur via IsolatedStorage:

var request = 
  new BackgroundTransferRequest(fileToDownload) {
  Method = "GET",
  DownloadLocation = saveToFilename
};

Das Anforderungsobjekt wird anschließend mithilfe des BackGroundTransferService in die Werteschlange eingereiht:

BackgroundTransferService.Add(request);

Windows Phone Apps im Hintergrund

Es können nicht beliebig viele Hintergrundübertragungen in die Warteschlange eingereiht werden. So lassen sich unter Windows Phone 8 maximal fünf Anforderungen je App einreihen und auf dem Gerät dürfen es insgesamt nicht mehr als 500 Anforderungen sein, wobei immer maximal zwei Anforderungen gleichzeitig bearbeitet werden können. Wird eines der Limits überschritten, tritt eine Exception beim Einreihen mithilfe des BackgroundTransferService auf. Ferner darf die Größe der Datei, abhängig von den Voreinstellungen des Requests, bei Mobilfunk 20 und bei WLAN 100 Megabyte nicht überschreiten. Für die Zertifizierung im Store ist zu beachten, dass jeder Transfer durch den Benutzer initiiert werden muss (keine automatischen Transfers, davon ausgenommen sind benutzerimitierte zeitgesteuerte Übertragungen). In jedem Fall ist der Benutzer über jeden stattfindenden Transfer zu informieren, mit der Möglichkeit, diesen ggf. abzubrechen. Der Benutzer sollte darüber hinaus auswählen können, ob UMTS oder WiFi für die Übertragungen verwendet werden soll. In der Beispielanwendung „03 FileTransfer“ unter [1] werden die zu übertragenden Dateien unter Verwendung der Hilfsfunktion GetDownloadItems aus der XML-Datei data.xml gelesen:

public IEnumerable GetDownloadItems(string uri = "data.xml") {
  var data = XElement.Load(uri);
  return from f in data.Descendants("file")
  select new DownloadItem {
    Name = f.Attribute("name").Value,
    Filename = f.Attribute("filename").Value,
    Url = f.Attribute("url").Value
  };
}

Nach dem Navigieren auf die Hauptseite MainPage.xaml werden zunächst alle Dateien ausgelesen, die übertragen werden können. Anschließend werden alle aktiven Hintergrundübertragungen ermittelt und mit den zu übertragenen Dateien abgeglichen. Dabei wird der aktuelle Status übernommen und die Event Handler der Vordergrund-App werden mit möglichen aktive Hintergrundübertragungen verbunden, sodass neben dem Status auch der Vorschnitt angezeigt werden kann (Listing 1).

protected override void OnNavigatedTo(NavigationEventArgs e) {
  EnsureTransfersDirectory();

  _items = new ObservableCollection(GetDownloadItems());
  lsItems.ItemsSource = _items;

  // reattach to ongoning transfers
  foreach (var request in BackgroundTransferService.Requests) {
    var file = Path.GetFileName(request.DownloadLocation.OriginalString);
    var item = _items.SingleOrDefault(i => i.Filename == file);
    if (item == null) continue;

    request.TransferProgressChanged += (sender, args) => UpdateProgress(item, request);
    request.TransferStatusChanged += (sender, args) => UpdateTransferStatus(item, request);
    UpdateTransferStatus(item, request);
    UpdateProgress(item, request);
  }
}

Der Status der einzelnen Elemente wird direkt mit der Beschriftung des Buttons gespeichert. Beim Betätigen eines Download-Buttons wird mithilfe der aktuellen Beschriftung ermittelt, welche Aktion auszuführen ist. Ferner lässt sich über den DataContext des Buttons, der das Ereignis ausgelöst hat, das zu verwendende Element ermitteln (Listing 2).

private async void BtnDownload_OnClick(object sender, RoutedEventArgs e) {
  // den Sender als Button verwenden
  var button = sender as Button;
  if (button != null) {
    // Der DataContext des Buttons enthält das Element der Liste
    // in diesem Beispiel ist das ein DownloadItem
    var item = button.DataContext as DownloadItem;
    switch (button.Content as string) {
      case "Download":
      StartDownload(item);
      break;
      case "Cancel":
      ChancelDownload(item);
      break;
      case "Show":
      var shared = await ApplicationData.Current.LocalFolder.GetFolderAsync("shared");
      var folder = await shared.GetFolderAsync("transfers");
      StorageFile pdffile = await folder.GetFileAsync(item.Filename);

      // Launch the pdf file
      await Windows.System.Launcher.LaunchFileAsync(pdffile);
      break;
    }
  }
}

Background Location Tracker

Wird eine App auf einem Windows Phone in den Hintergrund verlagert, geht sie zunächst in den Zustand „Dormant“ und anschließend „Tombstoned“ (je nach Ressourcenauslastung). In beiden Zuständen laufen keine Threads mehr, die App kann auf keine Ereignisse mehr reagieren und keine Daten verarbeiten. Alle Aktivitäten bleiben stehen. Mit Windows Phone 8 wurde eingeführt, dass eine App, die permanent Positionsdaten verarbeitet, auch im Hintergrund weiter laufen darf. Dieses neue Feature ermöglicht es Navigationsanwendungen und Positions-Trackern, auch im Hintergrund weiter ausgeführt zu werden. Jedoch gibt es auch hierbei Einschränkungen. Es darf nur eine Location-Tracker-App im Hintergrund weiterlaufen. Wird eine zweite in den Hintergrund verschoben, wird die erste App in den Zustand „Dormant“ überführt. Für die Zertifizierung im Store ist zu beachten, dass nur solche Apps als Background-Tracking-Apps zugelassen werden, die die Positionsdaten permanent verarbeiten müssen, um ihre Funktion zu erfüllen. Praktisch unterscheidet sich der Programmcode einer Background-Tracking-App nicht von einem Vordergrund-Tracker. Der Unterschied besteht lediglich in der Erlaubnis, im Hintergrund weiterlaufen zu dürfen. Diese Erlaubnis wird in der WMAppManifest.xml-Datei als Erweiterung von DefaultTask angegeben:


    
      
    
  

Hinweis: Wird diese Datei mit einem Doppelklick geöffnet, startet ein Assistent in Visual Studio 2013. Dieser enthält jedoch nicht die erforderliche Einstellung. Öffnen Sie die Datei stattdessen mit einem Rechtsklick und wählen Sie einen XML-Editor aus, um die Erweiterung manuell einzufügen. Während die App im Hintergrund ausgeführt wird, ist es empfehlenswert, alle UI-Aktivitäten (Aktualisieren von Steuerelementen) zu unterbinden. Diese Aktionen sind zwar nicht sichtbar, verbrauchen aber Energie und Ressourcen. Aus diesem Grund wurde mit Windows Phone 8 das weitere Systemereignis RunningInBackGround eingeführt. Der Zugriff auf dieses Systemereignis erfolgt über das globale Anwendungsobjekt in der Datei App.xaml:


  
  

In der Code-Behind-Datei App.xaml.cs könnte dann der Programmcode aus Listing 3 stehen. Eine globale öffentliche Variable RunningInBackground wird entsprechend von den Ereignissen gesetzt und teilt dem UI mit, ob eine Aktualisierung erfolgen soll oder nicht.

// Static variables global to application to support tracking
public static Geolocator Geolocator { get; set; }
public static bool RunningInBackground { get; set; }
// 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)
{
  RunningInBackground = false;
}
// Code to execute when the application is deactivated and is tracking location
private void Application_RunningInBackground(object sender, RunningInBackgroundEventArgs e)
{
  RunningInBackground = true;
  // Suspend all unnecessary processing such as UI updates
}

Beim Öffnen der Hauptseite MainPage.xaml wird in dem Ereignis OnNavigatedTo zunächst überprüft, ob bereits ein Geolocator angelegt wurde. Ist das nicht der Fall, wird eine neue Instanz erzeugt:

protected override void OnNavigatedTo(NavigationEventArgs e) {
  if (App.Geolocator != null) return;
  App.Geolocator = new Geolocator {
    DesiredAccuracy = PositionAccuracy.High, 
    MovementThreshold = 100
  };
  App.Geolocator.PositionChanged += geolocator_PositionChanged;
}

Um den Geolocator auch wieder beenden zu können, wird das Event OnRemovedFromJournal verwendet. OnNavigatedFrom kann nicht benutzt werden, weil dieses Event auch dann ausgelöst wird, wenn die App in den Hintergrund verschoben wird. Im Gegensatz dazu wird OnRemovedFromJournal nur ausgelöst, wenn die Navigation mittels Back-Button die App verlässt:

protected override void OnRemovedFromJournal(JournalEntryRemovedEventArgs e) {
  App.Geolocator.PositionChanged -= geolocator_PositionChanged;
  App.Geolocator = null;
}

Im Event PositionChanged des Geolocators wird zunächst die globale Variable RunningInBackground ausgewertet. Läuft die App im Vordergrund, wird die Position ausgegeben, läuft sie im Hintergrund, wird zu Demonstrationszwecken ein Toast erzeugt (Listing 4).

void geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args) {
  if (!App.RunningInBackground) {
    Dispatcher.BeginInvoke(() => {
        LatitudeTextBlock.Text = args.Position.Coordinate.Latitude.ToString("0.00");
        LongitudeTextBlock.Text = args.Position.Coordinate.Longitude.ToString("0.00");
    });
  } else {
    // Show toast if running in background
    var toast = new ShellToast {
      Content = string.Format("[{0} | {1}]", 
        args.Position.Coordinate.Latitude.ToString("0.00"),
        args.Position.Coordinate.Longitude.ToString("0.00")),
      Title = "Location: ",
      NavigationUri = new Uri("/TrackingPage.xaml", UriKind.Relative)
    };
    toast.Show();
  }
}

Aufmacherbild: internet connection by smart phone von Shutterstock / Urheberrecht: Wichy

[ header = Seite 2: BackgroundTasks ]

BackgroundTasks in Windows Phone 8.1

Mit Windows Phone 8.1 wurden die bereits aus Windows 8 bekannten BackgroundTasks auch auf der Phone-Plattform eingeführt. Sie stehen unter anderem in Universal-App-Projekten zur Verfügung.

>Eine BackgroundTask wird durch Implementierung der Schnittstelle IBackgroundTask erstellt. Dabei empfiehlt es sich, die eigene Hintergrundtaskklasse in ein separates (Windows-Runtime-Component-)Projekt abzulegen (Abb. 1).

Abb. 1: Anlegen des Projekts für die Hintergrundtask

Die Klasse MyBackgroundTask implementiert eine öffentliche Funktion Run. Innerhalb dieser Funktion wird eine Schleife zehn Mal durchlaufen. Innerhalb der Schleife wird mit einem asynchronen Delay „das Arbeiten“ simuliert. Mithilfe der übergebenen Instanz kann der Fortschritt an die Vordergrundtask übermittelt werden.

public sealed class MyBackgroundTask : IBackgroundTask {
  public async void Run(IBackgroundTaskInstance taskInstance) {
    var deferral = taskInstance.GetDeferral();
    bool cancelled = false;
    taskInstance.Progress = 0;

    BackgroundTaskCanceledEventHandler handler = (s, e) => {
      cancelled = true;
    };

    for (uint i = 0; ((i < 10) && !cancelled); i++) {
      await Task.Delay(5000);
      taskInstance.Progress = i + 1;
      ShowToast(i);
    }
    ApplicationData.Current.LocalSettings.Values["LAST_RUN_TIME"] = DateTimeOffset.Now;

    deferral.Complete();
  }

  private void ShowToast(uint cnt) {
    var toastXml  = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText01);
    XmlNodeList toastTextAttributes = toastXml.GetElementsByTagName("text");
    toastTextAttributes[0].InnerText = String.Format("Hello from Background {0}", cnt);
    var toast = new ToastNotification(toastXml);
    ToastNotificationManager.CreateToastNotifier().Show(toast);
  }
}

Die Hilfsfunktion ShowToast stellt eine ToastNotification dar. Hinweis: Diese funktioniert nur, wenn in Package.Manifest der Windows-8.1- und der Windows-Phone-8.1-App Toast-Benachrichtigungen aktiviert wurden (Abb. 2).

Abb. 2: Aktivieren der Toast-Benachrichtigungen in der Datei „Package.appxmanifest“

Die Vordergrund-App muss das Projekt mit der Hintergrundtask referenzieren. Anschließend kann der Auslöser für die Hintergrundtask festgelegt werden. Hier stehen verschiedene Optionen zur Auswahl, z. B. kann dies nach dem Eintreten eines Systemereignisses erfolgen (Benutzer anwesend, Internet verbunden, Einstellungen verändert etc.). Es können aber auch Geodaten oder ein Timer verwendet werden, um die Hintergrundtask auszulösen. Im Beispielprojekt soll das Ändern der Zeitzone als Auslöser verwendet werden. Dazu ist zunächst ein weiterer Eintrag in der Package.appxmanifest-Datei erforderlich. Dieser registriert die zuvor erstellt Klasse als Hintergrundtask und erlaubt die Ausführung (Abb. 3). Ist die Klasse nicht registriert, wirft das System eine Exception beim Aktivieren des Triggers (Auslösers) der Task. In der Beispielanwendung ist ein Systemauslöser hinzuzufügen.

Abb. 3: Registrieren einer Hintergrundtask in der Datei „Package.appxmanifest“

Jetzt kann die Hintergrundtask registriert werden, wobei der BackGroundTaskBuilder zum Einsatz kommt. Anschließend wird der Trigger festgelegt. In Listing 6 soll die Task ausgeführt werden, wenn die TimeZone verändert wird.

private async void RegisterTask() {
  var result = await BackgroundExecutionManager.RequestAccessAsync();
  if (result == BackgroundAccessStatus.Denied) {
    // Handle this if it is important for your app.
    
  }

  var taskBuilder = new BackgroundTaskBuilder { Name = "MyBackgroundTask" };
  var trigger = new SystemTrigger(SystemTriggerType.TimeZoneChange, false);

  taskBuilder.SetTrigger(trigger);
  taskBuilder.TaskEntryPoint = typeof(MyBackgroundTask).FullName;
  //taskBuilder.Register();
  var registration = taskBuilder.Register();
  registration.Completed += (sender, args) => {
    // Handle this if it is important for your app.
  };
  GetTask();
}

Die Hilfsfunktion GetTask ruft die erste registrierte Hintergrundtask ab und „attached“ den Ereignis-Handler für diese Task:

private void GetTask() {
  TaskRegistration = BackgroundTaskRegistration.AllTasks.Values.First();
  TaskRegistration.Completed += OnCompleted;
  TaskRegistration.Progress += OnProgress;
}

Um die Task deaktivieren zu können, wurde die registrierte Task in einer privaten Variablen zwischengespeichert. So kann die Funktion Unregister direkt aufgerufen werden:

private  void UnregisterTask() {
  TaskRegistration.Completed -= OnCompleted;
  TaskRegistration.Progress -= OnProgress;
  TaskRegistration.Unregister(false);
  TaskRegistration = null;
}

Da die Task auch nach dem Beenden der App registriert bleibt, wird die Hilfsfunktion zur Ermittlung möglicher registrierter Tasks auch im Konstruktor des ViewModels aufgerufen. So kann sichergestellt werden, dass die Ereignis-Handler auch an einer bereits vorhandenen Task registriert wurden:

public MainViewModel() {
  EnableBgTaskCommand = new DelegateCommand(p => RegisterTask(), (o) => TaskRegistration == null);

  DisableBgTaskCommand = new DelegateCommand(p => UnregisterTask(), (o) => TaskRegistration != null);

  if (TaskIsRegistered) {
    GetTask();
  }
}

Die laufende Hintergrundtask sehen Sie in Abbildung 4.

Abb. 4: Laufende Background-Task unter Windows Phone 8.1

Zusammenfassung

Dieser zweiteilige Artikel hat einen kleinen Einblick in die Möglichkeiten zur Ausführung von Aufgaben im Hintergrund gegeben. Mit Windows Phone 8.1 ist die Option dazugekommen, eine Aufgabe in Abhängigkeit von einem anderen Ereignis oder einer Benutzeraktion auszuführen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -