Every now and then we receive feedback on the performance and APK size of some NativeScript Android application, especially built with Angular 2. In this post I will try to elaborate a bit more on the technical challenges behind the Android Runtime, Core Modules and Angular 2 integration projects as well as to share some easy steps that would boost the performance several times.
Loading (Start-Up) Time
There are several tasks that happen while a NativeScript Android application loads:
- The NativeScript native library (*.so) is loaded and the V8 JS VM is initialized.
- The metadata files are extracted, read and their in-memory representation is built.
- The main application JS file is required. A require operation includes script parsing, compilation, execution and caching on the native side. The initial require graph is populated and cached.
Note: File extraction happens only upon the first application run after fresh installation. On next runs the files are read directly from the file system.
The first two steps are constant in term of execution time and are of no interest for optimization. Steps 3 and 4, however, are the main suspects for increased loading time. How do we optimize these? Meet the android-snapshot plugin.
What this snapshot plugin optimizes is:
- It provides a precompiled binary representation of all the framework JS files, including Angular and Core Modules.
- What gets inside the APK is a single binary file vs. 3500 separate files. Only the application logic - that is what resides within the “app” folder - gets packed as separate JS files. This saves a lot on the extraction time of step 3.
- The precompiled JS source saves a lot on step 4 - all Angular and Core Modules are preloaded in the V8 heap.
The only trade-off is that, due to snapshot files being CPU-dependent, application package size is increased with additional 5MB. Still, we have plans how to further improve this (read more in the Package Size section).
If you take a look at the Readme file on the snapshot repo you will see the approximate numbers of our tests. With snapshot enabled a blank Anular 2 application’s loading time is improved nearly twice - 4 seconds vs. 2 seconds.!
Synchronizing Two Garbage Collectors
All of this sounds like a perfect solution but unfortunately it leads to some performance issues when V8’s GC is unconditionally forced because it is a blocking operation running on the main UI thread. We are trying to call V8’s gc only upon main application loop idle but it seems this heuristic is not working as expected during navigation.
Application Package Size
As I explained in this blog post some time ago, the NativeScript Android Runtime ships with three separate builds for the three major CPU architectures available nowadays. This makes a blank Hello World application some 12MB big. If APK size is something that is critical for your clients then you may produce separate APKs for each CPU architecture your application needs to run on. For more information you may refer to the ABI splits section in the Publishing for Android help article.
How Do I Improve Performance Today?
Here is what you can do to improve the performance of your NativeScript Android application:
We have plans to further improve all of the above three major aspects of the overall performance in a NativeScript Android application:
- Enable the snapshot plugin by default for Android applications.
- Improve snapshot generation. Currently the plugin ships precompiled versions with all the Angular and Core Modules code. Instead, we can first use WebPack on the client machine, to skip all unnecessary code and then run the snapshot tool over the bundled result. This way we will be able to ship only small portion of today’s binary.
- Improve the default APK size (see this GitHub issue for more details). If ABI split is enabled apply it on the snapshot plugin as well.
Although NativeScript applications are 100% native and run fast and fluid in most of the cases, sometimes an application may need additional fine tuning, to become even faster. The NativeScript engineering team is actively working towards making all the above mentioned improvements enabled by default. As always, feedback is most welcome and much appreciated - please share it within the GitHub NativeScript and Android Runtime repositories.