According to the Cambridge dictionary, one of the acceptations of performance is "How well a person, machine, etc. does a piece of work or an activity." If we put it together with "high" we can define it as the output or efficiency with which a task is being done.
High performance in software refers to the strategies that developers adopt to create pieces of software that can perform a process efficiently. When we are developing mobile software, this affects, but is not limited to, layout development, energy and battery management, security concerns, efficient multithreading, programming patterns, and debugging techniques.
There is a big difference between doing things, and doing things right. In a real world with deadlines, budgets, and managers, software engineers fall very often into acquiring technical debt. A technical debt is "bought " (if we can use that verb) when a system is developed without a complete or proper design, moving problems forward instead of correctly addressing them. This has a snowball effect: at an advanced stage, the technical debt is so high that further development is very costly, and this leads to a dead point or astronomical damage to budgets in organizations.
While deadlines cannot always be avoided, adopting an efficient development process in any software development is vital to delivering good quality at a reasonable cost. It also means that the development skills become more mature in a developer, and instead of achieving tasks that fulfill the requirements, engineers can develop software that is efficient, robust, and that can be further extended in the future (what we called "maintainability").
This book introduces techniques for constructing high-performance software for Android devices.
Regardless of the industry, a decrease in the performance or quality of a software system can mean big losses. Software systems today control our finances and control the machines that take care of our health or our public transportation. There is almost no area in our lives that is not at least partially computerized. Not only losses: in a globalized and competitive world, a company producing low-performance software will soon be devoured by the more efficient and cheaper competition.
For a while, the only metric used in software development was "Is the software correct? Is it doing what it is supposed to be doing?". This made sense at the dawn of the computer systems era, when not every single process was computerized and we had not developed a culture of software engineering or good methods for quality control, team organization, and so on. Now, everybody demands more.
Graphs are an excellent way to display information. Let's analyze the smartphone penetration numbers:
The numbers are clear. In the last quarter of 2008, in almost every region of the world smartphone penetration was under 20%. Nowadays, in 2015, most developed countries present a penetration close to 80%, whereas developing countries are close to 40%. In 2020, it is estimated that developed countries will have a penetration close to 100%, and countries in development over 50%. There are a few countries with more mobile phones than inhabitants!
Mobile users nowadays do not only check their e-mail on a mobile phone. There are many operations that are performed on a mobile phone: the entertainment industry, banking and payment, tourism and traveling, gaming… This leads us to a conclusion: software must be not only correct but also efficient. Failure in software will lead to annoyed customers who might opt to use a different competitor with a better-performing product. In extreme cases, non-performing software can lead our business to lose its revenue—imagine the case of an application to make hotel reservations where you cannot proceed to payment.
One of the first thoughts that naturally arise is that testing plays a central role in increasing and improving application performance. This is partially true, or as we prefer to say: testing is a good complement to a smartly designed application, but not a substitute.
If we concentrate just on testing, there are two main types: manual testing and automatic testing. As in the previous case, both types of testing are mutually inclusive, and one should not be used in detriment to the other. Manual testing involves a real user playing around with an application and some defined use-case scenarios, but also with more free will and the ability to leave the road of predefined tests and explore new paths.
Automatic tests are tests written by developers to ensure consistency of the application throughout the evolution in the life of a system. There are a few different types: unit tests, integration tests, or UI tests, which will be familiar to the reader. Good test coverage provides robustness to the system when new changes are being applied, improving resistance against failures and performance problems. As in the previous case, we do not want to exclude manual tests in favor of automatic tests, or vice versa (at least until machines are able to pass the Turing test!).
ANR stands for Application Not Responding, and is one of the several nightmares of an Android developer. The Android operating system analyzes the status of apps and threads, and when certain conditions are met it triggers an ANR dialog, blocking the user from any interactive experience. The dialog announces that the application stopped responding, and is not responsive anymore. The user can select whether he/she wants to close the application, or keep waiting until the application becomes responsive again (if this ever happens):
Android systems trigger ANRs in two different situations:
When there has been no response for an event in five seconds
If a BroadcastReceiver is still executing 10 seconds after its execution
This happens mostly when an operation is being executed in the UI Thread. In general, any operation expected to be time- or operation-consuming should be performed in a separate thread, keeping the UI Thread available for the user interaction, and only notifying the UI Thread when the operation has been finished. In Chapter 5, Multithreading, we will show some advanced techniques for multithreading and thread communication. There are also different classes that can be used to perform operations in different threads, each of them with its own advantages and disadvantages. In general, when developing an application, remember: ANR dialog appearance frequency is inversely proportional to user satisfaction.
As with any other development framework, Android defines its own architecture and module. Android is a Linux-based operating system, although the numerous abstract layers provided by the SDK hide the Linux kernel almost completely, and it is really rare that we will be programming directly at the kernel level:
Each Android application runs in its own process inside a virtual machine called Dalvik. As we have seen, programs are typically written in Java and then compiled to bytecode. From the bytecode (
.class files) they are afterwards transformed into DEX format, commonly using a special tool provided by the Android SDK called
dx. This DEX format is more optimized and designed to have a smaller memory footprint in comparison with normal Java
.class files, since mobile devices lack the computational capabilities of desktops. This is achieved through compression and merging/optimization of the multiple
It is not completely accurate that the coding has to be strictly done in Java. Android allows using native code in our applications, too. Therefore, existing code that we were using before can be reused here. Also, in the computer vision area, there is a lot of code that has been reused from the OpenCV framework. This is achieved through the Native Development Kit (NDK), which is explored in Chapter 9, Native Coding in Android and Chapter 10, Performance Tips.
The Dalvik Virtual Machine also includes some Java Virtual Machine (JVM) features, such as garbage collection (GC). There has been a lot of criticism through the GC because of its non-generational nature; it's famous for driving developers crazy. However, since Android 2.3, an improved concurrent garbage collector makes some of the development easier.
Each application running on Dalvik has at least a total of 16 MB of available heap memory. This can be a real limitation for some applications, since we will likely need to deal with large amounts of image and audio resources. However, newer devices such as tablets or high-end devices have a higher heap limit to allow the usage of high-resolution graphics. We expect this situation to improve in the near future due to the fast evolution of mobile hardware.
Memory is always, by definition, a scarce resource on any software platform. But when it comes to mobile devices, this is an even more constrained resource. Mobile devices often have less physical memory and processor capacity that their bigger peers, and having an efficient memory management is crucial to improving user experience and software stability.
Dalvik Virtual Machine routinely triggers garbage collection in a similar way to Java, but this does not mean that we can ignore memory management completely. One very common error in junior programmers is to create memory leaks. A memory leak happens when an object is stored in memory, but it cannot be accessed anymore by the running code. The size can vary a lot (from an integer to a big bitmap or structure of several megabytes), but in general they affect software smoothness and integrity. We can use automated tools and frameworks to detect memory leaks and also apply some programming techniques to avoid allocating objects unnecessarily (and equally important, to deallocate them when they are no longer needed).
An Android application has a maximal amount of RAM memory that it can manage. It is different for each device (yes, another problem of the system fragmentation), and can be particularly checked by calling the function
getMemoryClass() on the
ActivityManager. Early devices had a per-app cap of 16 MB. Later devices increased that to 24 MB or 32 MB, and it will not be surprising to see devices up to 48 or 64 MB. There are several factors contributing to this fact, such as screen size. Larger screens generally mean larger resolutions for bitmaps; thus, as they increase, memory requirements will also grow. Some techniques can also bypass this limitation, such as using the NDK or requesting from the system a larger heap. This last is, however, considered to be poor form for an Android app.
When a process starts, it is forked from an existing or root process called Zygote. Zygote starts every time the system boots and loads the resources common to all the apps. By doing this, Android tries to share all the common resources among the applications, avoiding duplicating memory usage for the same frameworks.
Mobile devices have a limited battery size, and they are not connected to a permanent power supply as with a standard computer. Therefore, an efficient usage of the battery and energy is a vital factor of survival. If you are continuously performing operations that drain the battery or require continuous access to the device hardware it will affect the user experience, and it might lead to rejection of the application.
Good energy management requires an excellent understanding of how the energy is used, and which operations can drain the battery very quickly. There are tools and benchmark frameworks to find out the energy bottlenecks and sections in the software where the energy consumption is higher than expected.
Mobile consumer-electronics devices, especially phones, are powered from batteries that are limited in size, and therefore, capacity. This implies that managing energy well is paramount in such devices. Good energy management requires a good understanding of where and how the energy is used. To this end we present a detailed analysis of the power consumption of a recent mobile phone, the Openmoko Neo Freerunner. We measure not only overall system power, but the exact breakdown of power consumption by the device's main hardware components. We present this power breakdown for micro-benchmarks as well as for a number of realistic usage scenarios. These results are validated by the overall power measurements of two other devices: the HTC Dream and Google Nexus One.
Android is mostly written in Java. Although a few alternatives have appeared lately (we can come up with Kotlin and Android, which is a fantastic combination, for example), Java will likely remain the language of choice for Android. Its very mature environment, massive support from Google and other companies, and the vibrant developer scene, ensure it goes on leading Android's development.
One factor that did attract developers to the Android ecosystem was precisely this shared usage of an existing language. Java has some particular characteristics and techniques that we need to learn in order to use it effectively.
Using Native Development Kit (NDK) can mean sometimes the difference between a performing application or an application that just does its job. We will generally use NDK in the following contexts:
Use of existing C/C++ libraries: This is an obvious advantage, since you have access to powerful existing software such as OpenCV1, audio encoders, and so on.
Performance: For some critical inner loops, the marginal performance advantage of C/C++ over Java, especially before Just-In-Time compilation (JIT) is available in the Android compiler, may be a deciding factor.
To do something that the NDK allows that the Java API can't manage: Low-level operations close to the hardware, particularly to impact manufacturer-specific hardware, might only be possible through C/C++.
Obfuscation: Compiled code is somehow more difficult to reverse-engineer than Java bytecode. Security by obscurity is, however, not the ideal solution, but it can complement your already existing system.
There are three different thresholds accepted as limits to the user experience in any software system:
0.1 seconds is perceived by the user as instantaneous responsiveness. In such operations, there is no need to display any visual feedback or notification to the user, and this includes most operations in normal scenarios (for example, the lapse between clicking on a button and displaying a dialog, or showing a different activity).
1.0 seconds is the lapse when the user flow gets interrupted. Between 0.1 and 1.0 there is still no need to provide any feedback, but after a second, the user has lost the perception of performing an immediate operation.
10 seconds is the final limit, when the user loses concentration and interest in the application. More than 10 seconds in an operation generally means that the user will lose her/his interest in the system and procrastinate while the operation is being performed. Visual feedback is crucial here; without it, the user will get frustrated and reject our system.
Google suggests keeping all interactions under 100 to 200 ms. That is the threshold beyond which users will perceive slowness in an application. Although this is not always possible (think about downloading a large amount of data, such as media and so on), we will learn techniques to provide the user with the best experience.
Developers often need to justify to non-technical peers why some decisions are taken that do not bring immediate value (think about refactoring an old module or developing some test coverage). There is a clear gap between the business and engineer departments that needs to be reconciled.
When we have to discuss with other departments the business value of decisions that have been made for the sake of software quality, I always like to mention the word "money". Making some decisions, in the long run, is equivalent to saving money and providing direct value to the software. They might not generate an immediate output, or a corporeal item (as much as software can be corporeal), but they certainly will come back in the future with some benefits. I can remember a few situations when refactoring a piece of software at the right moment made the difference between having a sustainable artifact that could be extended and having a monolith, as the result of many bad design decisions, that nobody was able to maintain and in the end meant money and financial costs. The following figure reveals the losses and consequences for companies over time due to bad software quality:
This graph has been taken from a document by David Chappell, and it explains some examples of when bad software quality incurs financial loss. Losing value from lost business might remind us of when Sony closed the PlayStation network due to a network attack. If the software had been properly designed and protected, the network might have been able to keep operating, but poor design led to the company losing a considerable amount of money. A financial loss due to customer reparations happens every time a company needs to compensate clients for a problem happening as a consequence of a poor software system. The obvious financial loss from lost customers will happen when customers do not want to acquire any more services provided by an infamous company! Financial loss from lawsuits is inevitable in many cases, especially when privacy issues or stolen data are involved (and they can be very expensive!).
After this chapter, the reader should have a more accurate idea of the different areas we will explore together in this book. We also hope our arguments are convincing enough, and we will work towards developing them further throughout the entire book.
The reader should be able to argue why performance will matter in the context of his/her organization, and should know some of the keywords of efficient Android development. Do not get stressed, this is only the beginning.