App-Entwicklung für Windows Phone 8–Part 4-2

Von maxs Autor Feed 22. February 2013 07:00

Aufgrund des Ausmaßes des vierten Parts unserer Serie, haben wir uns dafür entschieden, den Blogeintrag auf zwei Einträge aufzuteilen. Das Ziel ist immer noch dasselbe: Einbindung von Live-Tiles in unsere App!

image_thumb[2]

In der letzten Woche haben wir kurz vor der Implementierung des Agents unterbrochen und genau dort werden wir nun ansetzen! Wir steigen also direkt voll ein und wenden uns der OnInvoke-Methode des Agents zu, welche immer dann vom System aufgerufen wird, wenn der Service einmal ausgeführt werden soll (dies ist im Normalbetrieb etwa alle 30 Minuten der Fall).

Beim Aufruf müssen wir nun zum einen alle Initialisierungen vornehmen und zum anderen den Watcher starten, sodass wir bei der nächsten Positionsänderung benachrichtigt werden:

protected override void OnInvoke(ScheduledTask task)
{
  // Der ScheduledAgent verliert über die Aufrufe hinweg seinen Status, deshalb muss
  // er bei jedem Invoke neu initialisiert werden.
  Deployment.Current.Dispatcher.BeginInvoke(() =>
  {
    Initialize();
    watcher.Start();
  });
}

clip_image001 Exkurs: BeginInvoke. BeginInvoke führt Code auf dem Thread aus, welcher zur Erstellung des Handles benutzt wurde. Zugegeben, diese Beschreibung mag vielleicht etwas abstrakt wirken. Der Grund, wieso ein Invoke in diesem Fall notwendig ist, ergibt sich aus der Tatsache, dass wir BitmapImages in der Foto-Klasse verwenden und diese wiederum benötigen den UI-Thread als ausführende Instanz. Durch das BeginInvoke erzwingen wir (vereinfacht gesagt) ein Ausführen im richtigen Thread.

Wurde nun OnInvoke aufgerufen, müssen wir lediglich auf das Event des Watchers warten, damit wir die neue Position des Geräts auslesen können. Dazu müssen wir jedoch zuerst noch die Callback-Methode fertigimplementieren:

private void watcher_PositionChanged(object sender,
    GeoPositionChangedEventArgs<GeoCoordinate> e)
{
  // Wir stoppen den Watcher, um das Gerät nicht unnötig zu belasten
  watcher.Stop();
  foreach (Foto foto in fotos)
  {
    foto.updatePosition(e.Position.Location);        
  }
  // Die Sortierung ist wichtig, damit die Bilder, die uns am nächsten liegen, ganz
  // oben in der Liste auftauchen
  fotos.Sort();

  // Auch hier wieder der asynchrone Aufruf
  Deployment.Current.Dispatcher.BeginInvoke(() =>
  {
    // Schlussendlich aktualisieren wir das Live Tile der Anwendung mit der neuen Fotoliste
    TileUpdater.UpdateTile(fotos);
    // Diese Methode erlaubt es uns, die Intervalle des ScheduledAgents zu verkürzen
    // (für Debug-Zwecke)
    //ScheduledActionService.LaunchForTest("GeoFotoalbumLiveTileServiceAgent", TimeSpan.FromSeconds(30));

    // Wir teilen dem Service mit, dass dieser Aufruf des Agents beendet ist
    NotifyComplete();
  });
}

Wie bereits durch die Kommentare beschrieben, passiert hier folgendes: Zuerst stoppen wir den Watcher, denn dieser ist relativ Ressourcenhungrig und wir möchten gerade in Hintergrundberechnungen das Gerät so wenig wie möglich belasten. Danach aktualisieren wir die Fotos, damit wir jeweils die aktuellen Entfernungswerte vorliegen haben. Nun müssen wir die Liste noch sortieren und können dann auch schon die UpdateTile-Methode aufrufen, um das Live-Tile zu aktualisieren.

Schlussendlich teilen wir dem Gerät noch die erfolgreiche Beendigung unseres Serviceaufrufs mit.

clip_image002 Tipp: Wenn der GeoCoordinateWatcher in Background Services benutzt wird, greift er nicht auf die aktuelle Position des Geräts zurück, sondern auf eine bereits im Vorhinein Gespeicherte. Dieser Wert wird spätestens alle 15 Minuten durch das Gerät aktualisiert (um Ressourcen zu schonen).

Nun sind wir eigentlich fertig – wäre da nicht noch die TileUpdater Klasse, das Herzstück dieses Blogeintrags.

Wir fangen zuerst damit an, die StartService-Methode zu implementieren. Diese füllen wir mit folgendem Code:

// Einen etwaigen alten Task entfernen wir zuerst, bevor wir einen neuen erstellen
PeriodicTask periodicTask = ScheduledActionService.Find(periodicTaskName) as PeriodicTask;
if (periodicTask != null)
{
  try
  {
    ScheduledActionService.Remove(periodicTaskName);
  }
  catch
  {
    // Keine Aktion notwendig
  }
}
// Zuerst muss der Task erstellt werden
periodicTask = new PeriodicTask(periodicTaskName);
// Dann erhält er eine Beschreibung...
periodicTask.Description = "Dies ist der BackgroundTask der GeoFotoalbum-App.";
// ... und ein "Ablaufdatum"
periodicTask.ExpirationTime = DateTime.Now.AddDays(14);
try
{
  // Task hinzufügen (und somit aktivieren)
  ScheduledActionService.Add(periodicTask);
  // Diese Methode erlaubt es uns, die Intervalle des ScheduledAgents zu verkürzen (für Debug-Zwecke)
  //ScheduledActionService.LaunchForTest(periodicTaskName, TimeSpan.FromSeconds(30));
}
catch
{
  MessageBox.Show("Fehler: Der BackgroundTask konnte nicht erstellt werden!");
}

Wir löschen hierbei etwaige vorherige Tasks, die wir bereits erstellt haben und legen dann ein neues PeriodicTask-Objekt an. Dieses statten wir zunächst mit Name, Beschreibung und Ablaufdatum aus und fügen es dann dem Scheduler hinzu, damit unser Service auch fortan aufgerufen wird.

Der Taskname ist für Referenzierungszwecke notwendig und wird noch als konstante Membervariable etwas weiter oben in der Klasse angelegt:

private const string periodicTaskName = "GeoFotoalbumLiveTileServiceAgent";

Nun sollte unser Service bereits vollständig laufen und aufgerufen werden. Doch weder die App, noch der Service aktualisieren momentan das Live-Tile, weil uns noch das letzte Stückchen Code fehlt, welches wir nun der UpdateTile-Methode hinzufügen:

List<Uri> uris = new List<Uri>();

// Wir kopieren die drei nahesten Bilder in den Isolated Storage (ein Zugriff aus dem Live Tile auf
// die Medienbibliothek ist leider unmöglich)
for (int i = 0; (i < fotos.Count && i < 3); i++)
{
  using (IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication())
  {
    using (IsolatedStorageFileStream stream = file.OpenFile("Shared/ShellContent/" + 
      i + ".jpg", FileMode.Create))
    {
      WriteableBitmap bmp = new WriteableBitmap((BitmapSource)fotos[i].GetBild());
      bmp.SaveJpeg(stream, bmp.PixelWidth, bmp.PixelHeight, 0, 100);
    }
  }
  uris.Add(new Uri("isostore:/shared/ShellContent/" + i + ".jpg", UriKind.Absolute));
}

// Und statten unser Live Tile mit den aktualisierten URIs aus
ShellTile tile = ShellTile.ActiveTiles.First();
if (null != tile)
{
  CycleTileData cycleTile = new CycleTileData();
  cycleTile.Title = "GeoFotoAlbum";
  cycleTile.CycleImages = uris.ToArray();
  tile.Update(cycleTile);
}

Wir haben nun jedoch ein kleines Problem! Live-Tiles (insbesondere das CycleTile, welches wir verwenden), referenzieren ihre Daten über URIs. Das ist per se erst einmal kein Problem, wenn man nun jedoch bedenkt, dass wir die Medienbibliothek nur direkt und nicht über URIs ansprechen können, stellt sich plötzlich die Frage, wie wir dem Live-Tile mitteilen können, welche Fotos es darstellen soll. Unglücklicherweise gibt es hier wirklich keinen besonders eleganten Weg, weshalb wir etwas halbelegant die drei nahesten Fotos wählen und diese in den IsolatedStorage unserer App kopieren. Eine Referenzierung aus dem IsolatedStorage heraus ist dann überhaupt kein Problem mehr, vorausgesetzt man verwendet im Falle der Live-Tiles den korrekten Ordner („shared/ShellContent“).

clip_image001[1] Exkurs: IsolatedStorage. Beim IsolatedStorage handelt es sich um ein “Plätzchen” im Gerätespeicher, welches lediglich für unser App vorgesehen ist. Keine andere App hat auf diesen Bereich Zugriff!

Gerade weil wir die Fotos kopieren müssen, haben wir uns für das recht geringe Limit von maximal 3 Bildern pro Aufruf entschieden. Denn wie auch schon oben, möchten wir das Gerät so wenig wie möglich aus dem Hintergrund heraus belasten.

Wenn wir nun die URIs, welche in den IsolatedStorage zeigen, gesammelt haben, ist das Aktualisieren des Live-Tiles eigentlich fast schon trivial. Wir legen ein neues CycleTileData-Objekt an, füllen es mit den gewünschten Daten (in diesem Fall den URIs und einem Titel) und aktualisieren das erstbeste Live-Tile, welches wir uns aus der Liste der aktiven Live-Tiles extrahieren.

Bevor wir nun jedoch zum Ende kommen, fehlt uns noch eine Kleinigkeit. Unsere App weiß momentan noch nicht, dass wir gerne ein CycleTile hätten, und nicht etwa beispielsweise ein FlipTile. Die Art des Live-Tiles können wir in der WMAppManifest.xml-Datei im Feld „Tile Template“ festlegen. Wir wählen dort „TemplateCycle“.

Wenn nun alles korrekt funktioniert hat, sollten wir, vorausgesetzt der User hat das Live-Tile der App auf den Home Screen gepinnt, die geografisch nahesten Fotos in Form einer Slide-Show beobachten können! Smile

image_thumb[2]

 

Blogeinträge und Ressourcen

Am Ende dieses Blogeintrags verweisen wir noch einmal auf eine Liste der bisher veröffentlichten Blogeinträge:

und auf den Link zum Download der aktuellen Projektversion (beinhaltet den kompletten Stand der App bis zum Ende dieses Eintrags):

 

Zu guter Letzt

Ein (vorerst) letztes Mal bedanken wir uns für Eure Aufmerksamkeit und das Durchstehen dieses massiven Blogeintrags. Winking smile Wir hoffen die GeoFotoalbum-Serie hat euch bis hierhin gefallen! Falls es noch Fragen, oder vielleicht sogar Anregungen und Wünsche für eine neue Blogserie (oder ein neues Feature für unsere GeoFotoalbum-App) gibt, lasst es uns bitte in den Kommentaren wissen!

In diesem Sinne: Danke noch einmal für Eure Aufmerksamkeit und bis zum nächsten Mal! Smile

Comments (1) -

>

3/14/2013 9:09:33 AM #

Hallo Max,
ich wollte dir hier eben ein Lob aussprechen- deine Tutorials sind super verständlich. Habe mir heir schon so manchen Tipp speziell zu Windows 8 geholt ;) Freue mich auf die nächste Ausgabe!
VG aus Stuttgart
Ben
www.proctic.de/de/app_entwickler/app_entwicklung/

Ben Deutschland

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

Datenschutzhinweis: Sie stimmen durch "Kommentar speichern" der Speicherung Ihrer Angaben durch Microsoft Österreich für die Beantwortung der Anfrage zu. Sie erhalten dadurch keine unerwünschten Werbezusendungen. Ihre Emailadresse wird auf Ihren Wunsch dazu verwendet Sie über neue Kommentare zu informieren.

Microsoft respektiert den Datenschutz. Datenschutz & Cookies

Aktuelle Downloads

Azure Free Trial
 
Visual Studio Downloads
 
Windows Azure Free Trial

 
    Developer Events
Instagram
CodeFest.at on Facebook

Datenschutz & Cookies · Nutzungsbedingungen · Impressum · Markenzeichen
© 2013 Microsoft. Alle Rechte vorbehalten · BlogEngine.NET 2.7.0.0 · Diese Website wird für Microsoft von atwork gehostet.
powered by atwork