W tym odcinku pierwsza porcja ciekawostek z kręgu koncepcji i architektury.
Profil .NET dla Metro jest wspierany w wieloplatformowych projektach Portable Class Library.
Z poziomu .NET w aplikacji Metro możemy użyć dwóch resource loaderów - jeden z Windows Runtime, a drugi typowy .NET. Tego drugiego należy używać tylko w kodzie przeznaczonym na wiele platform. W API WinRT możemy znaleźć odpowiedniki funkcjonalności znanych z menadżera zasobów .NET.
Moją uwagę zwróciła kontrolka RichEditBox oraz obsługa w XAML themes dla wysokiego kontrastu (w słowniku property ThemeDictionaries, zmienne kolory systemowe, alternatywne style i szablony).
W cyklu życia aplikacji uległa nieco zmianie lista zdarzeń powodujących aktywację. Dowiadujemy się, że w momencie przysłonięcia jednej aplikacji przez inną ta pierwsza działa jeszcze przez 10s, zanim zostanie uśpiona. Jeśli zostanie aktywowana później przez jeden z kontraktów, to najpierw wywoła się w niej zdarzenie resuming a potem activated. W przeciwieństwie do Windows Phone 7 nie ma oddzielnego zdarzenia informującego o zamykaniu aplikacji, przed zamknięciem jest ona usypiana, a następnie jej proces zostaje zakończony. Nie ma technicznej różnicy w zamykaniu aplikacji przez system i przez użytkownika. Przy aktywacji mamy dostęp do informacji czy aplikacja była zamknięta przez użytkownika czy przez system. Zamknięcie aplikacji w sposób programowy jest uważane przez system za błędne. Jeśli w aplikacji wystąpi błąd, przestaje być widoczna (można to było zaobserwować wielokrotnie zdaje się od wersji Developer Preview, ale nie było to wcześniej udokumentowane). Jeśli w naszej aplikacji wystąpi błąd, zostanie przesłany do naszego konta w Windows Store, gdzie mamy dostęp do takich informacji o naszych aplikacjach.
Katalog z aplikacjami Metro jest zabezpieczony i mniej zaawansowany użytkownik komputera nie będzie miał do niego dostępu. Z moich praktycznych doświadczeń można powiedzieć, że wystarczy Total Commander.
Dane dla roamingu są trzymane obecnie przez 30 dni. Jeśli nasze urządzenia nie będą używane, to dane zostaną w chmurze usunięte. Jeśli w tym okresie odinstalujemy aplikację i ponownie ją zainstalujemy, to zsynchronizuje się z danymi w chmurze.
W tym wydaniu otrzymujemy dużo praktycznych wskazówek, jak pisać wydajne aplikacje Metro w Java Script. W wersji Consumer Preview ważny jest tzw. bytecode caching i powinniśmy dopilnować, by “coś'” nie przeszkodziło w jego używaniu. Całkiem sporo pułapek dostarczają silniki przetwarzające Java Script, pamiętać też należy o różnych optymalizacjach i zachowaniach typowych tylko dla Windows 8.
W temacie przechowywania danych przez aplikację, wyłowiłem informację, że w aplikacjach Metro napisanych w HTML5, możemy korzystać z bazy IndexedDB kojarzonej z tym standardem.
Pod względem wydajnościowym w Windows 8 zalecane jest stosowanie H.264 czyli MPEG-4 oraz AAC i MP3. Chodzi tu o pełniejsze wsparcie sprzętowe dla tych formatów przez producentów.
Dla ListView w Java Script mamy wsparcie do napisania własnego wirtualizowanego źródła danych. Poprzez możliwość napisania własnej funkcji renderującej mamy też sporą kontrolę nad renderowaniem obiektów HTML z szablonu dla elementów, ich reużywaniem, zmianą w przypadku opóźnionego ładowania danych itd. Przeglądając tę ostatnią część mimowolnie porównywałem to z możliwościami Silverlight, w którym kiedyś w jednym zespole pisaliśmy podobne funkcjonalności (tzn. reużywanie i przyczepianie szablonów można kontrolować nadpisując pewne metody w kontrolce, ale wirtualizację danych trzeba było pisać samemu). Powstaje też tu pytanie jak z tymi możliwościami w C# dla aplikacji Metro w Windows 8, do tej pory nie rzuciło mi się to na oczy.
W przypadku kodu zarządzanego pułapek wydajnościowych może dostarczyć nadmierne odwoływanie się do komponentów Windows Runtime, gdzie w przypadku różnych języków są ponoszone koszty transformacji. Przy mierzeniu wydajności startu aplikacji .NET należy uwzględnić ich prekompilację do natywnego obrazu.
NET for Metro style apps overview
You can also create a Portable Class Library project to develop a .NET Framework library that can be used from a Metro style app. The project must include .NET for Metro style apps as one of the target platforms. The Portable Class Library is particularly useful when you want to develop classes that can be used from apps for different types of platforms, such as a Windows Phone app, desktop app, and Metro style app. See Portable Class Libraries.
Resources
In contrast, Metro style apps use a single resource file. This file is called a package resource index (PRI) file and stores resources for all languages, cultures, and scale factors.
Despite the difference of file extension, the .resw file format is identical to the .resx file format, except that .resw files may contain only strings and file paths. At compile time, all the .resw files for an app are packed into a single PRI file by the MakePRI utility and included with the app's deployment package.
Although the Resource File Generator (Resgen.exe) is primarily for use in desktop apps, you can also use this tool to decompile satellite assemblies into .resw files, which can then be compiled into a PRI file.
Although the System.Resources.ResourceManager class is included in the .NET APIs for Metro style apps, we do not recommend its use. Use ResourceManager only in libraries that are developed as Portable Class Library projects and that target multiple platforms.
In desktop apps, you use the NeutralResourcesLanguageAttribute attribute to define your app's neutral culture. In Metro style apps, this attribute is ignored when the PRI file is created and when the Windows ResourceLoader class is used to extract resources.
ResourceLoader rl = new ResourceLoader();
// Display greeting using the resources of the current culture.
string greeting = rl.GetString("Greeting");
// Display greeting using fr-FR resources.
ResourceContext ctx = new Windows.ApplicationModel.Resources.Core.ResourceContext();
ctx.Languages = new string[] { "fr-FR" } ;
ResourceMap rmap = ResourceManager.Current.MainResourceMap.GetSubtree("Resources");
string newGreeting = rmap.GetValue("Greeting", ctx).ToString();
C#, VB, and C++ programming concepts for Metro style apps
Routed events
The following is a list of input events that are routed events:
- KeyDown
- KeyUp
- GotFocus
- LostFocus
- PointerPressed
- PointerReleased
- PointerCanceled
- PointerEntered
- PointerExited
- PointerMoved
- PointerCaptureLost
- PointerWheelChanged
- Holding
- Tapped
- DoubleTapped
- RightTapped
- DragEnter
- DragLeave
- DragOver
- Drop
- ManipulationStarting
- ManipulationStarted
- ManipulationInertiaStarting
- ManipulationDelta
- ManipulationCompleted
Fundamentals
Making your app accessible
HTML/JS
Keyboard shortcuts
You should declare your shortcut keys in your app's HTML markup by using the accesskey and x-ms-acceleratorkey attributes.
<button accesskey="S">Save</button>
<!-- Ctrl+S invokes the Save button and is exposed by a tooltip. --><button id="sendButton" value="Send" title="Send (Ctrl+S)">Send</button>
XAML/C#
<Image Source="product.png"
AutomationProperties.Name="An image of a customer using the product."/>
<Image HorizontalAlignment="Left" Width="480" x:Name="img_MyPix"
Source="snoqualmie-NF.jpg"
AutomationProperties.LabeledBy="{Binding ElementName=caption_MyPix}"/>
<TextBlock Name="caption_MyPix">
Mount Snoqualmie Skiing
</TextBlock>
If a brief description is not enough to explain the control, you can set the AutomationProperties.HelpText attached property in addition to AutomationProperties.Name.
You can document access keys through screen readers by setting the AutomationProperties.AccessKey attached property to a string that describes the shortcut key. There is also an AutomationProperties.AcceleratorKey attached property for documenting non-mnemonic shortcut keys, although screen readers generally treat both properties the same way. In general, you should document shortcut keys in multiple ways, using tooltips, automation properties, and written help documentation.
<MediaElement x:Name="Movie" Source="sample.wmv"
AutoPlay="False" Width="320" Height="240"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button x:Name="Play" Margin="1,2"
ToolTipService.ToolTip="shortcut key: Alt+P"
AutomationProperties.AccessKey="Alt+P">
<TextBlock><Underline>P</Underline>lay</TextBlock>
</Button>
<Button x:Name="Pause" Margin="1,2"
ToolTipService.ToolTip="shortcut key: Alt+A"
AutomationProperties.AccessKey="Alt+A">
<TextBlock>P<Underline>a</Underline>use</TextBlock>
</Button>
<Button x:Name="Stop" Margin="1,2"
ToolTipService.ToolTip="shortcut key: Alt+S"
AutomationProperties.AccessKey="Alt+S">
<TextBlock><Underline>S</Underline>top</TextBlock>
</Button>
</StackPanel>
Localizing shortcut keys is relevant because the choice of key to use as the shortcut key typically depends on the visible text label for the element.
Making media content accessible
Captions
public void OnMarkerReached(object sender,
TimelineMarkerRoutedEventArgs e)
{
CaptionTextBlock.Text = e.Marker.Text;
}
private void AltAudioBtn_Click(object sender, RoutedEventArgs e
{
if (media.AudioStreamCount > 1)
{
if (media.AudioStreamIndex == 1)
{
media.AudioStreamIndex = 0;
(sender as Button).Content = "Play full-description audio";
} else {
media.AudioStreamIndex = 1;
(sender as Button).Content = "Play default audio";
}
} else
{
(sender as Control).IsEnabled = false;
}
}
Meeting requirements for accessible text
A Metro style app using C++, C#, or Visual Basic can use these default elements that are sometimes commonly referred to as text elements:
- TextBlock: role is Text
- TextBox: role is Edit
- RichTextBlock (and overflow class RichTextBlockOverflow): role is Text
- RichEditBox: role is Edit
A Metro style app using C++, C#, or Visual Basic supports high-contrast themes by default for all apps. When a user switches to a high-contrast theme from Windows 8 Consumer Preview system settings or accessibility tools, the framework automatically replaces all the colors and style settings in the Metro style app using C++, C#, or Visual Basic with the high-contrast look for controls and components in the UI.
This default support is based on using the default themes for the default controls. These themes make references to system colors, and the system colors themselves get redefined when the user switches to high contrast mode. However, if you use custom templates, themes and styles for your control, be careful that you do not disable the built-in support for high contrast. If you use Blend for styling, Blend will generate a separate high-contrast theme alongside the primary theme whenever you define a template that is significantly different from the default template. The separate theme dictionaries go into a dedicated property of a ResourceDictionary, ThemeDictionaries.
Application lifecycle
Apps can be activated as follows:
- cached file - The user wants to save a file that the app provides content management for.
- camera - The user wants to capture photos or video from an attached camera.
- contact picker - The user wants to pick contacts.
- device - The app handles AutoPlay.
- file - An app launched a file whose file type this app is registered to handle.
- file open picker - The user wants to picks files or folders that are provided by the app.
- file save picker - The user wants to save a file and selected the app.
- launch - The user launched the app or tapped a content tile.
- print task - The app handles print tasks.
- protocol - An app launched a URL whose protocol this app is registered to handle.
- search - The user wants to search with the app.
- share target - The app is a target for a share operation.
App visibility
When the user switches from your app to another app, your app is no longer visible but remains in the running state until Windows can suspend it (for about 10 seconds). If the user switches away from your app but activates or switches back to it before Windows can suspend it, the app remains in the running state.
Your app doesn't receive an activation event when app visibility changes, because the app is still running. Windows simply switches to and from the app as necessary. If your app needs to do something when the user switches away and back, it can handle the VisibilityChanged | msvisibilitychange event.
If a suspended app is activated to participate in an app contract or extension, it receives the Resuming | resuming event first, then the Activated | activated event.
Generally, users don't need to close apps, they can let Windows manage them. However, users can choose to close an app using the close gesture or by pressing Alt+F4. You can't include any UI in your app to enable the user to close your app, or it won't pass the Store certification process.
There's no special event to indicate that the user has closed an app. After an app has been closed by the user, it's suspended and terminated, entering the NotRunning state within about 10 seconds. If an app has registered an event handler for the Suspending | suspending event, it is called when the app is suspended.
You should decide how your app behaves when it's activated after being closed by the user. It may make no difference to you whether the app was terminated by Windows or by the user. If your app needs to do something different when it is closed by the user than when it is closed by Windows, the activation event handler can determine whether the app was terminated by the user or by Windows. See the descriptions of ClosedByUser and Terminated states in the docs for the ApplicationExecutionState enumeration.
Apps shouldn't close themselves programmatically unless absolutely necessary. For example, if an app detects a memory leak, it can close itself to ensure the security of the user's personal data. When you close an app programmatically, Windows treats this as an app crash.
App crash
Apps are required to follow the system crash experience, which is to simply return to the Start screen. The system crash experience is designed to get users back to what they were doing as quickly as possible, so you shouldn't provide a warning dialog or other notification because that'll cause a delay for the user. The disappearance of the app should make it clear to the user that something went wrong.
If your app crashes, stops responding, or generates an exception, Windows reports the error to Microsoft. Microsoft provides a subset of this error data to you so that you can use it to improve your app. You'll be able to see this data in your app's Quality page in your Dashboard in the Windows Dev Center for Metro style apps.
App install
Apps are installed under the %ProgramFiles%\WindowsApps directory. This directory is secured so that users can't easily access it.
App removal
When a user uninstalls your app, Windows removes the app only for that user. Application data for the app is also removed from the computer on a per-user basis. If multiple users have installed your app, all users must uninstall the app before it is removed from the computer.
Roaming application data
Roaming data for an app is available in the cloud as long as it is accessed by the user from some device within the required time interval. If the user does not run an app for longer than this time interval, its roaming data is removed from the cloud. If a user uninstalls an app, its roaming data isn't automatically removed from the cloud, it's preserved. If the user reinstalls the app within the time interval, the roaming data is synchronized from the cloud. The current policy specifies that this time interval is 30 days.
Temporary application data
The temporary app data store works like a cache. Its files do not roam and could be removed at any time. The user can clear files from the temporary data store using Disk Cleanup.
Performance
JS
Reducing your app's loading time
You can dramatically improve the loading time of an app by packaging its contents locally, such as JavaScript, CSS, images, and any other files important to the app. Disk operations are faster than network operations, such as a retrieving the file from a remote server. In addition, packaging your files locally provides other performance benefits, such as bytecode caching.
Bytecode caching
When a typical browser loads a JavaScript files, one of the first steps is to process the JavaScript into bytecode, a format of JavaScript that the browser can run. The browser does this for every JavaScript file, and this process increases the loading time for the files.
In contrast, when you install a Metro style app using JavaScript on the system, the system locates the JavaScript files in the package and converts them into bytecode. From that point on, the app uses the bytecode version of the JavaScript.
To ensure that your app benefits from bytecode caching:
- Ensure all JavaScript files are UTF8 encoded with a byte-order mark (BOM).
- Ensure all JavaScript files are statically referenced in the root of your HTML start page.
- Use static markup in the Start Page <script type="text/javascript" src='code.js'></script>
- Dynamically load JavaScript through code execution
var s = document.createElement(‘script');
s.src = ‘code.js';
document.head.appendChild(s);
The problem with loading JavaScript files dynamically is the system can't load the dynamic code until after the it loads and executes other code first—the code that performs the dynamic load.
We recommend dynamic loading only when:
- The app doesn't need the JavaScript file when it launches. It can load the file later.
- The app doesn't know the URI of the JavaScript file until after it launches, so the file can't be included statically.
- Declare inline JavaScript (not recommended) - it can't receive the same performance optimizations (such as bytecode caching) as JavaScript declared in its own file.
- Defer load JavaScript - the defer attribute informs the host process that the code located within this script is not vital to the app's initialization and can be loaded and executed at a later point. <script type="text/javascript" src='file1.js' defer='defer'></script>
- Reference JavaScript within the associated fragment
- Cache the app's fragments - You can use the WinJS.UI.Fragments.cache function to cache a fragment. After a fragment is cached, your app can display it quickly by using the renderCopy function. If the user interacts with the fragment right when your app is loaded or soon after, cache the fragment during activation or immediately after. If you don't explicitly cache a fragment, the app will cache it for you the first time it uses the fragment. There's no need to track which fragments you cached or when you should clear the fragment cache. The Windows Library for JavaScript maintains the cache for you. Więcej na Reducing your app's loading time.
- Don't duplicate references to Cascading Style Sheets (CSS) and JavaScript files in page fragments - reference common files in your start page.
Defer load JavaScript files when possible and use Fragments to divide your JavaScript into separate files that don't all have to be loaded right when your app launches.
Executing code
Two of the most important things to know about how your app executes code is that app code is event driven and all components share the same thread.
- Event driven
The Metro style app using JavaScript platform is event-driven. This means that your code executes only when it's triggered by an event (the only exception is that code may also be executed at parse time).
- Single threaded
The Metro style app using JavaScript platform is single-threaded. This means that all components of the platform run on the same thread, including both the UI and JavaScript code execution, and only one piece of code can execute at a time. The only way to run your code in another thread is to use web workers. In Windows 8 Consumer Preview, CPU cycles are taken away from background apps and given to the foreground app so that it can provide a more responsive UI.
Avoid running code that takes a long time to execute
- Use Web Workers to move execution to another thread
- Use msSetImmediate to execute code on the UI thread - use the msSetImmediate function to define a callback method that executes when the platform has finished processing all events and made display updates. Although the msSetImmediate function provides a way to execute code on the UI thread when no other events are waiting to be processed, it doesn't prevent new events from taking place while executing the callback. So, don't use the msSetImmediate function to execute expensive code. Use it only to perform quick operations without interfering with events that might have already been queued.
Cancel ongoing requests where appropriate
Some apps must perform a network request to get results for the user, and initiate the request when the event fires. But often the app overlooks the previous search request: if there was a previous search request, it's no longer needed and should be cancelled.
Optimizing your app's lifecycle
Use the correct initialization event
- DOMContentLoaded event
The DOMContentLoaded event is raised after the app loads all its JavaScript and CSS files. All the referenced files are ready to be accessed (including images, although they are not guaranteed to be loaded). At this point, you can begin initializing your app (app initialization doesn't usually require loaded images).
Tip Use the DOMContentLoaded event to begin general app initialization.
-
activated event
The activated event is raised when the app is activated. It tells the app whether it was activated because the user launched it or it was launched by some other means. Use the activated event handler to check the type of activation and respond appropriately to it, and to load any state needed for the activation.
- onload event
After the DOMContentLoaded event is raised and after all the images referenced by the page are loaded, the onload event is raised. If you need to process your images, this is where you do it.
Use the DOMContentLoaded event to perform global initialization of the activated event handler
(function () {
"use strict";
var app = WinJS.Application;
function initialize() {
// Set up global event handlers
// Initialize custom loading UI if necessary
}
function activatedHandler(e) {
if (e.detail.kind == Windows.ApplicationModel.Activation.ActivationKind.launch) {
// Check whether session state variables are valid.
// If so, retrieve the application data saved in the checkpoint handler
if (app.sessionState) {
// restore previous state from sessionState
}
// Check tile arguments and do any specific activation work
// related to those arguments.
// Then initialize all WinJS controls
WinJS.UI.processAll();
}
if (e.detail.kind ===
Windows.ApplicationModel.Activation.ActivationKind.filePicker) {
// Process the file picker request.
}
}
document.addEventListener("DOMContentLoaded", initialize, false);
app.addEventListener("activated", activatedHandler, false);
app.start();
})();
Suspend quickly and save memory
Writing JavaScript
In JavaScript there are no classes and objects are simply property bags (or dictionaries). You can add new properties to individual objects on the fly. You can even remove properties from existing objects. This flexibility used to come at a significant price in performance. Because objects can have any number of properties in any order, every property value retrieval required an expensive dictionary lookup. Modern JavaScript engines greatly speed up property access for certain common programming patterns by using an internal inferred type system that assigns a type (or map) to objects of the same property makeup.
- Add all properties in constructors
- Don't delete properties
- Use the same property order - When you create objects in different places, it's important to add properties in the same order. Otherwise, the objects won't share internal types, and property access is slow.
- Don't use default values on prototypes and other conditionally added properties - You can access properties defined on prototype objects just like you access properties defined on instance objects. It might be tempting to define default values for certain properties on prototypes. Defining default values can reduce memory consumption because the properties don't have to be replicated in every instance object. Unfortunately, objects defined this way receive different inferred types inside the JavaScript engine. As a result, accessing the properties of these objects is slow.
- Use a constructor for large objects - Maintaining the inferred type system costs resources, particularly for large objects that have many properties. Consequently, JavaScript engines typically impose a limit on the number of properties allowed on the object before the object becomes a property bag and provides slow property access. The property limit might be fairly low (such as just 12 or 16 properties). But the limit is relaxed (or lifted entirely) when all properties are added in the constructor.
function LargeObject() {
this.p01 = 0;
this.p02 = 0;
...
this.p31 = 0;
this.p32 = 0;
}
var largeObject = new LargeObject();
Methods added to instances as inner functions of the constructor become closures that capture the activation context of the constructor. Consequently, a new closure object must be allocated for every such method, every time a new object is constructed. The recommended style is to define methods on the object's prototype:
function Vector(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
Vector.prototype = {
magnitude: function () {
return Math.sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
},
normalize: function () {
var m = this.magnitude();
return new Vector(this.x / m, this.y / m, this.z / m);
}
}
Use integer arithmetic where possible
If your background is in traditional imperative programming languages, in which integer and floating point values have two distinctly different types, you might be surprised by JavaScript's uniform treatment of all numeric values. In JavaScript, all numeric values are of type Number, and all arithmetic operations must follow floating point semantics. This distinction doesn't have much impact if your code doesn't do much arithmetic.
Because integer arithmetic is far more common in most programs, modern JavaScript engines do their best to optimize for integer operations. Specifically, they perform two optimizations:
- They generate integer operations whenever possible (that is, whenever the result of a given operation is the same for an integer or floating point operation), because modern processors execute integer arithmetic much faster than floating point arithmetic.
- They represent integer values (in the commonly used range) in memory in such a way that don't require allocation on the heap (boxing), unlike any other JavaScript objects (including floating point numbers).
Explicitly tell the JavaScript runtime to use integer arithmetic, particularly when performing division. Since you can't convey this intent by using the int type (it doesn't exist in JavaScript), use the bitwise or operator instead:
Point.prototype.halve = function() {
this.x = (this.x / 2) | 0;
this.y = (this.y / 2) | 0;
}
Avoid floating point value boxing
The problem of floating point value boxing: any time a floating point number is assigned to a property of an object or an element of an array, it must first be boxed (allocated on the heap). If your program performs a lot of floating point math, boxing can be costly. You can't avoid boxing when working with object properties, but when operating on arrays, you can use typed arrays to avoid boxing.
var pixels = new Float32Array(100);
Move anonymous functions to global scope
Anonymous functions are closures that capture the activation context of the enclosing function. A new closure object must be allocated every time the enclosing function is called. Consequently, if the enclosing function is called often and the anonymous function is relatively small, the overhead of allocating a closure object can be significant.
Storing and retrieving state efficiently
Session state
The WinJS.Application.sessionState object is an in-memory data structure that is good for storing values that change often but need to be maintained even if the system terminates the process. It's automatically serialized to the file system when the app is suspended, and automatically reloaded when the app is reactivated. By using WinJS.Application.sessionState, you can reduce the number of expensive file operations your app performs.
Local settings
Use the local data store to store basic app settings, such as color and theme settings. This data store is written to disk (subject to disk caching) when a value changes, and we recommend that you don't use it for settings that are changed often. Use local settings for values that don't change often and must be preserved between app launches. For data that change frequently, use session state (the WinJS.Application.sessionState object) instead.
IndexedDB
IndexedDB provides Metro style apps using JavaScript with a way to easily store key-value pairs in a database. IndexedDB can handle large amounts of data. Use IndexedDB when you need key-value data management because using it is faster than an implementing your own system. Because using the indexedDB APIs initializes an entire database engine, don't use IndexedDB when you only need to store a few fields. To store smaller amounts of data, use session state or local storage
To access IndexedDB, use the msIndexedDB property.
eq = window.msIndexedDB.open( sDBName, nVersion );
The user wants to easily and quickly search through their contacts. Use IndexedDB to store contact info. IndexedDB is well suited for managing large amounts of data, and provides the quickest way to traverse the data.
Images of contacts can get large, so don't use session state or local settings. Instead, use either IndexedDB or local storage, which allow an app to serialize and retrieve binary data.
Accessing the file system efficiently
Use thumbnails for quick rendering
// Pick an image file
picker.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);
});
Batch requests for multiple file property reads - the Windows.Storage.BulkAccess APIs.
var library = Windows.Storage.KnownFolders.picturesLibrary;
var query = library.createFileQuery(Windows.Storage.Search.CommonFileQuery.orderByDate);
var delayLoad = true; // Depends on if/when/how fast you want your thumbnails
var access = new Windows.Storage.BulkAccess.FileInformationFactory(
query, Windows.Storage.FileProperties.ThumbnailMode.picturesView,
Math.max(app.settings.itemHeight, app.settings.itemWidth),
Windows.Storage.FileProperties.ThumbnailOptions.returnOnlyIfCached,
delayLoad);
access.getFilesAsync(start, page).then(function (files) {
var count = files.length;
for (var i = 0; i < count; i++) {
console.log(files[i].name + " - " + files[i].imageProperties.dateTaken);
// The more 'extra' properties you're accessing, the better the perf gains
}
});
Release media and streams when you're done
For example, if your app is using a Blob, be sure to call URL.revokeObjectURL to revoke the URL for the Blob which releases the reference to the underlying stream.
If your app is working with a RandomAccessStream or an IInputStream object, be sure to call the close method on the object when your app has finished using it, to release the underlying object.
Revoke all URLs created with URL.createObjectURL to avoid memory leaks
A common way to load media for an audio, video, or img element is to use the URL.createObjectURL method to create a URL it can use. When you use this method, it tells the system to keep an internal reference to your media (which might be a Blob, a File, or a StorageFile). The system uses this internal reference to stream the object to the appropriate element. But the system doesn't know when the data is need, so it keeps the internal reference until you tell it to release. It's easy to accidently retain unnecessary internal references, which can consume large amounts of memory.
There are two ways to release your objects:
- You can revoke the URL explicitly by calling the URL.revokeObjectURL method and passing it the URL.
- You can tell the system to automatically revoke the URL after it's used once.
If you are certain the URL will be read (by using it to set the src attribute of a media element), you can set the URL.createObjectURL method's second parameter, isReusable, to false if you want the system to automatically revoke the URL after the system finishes reading the object.
Note When using a non-reusable URL, make sure to use the URL to set the
src
attribute of an audio, video, or img element. Otherwise, the system won't release the internal reference.
Create media elements only when needed, and use a poster image
Don't create media elements, such as the video element, until you're about to use them. Creating media elements requires considerable CPU and memory overhead.
When creating a video element that doesn't play automatically, set the video element's poster attribute. If you don't set the poster attribute, the media engine has to start decoding the video to render the first frame to obtain a poster image.
<video src = "video.h264" poster = "poster.png"></video>
Managing layout efficiently
To render an app onscreen, the system must perform complex processing that applies the rules of HTML, CSS, and other specifications to the size and position of the elements in the DOM. This process is called a layout pass and can be very expensive.
Batch API calls that trigger a layout pass
Several APIs can trigger a layout pass. These include window.getComputedStyle, offsetHeight, offsetWidth, scrollLeft, and scrollTop. Because these APIs force a layout pass to take place, be careful how you use them in your app.
One way to reduce the number of layout passes is to batch API calls that trigger a layout pass.
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 will retrieve 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 will calculate layout
// again to take this change into account and render element to the screen
Audio and video performance
The Windows 8 specific extensions to the audio and video tags have feature-level and performance enhancements.
video {
position: fixed;
width: 100%;
height: 100%;
}
For the best system performance when the video is full-screen, the app should automatically hide all unused web elements, such as the transport controls. Otherwise, this could impact the optimized rendering process. For example, apps can use CSS styles to set the "visibility: hidden" or "display: none" on the transport controls. Alternatively, apps can use CSS z-index to place the video on top of all other elements.
You should avoid using other web elements to make your own letterboxes on the video. This disables some of the optimized rendering enhancements.
When in full-screen playback mode, apps should stop any animation in the background. Timers running in the background could unnecessarily wake up the CPU, even when the animation itself is not visible.
The msIsLayoutOptimalForPlayback property
The msIsLayoutOptimalForPlayback property is a read-only property that was introduced as a Windows 8 specific extension to the video tag. In a Metro style app using JavaScript, it indicates whether or not a video tag is in an optimized rendering path. The optimized code path allows for improved system performance, better data protection, and Stereo 3D video rendering. The optimized rendering path is not limited to full-screen video playback.
You should avoid the things in the following list.
-
Video elements with CSS outlines. This forces the video rendering to not use the optimized code path that is implemented in Windows 8.
-
Video elements rendered through a Canvas. This involves extensive memory copies in the rendering process.
-
Video elements embedded in Scalable Vector Graphics (SVG). This is similar to the Canvas case, and will invoke extensive memory copies.
-
Setting the msRealTime property on the video or audio tag to true. This causes the system to enter into a low-latency mode, which is desirable for communication scenarios but is less power-efficient.
-
Video playing in the background. When an app playing video is put into the background, the app should pause the video unless there is a specific reason for the video to continue playing in the background. This reduces the performance impact on the overall system.
Windows 8 specific extensions
Here is a short list of simple rendering options. They are implemented at the native media pipeline level and exposed as properties or methods on the video tag.
- msZoom: when set to true, crops all letterboxes or pillar boxes around the video.
- msSetVideoRectangle: selects a specific rectangular sub-region of the video to be rendered on the video tag. This can be used to zoom into a specific sub-region of the video.
- msHorizontalMirror: flips the video horizontally when set to true.
For more advanced or complicated video and audio Digital Signal Processing (DSP) operations, you should consider writing Microsoft Media Foundation (MF) based media plugins. These can perform better than DSP operations written directly using JavaScript.
Here are a few tips for writing DSP plugins.
-
For video DSP, you should consider using the graphics processing unit (GPU) (i.e. DX Shader code) and avoid software-based implementations as much as possible.
-
If you are writing multiple DSP filters, you should consider including them in a single Media Foundation Transform (MFT). This reduces the overhead of having two DSP MFTs chained together (although chaining is allowed in the Windows 8 platform).
Here are some considerations when you need to implement other media plugins, such as MF Media Sources or Decoders.
-
You should keep in mind that codecs for proprietary media formats can typically only operate in software mode. Therefore, they won’t be able to leverage hardware acceleration that is available to other standard media formats, such as H.264.
-
The plugin components should implement functions to handle Quality-Management (QM) messages. Then the overall media pipeline doesn’t have to take on tasks beyond the system capacity.
From a Windows 8 performance perspective, we’d like to recommend H.264 video as the primary video format and AAC and MP3 as the preferred audio formats. For local file playback, MP4 is the preferred file container for video content. H.264 decoding is accelerated through most recent graphics hardware. It is also worth mentioning that although hardware acceleration for VC-1 decoding is broadly available, for a large set of graphics hardware on the market, the acceleration is limited in many cases to a partial acceleration level (or IDCT level), rather than a full-steam level hardware offload (i.e. VLD mode). On the audio side, we expect hardware offload solutions will be available for AAC and MP3 on the upcoming Windows 8 SoC devices.
When including short, low-latency audio effects, for example in games, you should consider using WAV files with uncompressed PCM data to reduce processing overhead that is typical for compressed audio formats.
Tips for building transport controls with high-performing user experiences
How to make scrubbing in a custom control as efficient as with the native transport controls.
-
Set the playbackRate property of the video tag to "0" during scrubbing and reset it back to the pre-scrubbing value afterwards.
-
Make sure all pixels (or positions) on the slider count. One common mistake is to make apps that use sliders with only a hundred or so valid positions, even though there may be 1200 pixels on the slider.
Apps should avoid layout changes and use msTransform to update the web element positions. This invokes the optimized rendering path internally rather than recalculating the layout again.
You should make the transport controls automatically fade away once there is no user interaction for several seconds.
Subtitles support
The Windows 8 web platform provides basic subtitle functionality based on the <track> element defined in the World Wide Web Consortium (W3C) spec. We adopted Web Video Text Track (WebVTT) and SMPTE Timed Text (SMPTE-TT) as the formats natively supported in HTML5 apps. You should keep in mind the possible performance impact if you need to pick a different subtitle data format and handle the parsing of the rendering at the app level.
Animating
Metro style apps using JavaScript enable certain types of animations to be offloaded from the UI thread to a separate, hardware-accelerated system thread. This offloading creates smoother animations because it ensures that the animations are not blocked by the actions in the UI thread. This type of animation is called independent animation.
Use the Windows Animation Library
Use CSS3 transitions and animations
To create an animation that's not provided by the Animation Library, we recommend using CSS3 transitions and animations. Not all properties can be independently animated. You can use CSS3 transitions and animations to independently animate these properties:
- The translation, rotation, and skew of 2-D and 3-D transforms
- opacity
To animate the position of an element, always animate the translation of a transform applied to the element rather than directly animating the element's left and top Cascading Style Sheets (CSS) properties. Although both approaches achieve the same visual effect, the left and top properties can't be animated independently because changing them triggers a layout pass, and operations that trigger a layout pass can't be offloaded to the graphics processing unit (GPU).
Use infinite animations carefully
We recommend minimizing the use of infinite animations because they continually consume CPU resources and can prevent the CPU from going into a low power or idle state, potentially causing it to run out of power more quickly.
Handling user input
Use built in controls and views or CSS properties to pan and scale
We recommend using Windows Library for JavaScript and intrinsic HTML controls because they automatically follow the Windows 8 best-practices for touch interaction. The scrolling and zooming views provided by Windows 8 create highly responsive sliding and pinching.
To implement your own touch scroll and zoom behaviors in Metro style apps using JavaScript, use these Cascading Style Sheets (CSS) properties, DOM attributes, and DOM events (szczegóły na Handling user input)
For the best scrolling and zooming performance, use CSS properties. When you set these CSS properties, the platform optimizes performance and resource allocation by using separate threads to handle the touch input. You might not benefit from this optimization when you handle the MSPointer
or MSGesture events manually.
The basic DOM pointer events include mspointerdown, mspointermove, and mspointerup. You can also use MSGesture events to easily access the touch language.
Static gesture events
Manipulation gesture events
If your app needs to provide a quick, custom response immediately when the user touches an element, handle the mspointerdown event or the related manipulation gesture event.
Don't use an input event, such as MSPointerMove, to render UI
Don't animate your UI in time with input events. For example, if you're creating a paint app, don't draw to the canvas each time an mspointermove event is raised because it will degrade the app's performance.
To get smooth animations and rendering, an app needs to render only at 30 frames per second (fps), which is the speed most monitors can render at. But most input events happen quicker than 30 fps (touch events typically happen 60 times a second). If you render along with an input event, you're forcing the app to do more work than necessary.
Using ListView
There are a few places you can tweak to improve performance, including:
- Initialization – the time from when the control is created to items shown on screen
- Touch panning – the ability to pan the control by touch and having the UI keep up with the touch gestures
- Scrolling – the ability to use the mouse or keyboard to scroll the control and update the view
- Interaction for selecting, adding & deleting items
Synchronous data sources (Binding.List)
Working with asynchronous data sources
To create a custom data source, you need objects that implement the IListDataAdapter and IListDataSource interfaces. The Windows Library for JavaScript provides a VirtualizedDataSource object that implements IListDataSource—all you need to do is inherit from it and pass the base constructor an IListDataAdapter. The IListDataAdapter interacts directly with the data source to retrieve or update items. The IListDataSource connects to a control and manipulates the IListDataAdapter.
Optimize batches
The main way for the IListDataSource to retrieve items from the IListDataAdapter is to use these functions:
- Promise itemsFromIndex(index, countBefore, countAfter)
- Promise itemsFromKey(key, countBefore, countAfter)
The methods return a IListDataAdapter for an IFetchResult object that contains an array of records and additional metadata. If additional data is supplied, then it is usually cached by the IListDataSource for later use. If less data is supplied, then it passes the data it obtained back to the control (typically to render) and then makes subsequent requests for the data that it still needs.
Incremental count
ListView requires a count of the records in the data set so that it knows where to set the bounds for scrolling. It may not always be possible for the IListDataSource to have an accurate count of items, particularly if the count takes time to calculate. The IListDataAdapter can estimate and return an inaccurate count to the IListDataSource, and can then update that count over time by using one of these techniques:
- Specifying a new count as part of the response to requests for items
- Sending insert or delete notifications to signal that additional items have been discovered, or that the count is less than previously supplied.
It's better to increment the count rather than to reduce it over time. If the a request is made for records and the IListDataAdapter returns fewer records, with a lower count, then the IListDataSource will question where the records went, and will confirm the cached items are still valid by re-requesting them.
On-demand fields for records
Depending on your app, the records for items may have very large fields that can be supplied on demand.
Displaying items with a Template or render function
The ListView relies on a renderer that you supply to convert the data records into HTML for output. There are two ways of create a renderer: declare a WinJS.Binding.Template, or create a custom render function.
WinJS.Binding.Template
The template is simple to use, but doesn't provide much control over how items are rendered. The template has limited support for handling placeholders. Data binding is synchronous when the data is supplied. Using the WinJS.Binding.Template with a synchronous data source results in completely synchronous rendering.
The template uses simple logic to detect whether items can be recycled, but it errs on the side of creating new items if there is a chance that recycling an item will present the wrong UI. For apps that contain complex items, data is supplied asynchronously, or if performance is a real concern, we recommend using a custom render function for the template instead.
Custom render function
You can define custom render function for converting a data record into its HTML representation.
The render function takes these parameters:
- itemPromise: a IItemPromise for the data for the item to render. With a synchronous datasource, the IItemPromise is usually complete, but with an async datasource, it will complete at some time in the future.
- recycledElement : the DOM from a previous item that can be reused to display new content.
The render function must return an object that contains these properties:
element
: the root element of a DOM tree for the item, or a promise that when completed will return the root element for the item.renderComplete
: a Promise that completes when the item is fully rendered.
You call the render function once for each item.
function renderItem(itemPromise, recycledElement) {
// bare minimal placeholder work - create element with class to give it a color and size.
// and placeholder * { display: none; } or innerHTML = '' or removeChild() to hide the previous ui.
if (!recycledElement) {
recycledElement = document.createElement('div');
}
recycledElement.className = 'placeHolder';
var renderComplete = itemPromise.then(function (item) {
// Really quick paint of data.
// If your placeholder logic above didn't clear innerHTML you do it here.
// It would be good if it just did like 2 dom operations.
Utilities.removeClassName(recycledElement, 'placeHolder');
recycledElement.innerHTML = '<div class="title">' + item.data.title + '<div>';
return item.ready;
}).then(function (item) {
// This is where you do the more intense stuff
var imgEl = new Image();
// in css start img as opacity: 0;
recycledElement.appendChild(imgEl);
imgEl.addEventListener('load', function () { WinJS.UI.Animations.fadeIn(imgEl); }, false);
item.loadImage(item.data.someUrl, imgEl);
});
return { element: recycledElement, renderComplete: renderComplete };
}
Using a custom render function instead of a WinJS.Binding.Template provides several advantages:
-
You can use placeholders when data is not available - The HTML provided by the custom render function doesn't need to be static for the lifetime of the item, so the render function can set up callback functions that can update the HTML as data becomes available.
-
You can decide how to process the IItemPromise
-
If the render function uses a placeholder, it can register a handler for the IItemPromise that executes when the data is available. The renderer returns a placeholder immediately, and then uses the handler to update the HTML based on the data.
-
If you want the render function to delay sending a result, it can return a Promise for the HTML element and complete that promise once the IItemPromise is complete.
-
-
You can decide how to recycle elements
-
You can delay expensive work - For example, if an app enumerates a set of pictures, then retrieving the name of an image is faster than fetching the image thumbnail. When scrolling quickly, prioritize fetching the basic data for each item over fetching the image data. If the item is scrolled off screen before the image can be fetched, cancel the retrieval. Similarly, if the items contain complex structures, such as embedded controls, you might want to delay that work until all the basic state has been handled. For these cases, the IItemPromise provides a nested Promise called ready that doesn't completed until the basic rendering of other items is finished. This Promise can be chained and used to a signal that it's time to perform the more time-consuming work.
Variable sized items
The ListView can display items of different sizes in the same list. Differently-sized items must be integer multiples of a common size. For example, if the common size is 100 by 100 pixels, larger items might be 200 by 200 pixels, but not 150 by 200 pixels. The ListView uses the size of the common item to calculate the potential number of items to display. Therefore, if many of the items are larger than the base size, or the items have large multiples, the ListView will request more data than it really needs. The ListView also uses a packing algorithm when displaying items with different sizes. If the items have a range of sizes, it can take more time to compute item positioning, which makes layout passes take longer and degrades scrolling performance.
C#/VB.NET
Best practices for interoperability with Windows Runtime Components
Most of the Windows Runtime Components that ship with Windows 8 Consumer Preview are implemented in C++ so you cross interoperability boundaries when you use them from C# or Visual Basic.
Each time you access a property or call a method on a Windows Runtime Components, an interoperability cost is incurred. In fact, creating a Windows Runtime object is more costly than creating a .NET object. The reasons for this are that the Windows Runtime must execute code that transitions from your app's language to the component's language. Also, if you pass data to the component, the data must be converted between managed and unmanaged types.
Using Windows Runtime Components efficiently
Consider using .NET APIs for Metro style apps
There are certain cases where you can accomplish a task by using either Windows Runtime Components or the .NET APIs for Metro style apps. It is a good idea to try to not mix .NET types and Windows Runtime types. Try to stay in one or the other. For example, you can parse a stream of xml by using either the Windows.Data.Xml.Dom.XmlDocument type (a Windows Runtime type) or the System.Xml.XmlReader type (a .NET type). Use the API that is from the same technology as the stream. For example, if you read xml from a MemoryStream, use the System.Xml.XmlReader type, because both types are .NET types. If you read from a file, use the Windows.Data.Xml.Dom.XmlDocument type because the file APIs and XmlDocument are Windows Runtime components.
Copy Window Runtime objects to .NET types
If you call a Windows Runtime API that returns a collection and then you save and access that collection many times, it might be beneficial to copy the collection into a .NET collection and use the .NET version from then on.
Cache the results of calls to Windows Runtime Components for later use
Combine calls to Windows Runtime Components
Try to complete tasks with the fewest number of calls to Windows Runtime objects as possible.
Best practices for your app's startup performance
Measuring your app's startup time
It's a good idea to run the Native Image Generator (Ngen.exe) tool to precompile your app before you measure its startup time. By default, Windows 8 Consumer Preview automatically precompiles Metro style apps by running a scheduled task, so your users will get the benefit of your app running as quickly as possible. You should run Ngen.exe to get measurements that are representative of what the end user will experience.
Task Scheduler –> Task Scheduler Library –> Microsoft –> Windows: .NET Framework: .NET Framework NGEN 4.x, .NET Framework NGEN v4.x 64
Action –> Run
Ngen.exe precompiles all the apps on the machine that have been used and do not have native images. If there are a lot of apps that need to be precompiled, this can take a long time, but subsequent runs are much faster.
When you recompile your app, the native image is no longer used. Instead, the app is just-in-time compiled, which means that it is compiled as the app runs. You must rerun Ngen.exe to get a new native image.
Defer work as long as possible
To increase your app's startup time, do only the work that absolutely needs to be done to let the user start interacting with the app. This can be especially beneficial if you can delay loading additional assemblies. The common language runtime loads an assembly the first time it is used. If you can minimize the number of assemblies that are loaded, you might be able to improve your app's startup time and its memory consumption.
Brak komentarzy:
Prześlij komentarz