Georg fährt extra nach Berlin um Steve Ballmer zu treffen

31 Tage Mango | Tag #23: Das Ausführungsmodell

Die­ser Arti­kel ist Tag #23 der Serie 31 Tage Man­go von Jeff Blan­ken­burg.

Der Ori­gi­nal­ar­ti­kel befin­det sich hier: Day #23: Exe­cu­ti­on Model.

Die­ser Arti­kel wur­de in der Ori­gi­nal­se­rie von Gast­au­tor Sami­dip Basu geschrie­ben. Bei Twit­ter kann Sami­dip unter @samidip erreicht wer­den.

Sind wir ehr­lich — unse­re Smart­pho­nes kön­nen heut­zu­ta­ge ganz schön viel. Aber alle Emails, Inte­gra­ti­on von sozia­len Diens­ten, Spie­le und Anwen­dun­gen haben einen Preis: sie ver­brau­chen Kapa­zi­tät der ver­gleichs­wei­se klei­nen Pro­zes­so­ren und des Spei­chers und vor allem gehen sie zu Las­ten der Bat­te­rie­lauf­zeit. Es ist daher ent­schei­dend für jedes mobi­le Betriebs­sys­tem und 3rd Par­ty Anwen­dun­gen, ver­ant­wor­tungs­voll und scho­nend mit den Res­sour­cen des Smart­pho­nes umzu­ge­hen. Die Anwen­der sol­len ein flüs­si­ges Erleb­nis mit ihrem Tele­fon haben, das zudem noch eine Wei­le ohne Auf­la­den anhält. Hier kommt das Aus­füh­rungs­mo­dell ins Spiel. Für jeden Ent­wick­ler ist es wich­tig, die ver­schie­de­nen Zustän­de und Ereig­nis­se im Lebens­zy­klus einer Anwen­dung zu ken­nen und zu ver­ste­hen. In die­sem Arti­kel wer­den wir über das Aus­füh­rungs­mo­dell in Win­dows Pho­ne reden und uns ins­be­son­de­re anschau­en, was sich dar­an seit der Ver­öf­fent­li­chung von Man­go für die Anwen­der und für Ent­wick­ler geän­dert hat.

Der Windows Phone Weg

Wäh­rend wir den gan­zen Tag fröh­lich zwi­schen ver­schie­de­nen Tei­len des Win­dows Pho­ne Betriebs­sys­tems und 3rd Par­ty Anwen­dun­gen hin- und her­sprin­gen, voll­zieht das Tele­fon eine wohl­de­fi­nier­te Ver­wal­tung der Anwen­dun­gen vom Start bis zur Been­di­gung. Das Aus­füh­rungs­mo­dell erlaubt dabei prin­zi­pi­ell zu jeder Zeit nur die Aus­füh­rung einer Anwen­dung im Vor­der­grund. Wir wer­den zwar sehen, war­um das für das Bedien­erleb­nis vor­teil­haft ist, las­sen Sie mich den­noch kurz das Gegen­teil gedank­lich durch­spie­len. Selbst wenn es mög­lich wäre, kön­nen Sie sich wirk­lich vor­stel­len, dass meh­re­re Anwen­dun­gen gleich­zei­tig im Vor­der­grund auf einem Win­dows Pho­ne lau­fen? Ist der Platz auf dem Bild­schirm nicht etwas zu klein für so etwas? Die Beschrän­kung auf eine Anwen­dung im Vor­der­grund macht ein­fach Sinn für die Grö­ße des Bild­schirms und um dem Anwen­der ein Bedien­erleb­nis ohne über­flüs­si­ge Bedien­ele­men­te und ohne Ablen­kun­gen zu bie­ten. Dort wo mehr Platz auf dem Bild­schirm zur Ver­fü­gung steht, erlau­ben ähn­li­che Metro UI Betriebs­sys­te­me auch meh­re­re Anwen­dun­gen gleich­zei­tig im Vor­der­grund, wie bei den „Snap­ped Views“ in Win­dows 8. Aber ich schwei­fe ab…

Die Ent­schei­dung für immer nur eine Anwen­dung im Vor­der­grund hat natür­lich Aus­wir­kun­gen auf den Lebens­zy­klus einer Anwen­dung. Die meis­ten Win­dows Pho­ne Anwen­der sind es gewöhnt, schnell zwi­schen 3rd Par­ty Anwen­dun­gen und dem Betriebs­sys­tem selbst zu wech­seln, sei es durch die Start/Zurück But­tons, durch Tiles und Toast Noti­fi­ca­ti­ons oder sons­ti­ge Navi­ga­ti­ons­mög­lich­kei­ten. Jede Win­dows Pho­ne Anwen­dung durch­läuft dabei meh­re­re Zustän­de, die jeweils durch ent­spre­chen­de Events signa­li­siert wer­den. Details fol­gen.

Die Ausführungszustände

Im Wesent­li­chen durch­läuft eine Win­dows Pho­ne Anwen­dung die fol­gen­den Zustän­de:

  • Run­ning — in die­sem Zustand ist die Anwen­dung auf der Büh­ne. Das Betriebs­sys­tem und der Anwen­der wid­men der Anwen­dung die vol­le Auf­merk­sam­keit damit die Anwen­dung ihre Auf­ga­ben erfül­len kann.
  • Dor­mant — sobald die Anwen­dung die Büh­ne ver­lässt, wech­selt sie in den Zustand Dor­mant („schla­fend“). Die­ser Zustand ist neu in Win­dows Pho­ne Man­go. In die­sem Zustand wird die gan­ze Anwen­dung inklu­si­ve ihres inter­nen Zustan­des und des Back­stacks im Spei­cher gehal­ten. Das Ziel dabei ist, die Anwen­dung mög­lichst reak­tiv zu hal­ten, wenn der Benut­zer zur Anwen­dung zurück wech­selt. Die­ses schnel­le Zurück­wech­seln nen­nen wir in Win­dows Pho­ne Man­go Fast App­li­ca­ti­on Swit­ching (FAS).

Wol­len Sie das aus­pro­bie­ren? Sie brau­chen dafür natür­lich ein Gerät mit Man­go oder den aktu­ells­ten Emu­la­tor. Sprin­gen Sie hin und her zwi­schen ver­schie­de­nen 3rd Par­ty Anwen­dun­gen und den ver­schie­de­nen Tei­len des Betriebs­sys­tems und dann hal­ten Sie den Zurück But­ton gedrückt… Sie wer­den etwas wie den fol­gen­den Screen­shot sehen:

Was Sie hier sehen, ist der App­li­ca­ti­on Back­stack, also alle Anwen­dun­gen, die sich im Dor­mant („schla­fen­den“) Zustand befin­den. Die­se sind bereit, durch ein Tip­pen wie­der in den Vor­der­grund zu sprin­gen. Für den Anwen­der ist das Mul­ti-Tas­king. Für Sie als Ent­wick­ler ist es FAS… ein sehr schnel­les Auf­we­cken der App ohne dass Sie etwas spe­zi­el­les dafür tun müs­sen! Naja, abge­se­hen davon, dass Sie alte nicht-Man­go Anwen­dun­gen mit dem neu­es­ten Win­dows Pho­ne SDK 7.1 neu kom­pi­lie­ren müs­sen. Das hat sofort zwei Aus­wir­kun­gen — die alte „Res­uming…“ Nach­richt wird nicht mehr ange­zeigt, wenn die Anwen­dung aus dem Dor­mant Sta­te zurück­kommt und Sie sehen einen Screen­shot der Anwen­dung im App­li­ca­ti­on Back­stack. Wir wer­den gleich etwas Code dazu sehen.

  • Tomb­stoned — Wie wir gese­hen haben, hält die Win­dows Pho­ne Lauf­zeit­um­ge­bung einen Back­stack von den­je­ni­gen Anwen­dun­gen im Haupt­spei­cher, die kürz­lich im Vor­der­grund waren. Auch wenn die sofor­ti­ge Wie­der­her­stel­lung einer Anwen­dung natür­lich eine fei­ne Sache ist, hat jedes Smart­pho­ne nur einen begrenz­ten Haupt­spei­cher und das Betriebs­sys­tem braucht davon mög­li­cher­wei­se etwas um eine teu­re Ope­ra­ti­on durch­zu­füh­ren. Was pas­siert in die­sem Fall? Die Anwen­dun­gen wer­den aus dem Back­stack geschmis­sen (die „ältes­te“ zuerst)! Mit ande­ren Wor­ten wer­den sie ein­ge­fro­ren („tomb­stoned“) und ver­brau­chen nach die­sem Zeit­punkt kei­ne Sys­tem­res­sour­cen mehr. Die letz­te Behaup­tung ist tech­nisch gese­hen nicht ganz die Wahr­heit, wie wir gleich sehen wer­den.

Wie wirkt sich das Tomb­s­to­ning auf den Lebens­zy­klus einer Anwen­dung aus? Wenn der Benut­zer Ihre Anwen­dung geöff­net hat­te und dann mit ein paar res­sour­cen­hung­ri­gen Spie­len wei­ter­ge­macht hat, ist die Wahr­schein­lich­keit hoch, dass Ihre Anwen­dung ein­ge­fro­ren wur­de um Res­sour­cen frei­zu­ge­ben. Der Benut­zer wird sich jedoch nach einer Wei­le ent­schei­den, den Back­stack zurück­zu­ge­hen und Ihre Anwen­dung fort­zu­set­zen. Wie soll­te sich die Anwen­dung ver­hal­ten? Kön­nen wir uns die Din­ge mer­ken, die der Benut­zer zuletzt gemacht hat als er die Anwen­dung ver­las­sen hat?

Die Ant­wort ist ja! Wir kön­nen dem Benut­zer auf jeden Fall das Gefühl geben, dass er die Anwen­dung nie ver­las­sen hat und er dort wei­ter­ma­chen kann, wo er auf­ge­hört hat. Um die­sen Effekt zu erzie­len, müs­sen wir als Ent­wick­ler aber dafür sor­gen, dass der Zustand der Anwen­dung über ihren gesam­ten Lebens­zy­klus rich­tig ver­wal­tet wird. Im fol­gen­den Code wer­den wir sehen, wie wir den Zustand der Anwen­dung wie­der auf­tau­en kön­nen, nach­dem sie aus dem Tomb­stoned Modus zurück­kehrt. Der Anwen­der bekommt dadurch den Ein­druck, dass sie nie been­det wur­de. Die Wahr­heit ist, dass beim Ein­frie­ren der Anwen­dung ein paar Daten (in Form eines Dic­tion­a­ry) über den Zustand der Anwen­dung und der Back­stack der Sei­ten inner­halb der Anwen­dung vom Win­dows Pho­ne Betriebs­sys­tem im Spei­cher abge­legt wer­den.

  • Ter­mi­na­ted — Eine Anwen­dung in die­sem Zustand ver­braucht wirk­lich über­haupt kei­nen Haupt­spei­cher mehr. Nichts, auch kei­ne Zustands-Dic­tio­n­a­ries (über die wir spä­ter reden), wird auf­be­wahrt. Wird die Anwen­dung wie­der ver­wen­det, wird eine kom­plett neue Instanz erstellt. Eine Anwen­dung erreicht die­sen Zustand, nach­dem sie ein­ge­fro­ren wur­de oder der Benut­zer die Anwen­dung über den Zurück-But­ton ver­las­sen hat oder die App durch eine unbe­han­del­te Aus­nah­me been­det wur­de.

Seiten und Ereignisse

Bevor wir uns den Ereig­nis­sen im Lebens­zy­klus einer Anwen­dung zuwen­den, las­sen Sie uns noch mal einen Schritt zurück­tre­ten, damit wir sicher sind, dass die Grund­la­gen stim­men. Alle Win­dows Pho­ne Sil­ver­light Anwen­dun­gen bestehen aus einer Samm­lung von XAML Sei­ten, die inner­halb des Pho­ne App­li­ca­ti­on Frame gela­den wer­den. Solan­ge die Anwen­dung im Vor­der­grund ist, kann der Benut­zer frei zwi­schen den Sei­ten inner­halb der Anwen­dung navi­gie­ren. Durch Drü­cken des Back-But­tons tra­ver­siert der Benut­zer den Back­stack der Sei­ten inner­halb der Anwen­dung bis er die ers­te Sei­te der Anwen­dung erreicht. Drückt er jetzt noch mal den Back-But­ton, so wird die Anwen­dung been­det. Als Ent­wick­ler müs­sen wir für die­se Funk­tio­na­li­tät nichts machen, da das Win­dows Pho­ne Betriebs­sys­tem die­ses Ver­hal­ten wäh­rend der Lauf­zeit der Anwen­dung auto­ma­tisch bereit­stellt.

Den­ken Sie an fol­gen­de Situa­ti­on: der Benut­zer gibt etwas in einer Text­box auf einer XAML Sei­te ein, wird abge­lenkt und ver­lässt die Anwen­dung um etwas ande­res zu erle­di­gen. Wenn er zurück kommt, sind jetzt zwei Sze­na­ri­en denk­bar. Ers­tens, die Anwen­dung könn­te nur geschla­fen haben (dor­mant) und springt sofort wie­der in den Vor­der­grund. Die Benut­zer­ein­ga­ben blei­ben erhal­ten, ohne dass Ihre Anwen­dung dies extra unter­stüt­zen müss­te außer sicher­zu­stel­len, dass sie gera­de aus dem schla­fen­den Zustand auf­ge­weckt wur­de (und des­halb beim Start eben nichts zu tun ist). Zwei­tens, die Anwen­dung wur­de ein­ge­fro­ren. In die­sem Fall haben Sie ein klei­nes Pro­blem. Sie müs­sen die Anwen­dung wie­der zum Leben erwe­cken und den Zustand der XAML Sei­te wie­der auf­tau­en, so wie er war, als der Benut­zer die App ver­las­sen hat. Wir wer­den uns hier­zu ent­spre­chen­den Code anschau­en.

Wo wir bei den Zustän­den der Sei­ten sind: jetzt ist eine gute Gele­gen­heit, über zwei Ereig­nis­se im Lebens­zy­klus einer Sei­te zu reden:

  • OnNavi­ga­ted­To — Die­ser Event wird aus­ge­löst, wenn die Sei­te in den Vor­der­grund zur Anzei­ge gebracht wird. An die­ser Stel­le kön­nen wir als Ent­wick­ler an die Sei­te über­ge­be­ne Que­ry Strings aus­le­sen um die Con­trols auf der Sei­te wie­der ent­spre­chend mit Leben zu fül­len, falls wir aus dem Tomb­stoned Modus zurück kom­men.
  • OnNavi­ga­ted­From — Die­ser Event wird gefeu­ert, wenn der Anwen­der die Sei­te ver­lässt, um zu einer ande­ren Sei­te in der Anwen­dung zu wech­seln, oder wenn er die Anwen­dung kom­plett ver­lässt. In bei­den Fäl­len ist die­ser Event eine gute Gele­gen­heit, Zustand­s­in­for­ma­tio­nen zur aktu­el­len Sei­te zu spei­chern. Wir wer­den hier­für spä­ter Code­bei­spie­le sehen.

Daten und State Dictionaries

Las­sen Sie uns jetzt anschau­en, wel­che Daten wir sichern und wie­der auf­tau­en wol­len. Mei­ner Mei­nung nach las­sen sich die Daten einer Anwen­dung in zwei Arten ein­tei­len:

  • Ein­stel­lun­gen und per­ma­nen­te Daten — Die­se Art von Daten macht die Anwen­dung aus und muss über die ver­schie­de­nen Starts der Anwen­dung geret­tet wer­den. Die­se Daten soll­ten wir sofort im Iso­la­ted Sto­rage spei­chern, sei es als Name/Value Paa­re, als Dateien/Verzeichnisse oder über SQL CE falls die Daten rela­tio­nal sind.
  • Sei­ten und flüch­ti­ge Daten — Die­se Daten sind für den Anwen­der im Prin­zip ver­zicht­bar aber prak­tisch, wenn sie nicht ver­lo­ren gehen. Das kann rei­chen von nicht gespei­cher­ten Benut­zer­ein­ga­ben über irgend ein View­Mo­del bis hin zu tem­po­rä­ren Daten, die zum Bei­spiel her­un­ter­ge­la­den wur­den. Die­se Daten müs­sen wir acht­sam behan­deln, damit der Benut­zer das Gefühl einer leben­di­gen App erhält. Wir wer­den hier­bei jedoch nicht ganz allein gelas­sen. Das Win­dows Pho­ne Betriebs­sys­tem bie­tet jeder Anwen­dung und jeder Sei­te ein Dic­tion­a­ry, wel­ches ver­wen­det wer­den kann, um Key-Value-Paa­re zu seria­li­sie­ren. Die­ses Sta­te Dic­tion­a­ry wird vom Pho­neAp­p­li­ca­ti­on­Ser­vice ange­bo­ten und soll­te nur zur Spei­che­rung von flüch­ti­gen Zustands­da­ten ver­wen­det wer­den. Sie soll­ten die­se Eigen­schaft nicht für die Spei­che­rung gro­ßer Daten­men­gen ver­wen­den, da es eine Beschrän­kung auf 2 MB für jede Sei­te und 4 MB für die gesam­te Anwen­dung gibt. Wie wir im Code sehen wer­den, ist es den­noch hilf­reich, den Zustand im Lebens­zy­klus der Anwen­dung zu bewah­ren. Es sind genau die­se Dic­tio­n­a­ries, die vom Win­dows Pho­ne Betriebs­sys­tem per­sis­tiert wer­den — auch nach­dem die App ein­ge­fro­ren wur­de. Wenn die Anwen­dung nach dem Tomb­stone wie­der auf­ge­taut wird, wird das Zustands-Dic­tion­a­ry mit den gespei­cher­ten Daten wie­der gefüllt. Da die­se Daten im Haupt­spei­cher gehal­ten wer­den, kön­nen wir den Zustand ohne auf­wän­di­ge Datei­ope­ra­tio­nen wie­der­her­stel­len.

Anwendungsereignisse

Da wir jetzt die gan­zen Details und Abläu­fe ken­nen, las­sen Sie uns anschau­en, wann wir was im Lebens­zy­klus der Anwen­dung tun. Der Pho­neAp­p­li­ca­ti­on­Ser­vice feu­ert wäh­rend des Lebens­zy­klus‘ der Anwen­dung 4 Events, die uns erlau­ben, den Zustand zu ver­wal­ten:

  • Laun­ching — Die­ser Event wird gefeu­ert, wenn eine neue Instanz der Anwen­dung erstellt wird — sei es durch Start aus der Anwen­dungs­lis­te, durch Drü­cken auf eine Kachel oder durch eine Toast-Noti­fi­ca­ti­on oder durch wei­te­re Mög­lich­kei­ten. In die­sem Fall begin­nen wir mit einem fri­schen Zustand und nicht als Fort­set­zung einer vor­an­ge­gan­ge­nen Instanz der Anwen­dung.
  • Deac­tiva­ted — Die­ser Event wird gefeu­ert, wenn der Anwen­der von der Anwen­dung weg navi­giert oder ein Choo­ser auf­ge­ru­fen wird. Dies ist die bes­te Gele­gen­heit, den Zustand der Anwen­dung im Zustands-Dic­tion­a­ry für spä­te­re Ver­wen­dung beim Auf­we­cken zu spei­chern. Wenn die Anwen­dung aus dem schla­fen­den Zustand auf­ge­weckt wur­de, wäre dies nicht nötig. Es ist aber abso­lut not­wen­dig, wenn die Anwen­dung vom Tomb­stone auf­ge­taut wird und der Deac­tiva­ted Event ist unse­re ein­zi­ge Gele­gen­heit zur Spei­che­rung des Anwen­dungs­zu­stan­des.
  • Activa­ted — Die­ser Event wird gefeu­ert, wenn die Anwen­dung in den Vor­der­grund zurück­kehrt, ent­we­der aus dem schla­fen­den (dor­mant) oder aus dem ein­ge­fro­re­nen (tomb­stoned) Zustand. Ein ein­fa­ches Flag IsAp­p­li­ca­tionIn­stan­ce­P­re­ser­ved der Event Args zeigt uns an, aus wel­chem Zustand die Anwen­dung auf­ge­weckt wur­de. Wenn das Flag true ist, hat die Anwen­dung im Haupt­spei­cher geschla­fen. In die­sem Fall müs­sen wir nichts machen, da FAS die Arbeit für uns erle­digt. Wenn das Flag fal­se ist, keh­ren wir aus dem ein­ge­fro­re­nen Zustand zurück und ver­wen­den das Zustands-Dic­tion­a­ry zur Wie­der­her­stel­lung des Anwen­dungs­zu­stands.
  • Clo­sing — Die­ser Event wird gefeu­ert, wenn der Benut­zer den Back­stack tra­ver­siert und dabei an der ers­ten Sei­te der Anwen­dung vor­bei­kommt. Kei­ne Zustand­s­in­for­ma­tio­nen wer­den auf­be­wahrt und ein erneu­ter Start der Anwen­dung erzeugt eine fri­sche Instanz der Anwen­dung.

Alle Events haben gemein­sam, dass hier kei­ne auf­wän­di­gen, res­sour­cen­hung­ri­gen Auf­ga­ben durch­ge­führt wer­den soll­ten. Das Betriebs­sys­tem war­tet nur eine begrenz­te Zeit auf die Been­di­gung unse­rer Metho­den (10 Sekun­den um genau zu sein). Um die bes­te Benut­zer­er­fah­rung zu gewähr­leis­ten, soll­ten die teu­ren Ope­ra­tio­nen zur Bewah­rung des Zustands auf Hin­ter­grund­threads wäh­rend der Lauf­zeit der Anwen­dung aus­ge­führt wer­den.

War die gan­ze Infor­ma­ti­on oben etwas zu viel? Las­sen Sie uns einen Blick wer­fen auf ein Dia­gramm aus einem MSDN Arti­kel zum Aus­füh­rungs­mo­dell in Win­dows Pho­ne:

(Über­nom­men von MSDN)

Demoanwendung

Bis jetzt war das viel­leicht etwas tro­cken. Las­sen Sie uns ein wenig Code anschau­en. Wir bau­en eine klei­ne Win­dows Pho­ne Anwen­dung, wel­che die Zustands­ver­wal­tung über den Lebens­zy­klus der Anwen­dung demons­triert. Die gan­ze Anwen­dung steht unten zum Down­load zur Ver­fü­gung. Wenn Sie das Win­dows Pho­ne SDK 7.1 haben, brau­chen Sie nur F5 drü­cken!

So, las­sen Sie uns begin­nen. File > New und wir wäh­len gleich die ein­fachs­te mög­li­che Vor­la­ge mit nur einer XAML Sei­te MainPage.xaml/cs. Die­se öff­nen wir und fügen eini­ge Demo-Con­trols ein. Am Ende soll­te etwa fol­gen­des her­aus­kom­men (kli­cken zum Ver­grö­ßern):

Wie Sie im Screen­shot sehen, haben wir Con­trols für die unter­schied­li­chen Arten von Daten:

  • App Set­ting — das ist ein Bei­spiel für eine Anwen­dungs­ein­stel­lung, wel­che über ver­schie­de­ne Anwen­dungs­in­stan­zen hin­weg gesi­chert wer­den soll. Ein klas­si­sches Bei­spiel hier­für sind Ein­stel­lun­gen des Benut­zers.
  • App Tran­si­ent Data — Die­se Con­trols simu­lie­ren Daten auf Anwen­dungs­ebe­ne, die nur für eine Anwen­dungs­in­stanz rele­vant sind aber auf allen Sei­ten ver­wen­det wer­den sol­len. Ein Bei­spiel hier­für wären Daten eines Web­ser­vices, die die Anwen­dung bei jedem Start neu lädt.
  • Page Tran­si­ent Data — Die­se Con­trols ste­hen für Sei­ten-spe­zi­fi­sche Daten, die wir nicht ver­lie­ren möch­ten, wenn der Benut­zer die Anwen­dung ver­lässt und über den Back­stack zurück­kommt.

Wir wer­den jeden Typ von Daten sepa­rat behan­deln. Fan­gen wir an mit unse­rer App.xaml.cs — dem Ort für die Behand­lung glo­ba­ler Anwen­dungs­da­ten/-logik. Wir fügen eini­ge Eigen­schaf­ten und etwas Code im Kon­struk­tor hin­zu:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
   public partial class App : Application
   {
      public PhoneApplicationFrame RootFrame { get; private set; }

      public bool AppSetting { get; set; }
      public string AppData { get; set; }

      public static new App Current
      {
         get { return Application.Current as App; }
      }

      // Constructor.
      public App()
      {
         // All other initialization code ..
         AppData = string.Empty;

         if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
         {
            AppSetting = (bool)IsolatedStorageSettings.ApplicationSettings["AppSetting"];
         }
      }
   }
}

Als nächs­tes fügen wir etwas Code in den Event Hand­lern für den Lebens­zy­klus der Anwen­dung ein. Beach­ten Sie die Ver­wen­dung des Flags IsAp­p­li­ca­tionIn­stan­ce­P­re­ser­ved im Activa­ted Event Hand­ler, um zu sehen, ob die Anwen­dung aus dem schla­fen­den (dor­mant) oder aus dem ein­ge­fro­re­nen (tomb­stoned) Modus zurück­kehrt. Ich hat­te kei­ne Bedarf für spe­zi­el­len Code in den Launching/Closing Event Hand­lers — wenn Ihre Anwen­dung der­ar­ti­gen Code benö­tigt, kön­nen Sie hier natür­lich Code ein­tra­gen. Beach­ten Sie wei­ter­hin die Ver­wen­dung des Sta­te Dic­tio­n­a­ries des Pho­neAp­p­li­ca­ti­on­Ser­vice.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
   public partial class App : Application
   {
      public PhoneApplicationFrame RootFrame { get; private set; }

      public bool AppSetting { get; set; }
      public string AppData { get; set; }

      public static new App Current
      {
         get { return Application.Current as App; }
      }

      // Constructor.
      public App()
      {
         // All other initialization code ..
         AppData = string.Empty;

         if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
         {
            AppSetting = (bool)IsolatedStorageSettings.ApplicationSettings["AppSetting"];
         }
      }

      private void Application_Launching(object sender, LaunchingEventArgs e)
      {
      }

      private void Application_Activated(object sender, ActivatedEventArgs e)
      {
         if (e.IsApplicationInstancePreserved)
         {
            // Returning from Dormancy.
            // Do nothing.
         }
         else
         {
            // Returning from Tombstone.
            if (PhoneApplicationService.Current.State.ContainsKey("AppData"))
            {
               AppData = PhoneApplicationService.Current.State["AppData"].ToString();
            }
         }
      }

      private void Application_Deactivated(object sender, DeactivatedEventArgs e)
      {
         PhoneApplicationService.Current.State["AppData"] = AppData;
      }

      private void Application_Closing(object sender, ClosingEventArgs e)
      {
      }

      // Other auto-generated code here ..
   }
}

Zurück zum code-behind unse­rer MainPage.xaml. Wir fügen eini­ge Event Hand­ler ein: einen, wenn die Sei­te gela­den hat; einen, wenn der Anwen­der auf den But­ton drückt um eini­ge Daten zu „holen“ und einen, wenn sich der Zustand der Check­box der Anwen­dungs­ein­stel­lun­gen ändert. Wie Sie sehen, mer­ken wir uns die Bei­spiel­da­ten auf Anwen­dungs­ebe­ne und spei­chern die Anwen­dungs-/Be­nut­zer­ein­stel­lun­gen direkt im Iso­la­ted Sto­rage:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;

using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
   public partial class MainPage : PhoneApplicationPage
   {
      // Constructor.
      public MainPage()
      {
         InitializeComponent();
      }

      private void chkAppSetting_Tap(object sender, GestureEventArgs e)
      {
         // Store App Setting changes immediately in Isolated Storage for persistence.
         if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
         {
            IsolatedStorageSettings.ApplicationSettings.Remove("AppSetting");
         }

         if ((bool)this.chkAppSetting.IsChecked)
         {
            IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", true);
         }
         else
         {
            IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", false);
         }
      }

      private void Button_Click(object sender, RoutedEventArgs e)
      {
         this.txtAppData.Text = "This is some sample data ..";

         // As if we are caching the data.
         App.Current.AppData = this.txtAppData.Text;
      }

      private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
      {
         this.chkAppSetting.IsChecked = App.Current.AppSetting;
      }
   }
}

Jetzt feh­len noch die wich­ti­gen Events für die Sei­ten­na­vi­ga­ti­on. In die­sen regeln wir, wie wir die Con­trols mit flüch­ti­gen Daten bele­ben und uns die flüch­ti­gen Daten mer­ken, wenn die Sei­te in den Vor­der­grund wech­selt bzw. aus dem Vor­der­grund ver­schwin­det:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO.IsolatedStorage;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace Day_24___Execution_Model
{
   public partial class MainPage : PhoneApplicationPage
   {
      // Constructor.
      public MainPage()
      {
         InitializeComponent();
      }

      private void chkAppSetting_Tap(object sender, GestureEventArgs e)
      {
         // Store App Setting changes immediately in Isolated Storage for persistence.
         if (IsolatedStorageSettings.ApplicationSettings.Contains("AppSetting"))
         {
            IsolatedStorageSettings.ApplicationSettings.Remove("AppSetting");
         }

         if ((bool)this.chkAppSetting.IsChecked)
         {
            IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", true);
         }
         else
         {
            IsolatedStorageSettings.ApplicationSettings.Add("AppSetting", false);
         }
      }

      private void Button_Click(object sender, RoutedEventArgs e)
      {
         // Fake data fetch..
         this.txtAppData.Text = "This is some sample data ..";

         // As if we are caching the data.
         App.Current.AppData = this.txtAppData.Text;
      }

      private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
      {
         // Load user setting from Isolated Storage.
         this.chkAppSetting.IsChecked = App.Current.AppSetting;
      }

      protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
      {
         base.OnNavigatedTo(e);

         // Load in App Data, if available.
         if (App.Current.AppData != null && App.Current.AppData != string.Empty)
         {
            this.txtAppData.Text = App.Current.AppData;
         }

         // Load any Page State, if available.
         if (PhoneApplicationService.Current.State.ContainsKey("PageData"))
         {
            this.txtPageData.Text = PhoneApplicationService.Current.State["PageData"].ToString();
         }
         if (PhoneApplicationService.Current.State.ContainsKey("PageSetting"))
         {
            this.chkPageSetting.IsChecked = (bool)PhoneApplicationService.Current.State["PageSetting"];
         }
      }

      protected override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e)
      {
         base.OnNavigatedFrom(e);

         // Clear past state, if any.
         if (PhoneApplicationService.Current.State.ContainsKey("PageData"))
         {
            PhoneApplicationService.Current.State.Remove("PageData");
         }

         if (PhoneApplicationService.Current.State.ContainsKey("PageSetting"))
         {
            PhoneApplicationService.Current.State.Remove("PageSetting");
         }

         // Save off Page state.
         PhoneApplicationService.Current.State["PageData"] = this.txtPageData.Text;
         PhoneApplicationService.Current.State["PageSetting"] = this.chkPageSetting.IsChecked;
      }
   }
}

Das war’s! Wir haben jetzt genug Code hin­zu­ge­fügt um die ver­schie­de­nen Arten von Daten und Anwen­dungs­zu­stand über den Lebens­zy­klus der Anwen­dung jeweils auf ange­mes­se­ne Wei­se zu ver­wal­ten. Am bes­ten pro­bie­ren Sie die Anwen­dung mal aus. Star­ten Sie die Anwen­dung, machen Sie eine Ände­rung der Ein­stel­lung, gehen Sie woan­ders hin und kom­men zur Anwen­dung zurück oder been­den Sie die Anwen­dung und star­ten eine fri­sche Instanz. Die Daten und Zustand­s­in­for­ma­tio­nen soll­ten wie erwar­tet gespei­chert und gege­be­nen­falls zurück­ge­setzt wer­den. Eine typi­sche Aus­füh­rung mit der akti­vier­ten Ein­stel­lung soll­te bei­spiels­wei­se so aus­se­hen:

Wenn Sie so fröh­lich die App in den Vor­der­grund brin­gen und wie­der ver­las­sen, wer­den Sie sich viel­leicht fra­gen, wie Sie wis­sen kön­nen, ob die App in den ein­ge­fro­re­nen Zustand (tomb­stoned) über­ge­gan­gen ist. Wenn die App deak­ti­viert wird, geht sie in den meis­ten Fäl­len in den schla­fen­den (dor­mant) Zustand über und kommt direkt wie­der zum Leben wenn sie wie­der akti­viert wird. Auch wenn wir Code zur Wie­der­be­le­bung der Con­trols haben, falls die Anwen­dung aus dem Tomb­stoned Zustand kam, gibt es kei­ne zuver­läs­si­ge Mög­lich­keit, die­sen Fall zu tes­ten.

Sie müs­sen sich aber nicht den Kopf zer­bre­chen! Es gibt eine klei­ne Ein­stel­lung des Debug­gers, die Ihre Anwen­dung immer sofort ein­friert (also in den tomb­stoned Modus ver­setzt) sobald sie deak­ti­viert wird. Also auch wenn die App theo­re­tisch nur schla­fen gelegt wer­den könn­te, wird sie durch die­se Ein­stel­lung immer ein­ge­fro­ren. Das ist für unse­re Tests natür­lich sehr hilf­reich.

Zusammenfassung

Für das Ver­ständ­nis des Ablauf­mo­dells hilft es unge­mein, in jeden der ent­spre­chen­den Event Hand­lers einen Bre­ak­point zu set­zen und dann zu schau­en, wann und unter was für Umstän­den die ver­schie­de­nen Events feu­ern. Wenn Sie das alles ver­stan­den haben, steht Ihnen nichts mehr im Weg, eine Anwen­dung zu ent­wi­ckeln, die den Benut­zern stets leben­dig erschei­nen wird.

Ergeb­nis: zufrie­de­ne Anwen­der und letzt­end­lich mehr Zufrie­den­heit für Sie! Adi­os!

Um ein kom­plet­tes Win­dows Pho­ne Pro­jekt mit den Kon­zep­ten die­ses Arti­kels her­un­ter­zu­la­den, drü­cken Sie auf den Down­load Code But­ton.

Mor­gen wird Chris Koenig über ein nütz­li­ches Werk­zeug im Visu­al Stu­dio 2010 berich­ten: das Sil­ver­light Per­for­mance Ana­ly­sis Tool.

Bis dahin!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert