This topic presents a case study of improving an application to reduce its launch time. It describes the application launch process, techniques for reducing application loading time, and best practices for writing more efficient JavaScript.
Applications that offer a good user experience (UX) attract more users. Users who have positive impressions of an application return to it and are more likely to recommend it to others. If you monetize the application, this can result in greater profit.
Application launch time is the total time from the moment when the user clicks the application icon until the application is ready to use. Improving the application launch time improves the user experience. Because application loading is the first experience the user gets of your application, a long loading time can leave them with a negative impression. Therefore, it is worthwhile to prioritize reducing application launch time.
User satisfaction drastically decreases as the application launch time increases, as shown in the following figure based on a user survey. Using this information, a target launch time of around 5-7 seconds satisfies most users.
The application launch process can be divided into 5 stages. Some stages are controlled by the system, but most stages are dependent on application implementation, providing opportunities for performance improvement.
This case study is based on an application running on a 2015 Samsung Smart TV. Video was captured while launching the application on the TV, enabling the duration of each application launch stage to be measured with an accuracy of one video frame.
If you want to test your application in a similar way, make sure that all tests are performed under the same circumstances, such as using the same device with the same network conditions, to reduce the effects of external factors.
The following figure illustrates the application launch stages, and the time spent at each stage for the case study application.
The following table describes each stage in detail.
window.onload()
Table 1. Application launch stages
This section assumes that the case study application utilizes a framework. The framework contains modules responsible for activities, such as sending AJAX requests to the backend server, application authorization in the backend side, and modules responsible for the UI and logic.
The case study application loading process is illustrated in the following figure.
This model is not optimized because the entire framework must be loaded and initialized before the application can start the authorization and content loading processes.
A possible approach to speeding up the launch process is by rebuilding the application in the following way.
This flow is more optimized, as many tasks are performed in parallel. Initially, the application loads only the modules which are necessary for authorization and starts authorization immediately along with other operations, such as loading the rest of the framework, the UI, resources, and content. Any tasks not required for displaying the home page are postponed to start after the launch process. Consequently, application launch time is much shorter than before.
The following table lists optimization strategies that can improve application launch time, and the application launch stages they affect.
Table 2. Launch stages affected by optimizations
Tizen is more efficient than Samsung Legacy Platform. Migrating the application from Samsung Legacy Platform to Tizen can improve its performance significantly.
TV Web applications in Tizen support prelaunching. Applications can load in the background when the Samsung TV is switched on. When the user launches the application, it loads more quickly because it has been prelaunched.
For more information, see Prelaunching Applications.
Application launch time can be improved by loading and parsing less code at launch. When the application launches, load only the HTML, JS, and CSS code necessary to show the home page. Other code can be loaded on demand using a library, such as require.js, to avoid building and parsing the DOM before it is needed.
require.js
When loading external JavaScript files normally, the HTML parser stops while the JavaScript files are downloaded and executed.
To avoid the pause in HTML parsing during download, you can use the defer and async attributes:
defer
async
<script src='js/main.js' defer></script> <script src='js/scripts.js' async></script>
Using the defer attribute downloads the JavaScript file in parallel while parsing HTML. The JavaScript code executes only when all HTML parsing is complete. Scripts using the defer attribute are guaranteed to execute in the same order that they appear in the document.
Using the async attribute downloads the JavaScript file in parallel while parsing HTML, but when the download completes, the HTML parser pauses to execute the script.
The defer and async attributes can be used only for scripts implemented with the script element and src attribute.
script
src
For more information, see the HTML <script> element.
You can improve launch time by optimizing AJAX request usage:
sendRequest1 onSuccess: sendRequest2 onSuccess: sendRequest3 onSuccess: sendRequest4 onSuccess: sendRequest5
sendRequest1 onSuccess: sendRequest2 sendRequest3 sendRequest4 sendRequest5
Stages affected: 2, 4, and 5 Limit the number of AJAX requests by merging several requests, if possible. The fewer requests there are, the less time it takes to process all of them.
For example, consider an application that sends 3 requests to display the home page, such as for application authorization, a list of recommended movies, and a list of the latest movies. Verify whether it is possible to modify the requests and backend in such a way that the response for the authorization request contains the recommended and latest movies as well.
You can also use this strategy throughout the application to reduce the loading time of other scenes.
Many applications make early calls to Tizen and platform APIs for static information, such as the DUID or model code. Because API initialization is "lazy" (the API is initialized only when it is first needed), the first call to each API module takes time. Postpone API calls until the application is fully started and ready to use, if possible.
Instead of querying the API each time, cache the static output from Product and Tizen APIs in JavaScript variables.
Consider the following scenarios:
localStorage
Newer versions of jQuery are smaller and faster. You can also consider using a custom jQuery build.
Load static application resources directly from the local file system, by including all application source code, external libraries, and static UI elements, such as CSS and images, within the Tizen package:
<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css'> <link rel='stylesheet' href='https://example.com/myapp/css/styles.min.css'> <script src='http://code.jquery.com/jquery-2.0.0.min.js'></script> <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js'></script>
<link rel='stylesheet' href='css/bootstrap.min.css'> <link rel='stylesheet' href='css/styles.min.css'> <script src='js/jquery-2.0.0.min.js'></script> <script src='js/bootstrap.min.js'></script>
This not only decreases loading time, but can also improve application security and eliminate some application errors caused by network issues.
The application loads faster when it makes fewer requests to the file system. If you do not want to use a lazy loading mechanism to load parts of the application on demand, concatenate and minimize your source code, especially JavaScript and CSS files:
<!DOCTYPE HTML> <html lang='en'> <head> <meta name='viewport' content='width=1280, user-scalable=no'> <meta http-equiv='Content-Type' content='text/html; charset=utf-8'> <title>My application</title> <link rel='stylesheet' type='text/css' href='styles/main.css'> <link rel='stylesheet' type='text/css' href='styles/list.css'> <link rel='stylesheet' type='text/css' href='styles/player.css'> <link rel='stylesheet' type='text/css' href='styles/login.css'> <link rel='stylesheet' type='text/css' href='styles/account.css'> <script src='js/main.js'></script> <script src='js/list.js'></script> <script src='js/player.js'></script> <script src='js/login.js'></script> <script src='js/account.js'></script> </head> <body> <div id='Menu'></div> <div id='mainContainer'> <div id='helpbar'></div> </body> </html>
<!DOCTYPE HTML> <html lang='en'> <head> <meta name='viewport' content='width=1280, user-scalable=no'> <meta http-equiv='Content-Type' content='text/html; charset=utf-8'> <title>My application</title> <link rel='stylesheet' type='text/css' href='styles/all_styles.css'/> <script src='js/all_scripts.js' defer></script> </head> <body> <div id='Menu'></div> <div id='mainContainer'> <div id='helpbar'></div> </body> </html>
In some situations, it can be better to concatenate your code into 2 files instead of 1:
Make sure that the code loads in the correct order to satisfy its dependencies.
If the application loads any unused source code, whether it is JavaScript, CSS, or HTML, the browser takes time to parse it, build the DOM, and search the HTML code for matching CSS rules. Keep your code clean and up to date to avoid unnecessary performance issues caused by obsolete or redundant code.
For example, when the window.onload() method is called, calling the unregisterKey() method of the TVInputDevice API is unnecessary because no keys are registered other than the default "Left", "Up", "Right", "Down", "Enter", and "Back" keys.
unregisterKey()
If your application loads many small graphics, such as icons, button elements, and backgrounds, consider using CSS sprites. CSS sprites help images load faster by making fewer requests to the file system, which improves overall application performance.
You can merge all of the icons or background images into 1 large image and use CSS to set the positions for each element. Consider the following mouse hover implementation:
#myButton { width: 300px; height: 100px; background: url('img/button-normal.png') 0px 0px no-repeat; } #myButton:hover { background: url('img/button-hover.png') 0px 0px no-repeat; }
#myButton { width: 300px; height: 100px; background: url('img/button-sprite.png') 0px 0px no-repeat; } #myButton:hover { background: url('img/button-sprite.png') 0px -100px no-repeat; }
Tizen supports CSS3, which allows you to create many graphic effects without using image files. Pure CSS renders faster and takes less time to calculate its properties, which is especially valuable when animating CSS elements.
The following code creates a button using only pure CSS:
button { width: 180px; height: 62px; line-height: 62px; font-size: 24px; color: #fff; text-align: center; text-shadow: 1px 0 1px rgba(0, 0, 0, .75); background: -webkit-linear-gradient( top, #1e5799 0%, #2989d8 50%, #207cca 51%, #7db9e8 100% ); border: solid 2px #fff; border-radius: 12px; box-shadow: 0px 0px 15px 5px rgba(255, 255, 190, .75); }
Normally, the browser applies GPU acceleration when there is some indication that a DOM element can benefit from it.
CSS3 allows you to force the browser to render elements with GPU acceleration. One of the simplest methods is setting a style indicating 3D transform, even when no real 3D transformation is applied:
.acceleratedElem { -webkit-transform: translateZ(0); }
In general, 3D transformations in CSS force the browser to use the GPU.
The following declarations can reduce flickering during CSS transforms or animations:
.acceleratedElem { -webkit-backface-visibility: hidden; -webkit-perspective: 1000; }
For more information, see An Introduction to CSS 3-D Transforms and Increase Site Performance With Hardware-Accelerated CSS.
Follow a reliable and consistent coding standard and style for JavaScript, as it makes the code easier to write, easier to read, improves its maintainability, and makes it less error-prone.
The following section presents JavaScript best practices that are frequently used in the Web community.
Searching the DOM tree is a resource-hungry operation. If there are DOM elements on which you perform operations frequently, store them in JavaScript variables to avoid repetitive DOM traversal:
Prefer:
var elem1 = document.getElementById('elem1'); elem1.innerHTML = 'Lorem ipsum dolor'; elem1.classList.remove('class1'); elem1.classList.add('class2'); var $elem2 = $('#elem2'); $elem2.html('Lorem ipsum dolor sit amet'); $elem2.addClass('class3');
Avoid:
document.getElementById('elem1').innerHTML = 'Lorem ipsum dolor'; document.getElementById('elem1').classList.remove('class1'); document.getElementById('elem1').classList.add('class2'); $('#elem2').html('Lorem ipsum dolor sit amet'); $('#elem2').addClass('class3');
It can be helpful to store references to frequently-used DOM elements in a global object. For example:
var domElementsCache = { elDocument: window.document, elHtml: window.document.documentElement, elBody: window.document.body, elHead: window.document.head, elTitle: window.document.getElementById('title') }; domElementsCache.elTitle.innerHtml('My title');
jQuery selectors work slower than native ones. If application performance is critical, consider replacing jQuery selectors, for example:
//jQuery var elem = $('#myElem'); //native JS equivalent var elem = document.querySelector('#myElem');
//jQuery var elems = $('.class1'); //Native JS equivalent var elems = document.querySelectorAll('.class1');
//jQuery var elems = $('span'); //Native JS equivalent var elems = document.querySelectorAll('span');
$.find()
//jQuery var elems = $el.find('.target1, .target2, .target3'); //Native JS equivalent var elems = el.querySelectorAll('.target1, .target2, .target3');
Native JavaScript methods are faster than wrappers from different libraries. In some cases, it can be helpful to use them instead of library methods:
var myArray = [1, 2, 3]; //Underscore _.each(myArray, function(val) { console.log(val); }); //jQuery $.each(myArray, function(val) { console.log(val); }); //Native JS equivalent for (var i = 0; i < myArray.length; i++) { console.log(myArray[i]); }
Extensive logging decreases application performance, because each console.log() method call blocks the JavaScript thread in the browser. Other logging methods, such as the console.warn(), console.info(), console.error() methods, behave similarly. To improve application performance, remove log methods in the final build.
console.log()
console.warn()
console.info()
console.error()
You can use a logger utility to easily switch logging on and off. Open source libraries, such as Woodman, are available, or you can write your own logger, such as the following example:
// Global object for storing application configuration var applicationConfig = { debugMode: true //enables logs in application }; // Logger object factory var Logger = function() { var logger = {}; if (applicationConfig.debugMode === true) { logger = { log: function() { var args = Array.prototype.slice.call(arguments, 0); console.log(args); }, info: function() { var args = Array.prototype.slice.call(arguments, 0); console.info(args); }, warn: function() { var args = Array.prototype.slice.call(arguments, 0); console.warn(args); }, error: function() { var args = Array.prototype.slice.call(arguments, 0); console.error(args); } }; } else { logger = { log: function() {}, info: function() {}, warn: function() {}, error: function() {} }; } return logger; }; // USAGE: // For development and debugging var applicationConfig = { debugMode: true }; var myLog = new Logger(); // Initializes logger myLog.log('test'); // Outputs 'test' to console //For production var applicationConfig = { debugMode: false }; var myLog = new Logger(); // Initializes logger myLog.log('test'); // Does nothing
Never override console.log() or any other native function. Overriding native objects is a bad practice in JavaScript, as it can lead to unexpected behavior and introduce defects that are very hard to debug.
The principles described in this guide were applied to several Samsung TV applications, as listed in the following table.
Table 3. Optimizations applied to Samsung TV applications
The following table lists the launch time optimization results.
Table 4. Results of application launch time optimization
The average reduction in application launch time was almost 5 seconds, a 42% improvement. This result suggests that it is worthwhile to optimize applications, wherever possible.