Die Entwicklung von Windows 8
Windows Store-Blog für Entwickler
IEBlog – Deutsch
Visual Studio-Blog
Windows-Teamblogs
Blog „Inside Windows Live“
Windows 8 Release Preview herunterladen
Dev Center – Apps im Metro-Stil
Folgen Sie uns @windevs
//build/-Konferenz
Foren zu Windows-Apps im Metro-Stil
In meinem letzten Beitrag „So verbessern Sie die Leistung von Apps im Metro-Stil“ habe ich Methoden und Tools erläutert, die Ihnen beim Erstellen von schnellen und flüssigen Apps zur Verfügung stehen. Nun möchte ich mich eingehend mit verbreiteten Leistungshemmern beschäftigen, die mir in Apps aufgefallen sind. Ich werde die wichtigsten Orientierungspunkte behandeln, die meiner Erfahrung nach zu merklichen und messbaren Verbesserungen von Apps im Metro-Stil führen. Dies gilt sowohl für JavaScript- als auch für XAML-Apps. Außerdem stelle ich fünf spezifische Vorgehensweisen vor, die sich unabhängig von der verwendeten Sprache erwiesenermaßen positiv auswirken. Dafür sind weder raffinierte Tricks noch komplexe Arbeitsschritte erforderlich. Ich bin mir sicher, dass Sie mithilfe dieser Richtlinien die Leistung Ihrer App deutlich steigern können. Ich freue mich über Kommentare zu diesen Richtlinien und selbstverständlich auch über Ihre eigenen Tipps.
Verwenden Sie Inhaltspakete anstelle von Netzwerkinhalten.
Skalieren Sie lokal gespeicherte Bilder auf die richtige Größe.
Gewährleisten Sie eine kurze Startzeit der App.
Windows 8 kann auf unterschiedlichsten Geräten ausgeführt werden. Verwenden Sie Medieninhalte, die der Auflösung des jeweiligen Bildschirms angemessen sind.
Optimieren Sie das Reaktionsverhalten Ihrer Apps.
Beim Erstellen von JavaScript-Apps im Metro-Stil gelten auch weiterhin alle Tipps und Tricks, die Sie von der JavaScript-Programmierung für Websites kennen. Für Apps im Metro-Stil, die mit JavaScript erstellt wurden, gibt es jedoch drei wichtige, zusätzliche Methoden. Diese ermöglichen gemäß unserer Erfahrung besonders deutliche Leistungssteigerungen. Weitere Informationen finden Sie im Entwicklungscenter unter Best Practices für die Leistung von Apps im Metro-Stil mit JavaScript.
Das Dateisystem und die Mediendateien sind ein wichtiger Bestandteil der meisten Apps und eine häufige Ursache für Leistungsprobleme. Da Speicher- und CPU-Ressourcen zum Speichern, Dekodieren und Anzeigen der Medien erforderlich sind, kann dies den Zugriff auf Mediendateien verlangsamen.
Verwenden Sie die Miniaturansicht-APIs von Windows-Runtime, statt eine große Bildversion zu einer Miniaturansicht herunterzuskalieren. Ein großes Bild herunterzuskalieren ist eine ineffiziente Methode. Denn erst muss die App das Bild in voller Größe lesen und dekodieren und anschließend zusätzliche Zeit und Ressourcen für die Skalierung aufwenden. Windows-Runtime stellt eine Reihe von APIs zur Verfügung, die mithilfe eines effizienten Caches rasch eine kleinere Bildversion bereitstellen, die von Apps als Miniaturansicht verwendet werden kann.
In diesem Beispiel wird der Benutzer aufgefordert, ein Bild auszuwählen, das anschließend angezeigt wird.
// Pick an image filepicker.pickSingleFileAsync().then(function (file) { var imgTag = document.getElementById("imageTag"); imgTag.src = URL.createObjectURL(file, false);});
Dieses Beispiel ist gut geeignet, wenn das Bild in Originalgröße oder beinahe in Originalgröße gerendert werden soll. Es ist jedoch ineffizient, um eine Miniaturansicht des Bilds anzuzeigen. Die Miniaturansicht-APIs geben eine Miniaturansicht des Bilds zurück, die von der App wesentlich schneller dekodiert und abgebildet werden kann als das Bild in voller Größe. Das nächste Beispiel ruft mithilfe der getThumbnailAsync-Methode das Bild ab und erstellt auf dieser Grundlage eine Miniaturansicht.
// Pick an image filepicker.pickSingleFileAsync().then(function (file) { var properties = Windows.Storage.FileProperties.ThumbnailMode; return file.getThumbnailAsync(properties.singleItem, 1024);}).then(function (thumb) { var imgTag = document.getElementById("imageTag"); imgTag.src = URL.createObjectURL(thumb, false);});
Unsere Messungen zeigen, dass dieses Schema die Bildladezeit um bis zu 1000 % verbessern kann (d. h., bei der Verwendung der Miniaturansichts-API lädt das Bild bis zu zehnmal schneller, als wenn das Bild direkt von der Festplatte geladen und dann skaliert wird.)
In der Plattform für Apps im Metro-Stil auf Grundlage von JavaScript sind das DOM- und das JavaScript-Modul separate Komponenten. Jeder JavaScript-Vorgang, für den Kommunikation zwischen diesen beiden Komponenten erforderlich ist, benötigt im Vergleich zu Vorgängen, die ausschließlich innerhalb der JavaScript-Laufzeit ausgeführt werden können, deutlich mehr Ressourcen. Daher ist es wichtig, Interaktionen zwischen diesen Komponenten auf ein Minimum zu begrenzen. Beispielsweise kann das Abrufen oder Festlegen von Eigenschaften von DOM-Elementen recht aufwendig sein. In diesem Beispiel wird wiederholt auf die body-Eigenschaft und verschiedene weitere Eigenschaften zugegriffen.
// Don’t use: inefficient code. function calculateSum() { // Retrieve Values var lSide = document.body.all.lSide.value; var rSide = document.body.all.rSide.value; // Generate Result document.body.all.result.value = lSide + rSide;}function updateResultsStyle() { if (document.body.all.result.value > 10) { document.body.resultsDiv.class = "highlighted"; } else { document.body.resultsDiv.class = "normal"; }}
Das folgende Beispiel verbessert den Code, indem der Wert der DOM-Eigenschaften zwischengespeichert wird, anstatt auf diese wiederholt zuzugreifen.
function calculateSum() { // Retrieve Values var all = document.body.all; var lSide = all.lSide.value; var rSide = all.rSide.value; // Generate Result all.result.value = lSide + rSide;}function updateResultsStyle() { var body = document.body; var all = body.all; if (all.result.value > 10) { body.resultsDiv.class = "highlighted"; } else { body.resultsDiv.class = "normal"; }}
Verwenden Sie DOM-Objekte ausschließlich, um Informationen zu speichern, die einen unmittelbaren Einfluss darauf haben, wie durch das DOM Elemente angeordnet oder gezeichnet werden. Wenn die lSide- und rSide-Eigenschaften des vorhergehenden Beispiels ausschließlich Informationen über den internen Status der App speichern, sollten Sie diese nicht an ein DOM-Objekt anfügen. Im folgenden Beispiel wird der interne Status der App mithilfe reiner JavaScript-Objekte gespeichert. Hier werden DOM-Elemente nur dann aktualisiert, wenn der Bildschirm aktualisiert werden muss.
var state = { lValue: 0, rValue: 0, result: 0};function calculateSum() { state.result = lValue + rValue;}function updateResultsStyle() { var body = document.body; if (result > 10) { body.resultsDiv.class = "highlighted"; } else { body.resultsDiv.class = "normal"; }}
Unsere Messungen ergeben, dass sich alleine durch den Zugriff auf Daten im DOM eine Verlängerung der Zugriffszeit von bis zu 700 % gegenüber dem Zugriff auf Daten ergeben kann, die nicht an das DOM gebunden sind.
Um eine App auf dem Bildschirm zu rendern, muss durch das System eine komplexe Verarbeitung durchgeführt werden, in der die in HTML, im CSS und in anderen Spezifikationen festgelegten Regeln in Bezug auf Größe und Position der Elemente im DOM angewendet werden. Dieser Prozess wird als ein Layoutdurchlauf bezeichnet und kann sehr aufwendig werden.
APIs, die einen Layoutdurchlauf auslösen, sind z. B.:
Ein Layoutdurchlauf ist Bestandteil des Aufrufs dieser APIs, wenn nach dem Abrufen der Layoutinformationen eine Änderung stattfindet, durch die das Layout beeinflusst wird. Eine Möglichkeit, die Anzahl der Layoutdurchläufe zu verringern, besteht in der Stapelverarbeitung der API-Aufrufe, die einen Layoutdurchlauf auslösen. Um das entsprechende Vorgehen zu verstehen, betrachten wir den folgenden Codeausschnitt. In den beiden Beispielen werden die Werte eines Elements für offsetHeight und offsetWidth um 5 verändert. Werfen Sie zunächst einen Blick auf einen üblichen jedoch ineffektiven Weg zur Veränderung von offsetHeight und offsetWidth:
// Don't use: inefficient code.function updatePosition(){ // Calculate the layout of this element and retrieve its offsetHeight var oH = e.offsetHeight; // Set this element's offsetHeight to a new value e.offsetHeight = oH + 5; // Calculate the layout of this element again because it was changed by the // previous line, and then retrieve its offsetWidth var oW = e.offsetWidth; // Set this element's offsetWidth to a new value e.offsetWidth = oW + 5; }// At some later point the Web platform calculates layout again to take this change into account and render the element to the screen
Im vorhergehenden Beispiel wurden drei Layoutdurchläufe ausgelöst. Betrachteten wir nun eine bessere Methode, um das gleiche Resultat zu erzielen:
function updatePosition() { // Calculate the layout of this element and retrieve its offsetHeight var height = e.offsetHeight + 5; // Because the previous line already did the layout calculation and no fields were changed, this line retrieves the offsetWidth from the previous line var width = e.offsetWidth + 5; //set this element's offsetWidth to a new value e.offsetWidth = height; //set this element's offsetHeight to a new value e.offsetHeight = width;}// At some later point the system calculates layout again to take this change into account and render element to the screen
Obwohl sich das zweite Beispiel nur geringfügig vom ersten unterscheidet, werden dort lediglich zwei anstatt drei Layoutdurchläufe ausgelöst – eine Verbesserung um 33 %. Die Auswirkung auf die Leistung ist von der Größe und Komplexität des DOM und der damit verbundenen Stile abhängig. Diese Richtlinie ist um so bedeutender, je umfangreicher die Benutzeroberfläche Ihrer App gestaltet ist.
Gut strukturiertes XAML ist in vielen wichtigen Szenarien von Vorteil, einschließlich Aktivierungszeit, Seitennavigation und Speicherauslastung. Hier finden Sie verschiedene Hinweise für die Optimierung von XAML in Ihrer App.
Das Analysieren von XAML und die Erstellung entsprechender Objekte im Speicher kann bei komplexen Benutzeroberflächen mit tiefer Elementstruktur zeitaufwendig sein. Daher empfehlen wir, lediglich das für den Startvorgang erforderliche XAML zu laden. Am einfachsten ist es, nur die Seiten zu laden, die für die erste Anzeige erforderlich sind. Untersuchen Sie das XAML der ersten Seite sorgfältig, um sicherzustellen, dass alle erforderlichen Komponenten vorhanden sind. Wenn Sie einen Verweis auf Steuerelemente oder Stile verwenden, die an einer anderen Stelle definiert sind, wird die entsprechende Datei durch das Framework ebenfalls analysiert.
<!--This is the first page an app displays. A resource used by this page, TextColor, is defined in AppStyles.xaml which means that file must be parsed when this page is loaded. Because AppStyles.xaml contains many app-wide resources, all of these resources need to be parsed even though they aren’t necessary to start the app. --><Page ...> <Grid> <TextBox Foreground="{StaticResource TextColor}" /> </Grid></Page>Contents of AppStyles.xaml<ResourceDictionary …> <SolidColorBrush x:Key="TextColor" Color="#FF3F42CC"/> <!--many other resources used across the app and not necessarily for startup.--></ResourceDictionary>
Reduzieren Sie Ressourcenverzeichnisse. Speichern Sie Ressourcen, die in der gesamten App verwendet werden, zur Vermeidung von Duplizierungen im Application-Objekt. Verschieben Sie jedoch die Ressourcen, die spezifisch für einzelne Seiten sind, in das Ressourcenverzeichnis der entsprechenden Seite. Dies verringert beim Start der App den Umfang an zu analysierendem XAML. Die entsprechenden XAML-Komponenten müssen nur dann analysiert werden, wenn ein Benutzer diese spezifische Seite aufruft.
<!--Bad: XAML which is specific to a page should not be included in the App’s resource dictionary. The app incurs the cost of parsing resources that are not immediately necessary at startup, instead of parsing on demand. These page specific resources should be moved to the resource dictionary for that page.--><Application …> <Application.Resources> <SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/> <SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/> <SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/> <SolidColorBrush x:Key="ThirdPageTextColor" Color="#FF3F42CC"/> </Application.Resources></Application>XAML for the home page of the app<Page ...> <StackPanel> <TextBox Foreground="{StaticResource HomePageTextColor}" /> </StackPanel></Page>XAML for the second page of the app<Page ...> <StackPanel> <Button Content="Submit" Foreground="{StaticResource SecondPageColor}" /> </StackPanel></Page>
<!--Good: All page specific XAML has been moved to the resource dictionary for the page on which it’s used. The home page specific XAML was not moved because it must be parsed at start up anyway. Moving it to the resource dictionary of the home page wouldn’t be bad either.--><Application …> <Application.Resources> <SolidColorBrush x:Key="DefaultAppTextColor" Color="#FF3F42CC"/> <SolidColorBrush x:Key="HomePageTextColor" Color="#FF3F42CC"/> </Application.Resources></Application>XAML for the home page of the app<Page ...> <StackPanel> <TextBox Foreground="{StaticResource HomePageTextColor}" /> </StackPanel></Page>XAML for the second page of the app<Page ...> <Page.Resources> <SolidColorBrush x:Key="SecondPageTextColor" Color="#FF3F42CC"/> </Page.Resources> <StackPanel> <Button Content="Submit" Foreground="{StaticResource SecondPageTextColor}" /> </StackPanel></Page>
Das XAML-Framework ist darauf ausgelegt, zahlreiche Objekte anzuzeigen. Wenn Sie jedoch die Anzahl der Elemente auf einer Seite verringern, können Sie Layout der App und das Szenenrendering weiter beschleunigen. Mit verschiedenen Tricks können Sie unter Beibehaltung der visuellen Komplexität die Anzahl der Elemente einer Szene verringern.
<!--Bad XAML uses an unnecessary Rectangle to give the Grid a black background--> <Grid> <Rectangle Fill="Black"/> </Grid> <!--Good XAML uses the Grid’s background property--><Grid Background="Black" />
<!--Bad XAML uses an unnecessary Rectangle to give the Grid a black background--> <Grid> <Rectangle Fill="Black"/> </Grid>
<!--Good XAML uses the Grid’s background property--><Grid Background="Black" />
Reduzieren Sie Elemente, die blockiert oder transparent und somit unsichtbar sind.
Wenn das gleiche sektorbasierte Element mehrfach verwendet wird, sollten Sie die Umwandlung in ein Bild in Betracht ziehen. Der Speicher für ein Bild wird nur einmal pro Bild-URI zugewiesen. Im Gegensatz dazu wird für vektorbasierte Elemente jedes Mal eine gesonderte Instanz erstellt.
Animationen können nach der Erstellung von Anfang bis Ende berechnet werden. Manchmal sind durch die Änderungen einer animierten Eigenschaft die übrigen Objekte einer Szene nicht betroffen. Diese werden als unabhängige Animationen bezeichnet und im Kompositions-Thread anstelle des UI-Threads ausgeführt. Dadurch wird deren reibungsloser Ablauf gewährleistet, da der Kompositions-Thread in einem konstanten Intervall aktualisiert wird. Die folgenden Animationen sind alle garantiert unabhängig:
Abhängige Animationen beeinflussen das Layout und können ohne eine zusätzliche Eingabe vom UI-Thread nicht berechnet werden. Diese Animationen umfassen Änderungen an Eigenschaften wie z. B. Breite und Höhe. Standardmäßig werden abhängige Animationen nicht ausgeführt und erfordern ein Abonnement vom App-Entwickler. Die Animationen werden bei Aktivierung reibungslos ausgeführt, wenn der UI-Thread nicht blockiert wird. Allerdings treten bei einer anderweitig hohen Auslastung des Frameworks oder der App Unterbrechungen auf.
Das XAML-Framework kann nun fast alle Animationen standardmäßig unabhängig gestalten. Es stehen allerdings einige Aktionen zur Verfügung, um diese Optimierung zu deaktivieren. Gehen Sie bei folgenden Schritten mit äußerster Vorsicht vor:
Die Erstellung von schnellen und flüssigen Apps, die reichhaltig und komplex sind, erfordert hohe Fertigkeit. Ich habe Ihnen verschiedene Aspekte als Denkanstoß vorgestellt. Allerdings ist ein einziges Tool oder eine einzige Methode selten der Schlüssel zu guter Leistung. Meist handelt es sich um die Kombination bewährter Verfahren, vor allem, wenn Sie bereits in der Frühphase der Entwicklung Leistungsfragen berücksichtigen. Hoffentlich unterstützt Sie dieser Blog bei der Erfüllung der hohen Kundenerwartungen.
Viel Spaß (und viel Erfolg) beim Erstellen effizienter Anwendungen!
– Dave Tepper, Programmmanager, Windows