📜 ⬆️ ⬇️

Development of fast mobile applications on Android. Part one

In Edison, we often encounter optimization of mobile applications and want to share material that we consider extremely useful if you solve one of two tasks: a) you want the application to slow down less; b) want to make a beautiful, soft and smooth interface for the mass user.
We bring to your attention the first part of the translation of the article Udi Cohen, which we used as a tool for teaching young colleagues to optimize for Android.



A few weeks ago, I gave a talk on optimizing Android’s performance on Droidcon in New York.
')
I spent a lot of time on the presentation, because I wanted to show real examples of performance problems, and how they can be identified using the available tools. I had to remove half of the slides, because I did not have enough time to show everything. In this article I collected everything that I was talking about, as well as examples that I did not have time to show.

My basic rules that I follow when working with optimization.

My rules


Every time I encounter performance problems or seek solutions to these problems, I follow these rules.

Always measure - eye optimization is always a bad idea. After you look at the same animations several times, it will start to seem to you that they go faster. Numbers don't lie. Use the tools we’ll get to soon, and measure a few times how your application works before and after the changes.

Use slow devices - if you really want to find all the weak points, slow devices will help you more. With newer and more powerful devices, you may not be so worried about performance issues, but not all users use the latest and better models.

Tradeoffs - performance optimization is built on tradeoffs. You are optimizing one thing, but at the cost of something else. In many cases, this other thing may be your time spent searching for and correcting, or the quality of your bitmap images, or the amount of data you need to store in certain structures. Get ready for such sacrifices.

Systrace


Systrace is one of the greatest tools that you probably don't use. This is because the developers were not sure what to do with the information received.

Systrace makes an overview of the applications that are currently running on the phone. This tool reminds us that the phone that we hold in our hands is a powerful computer that can perform many operations at one time. In one of the latest updates of the SDK Tools, this tool has been supplemented with data-based assumptions that help us find the problem. Let's see what the result of the trace looks like:

(pictures are clickable)
image

You can get the trace result using Android Device Monitor or using the command line. More information you can find here .

In the video, I talked about the components of the report. Particularly interesting of them are Alerts and Frames, representing to us assumptions generated from the collected data. Let's take a look at the trace I took and choose one of the warnings above.

image

The Alert indicates that there was a long call to View # draw (). We also get descriptions, links to documentation, and even links to videos with discussions on this topic. Looking below at the Frames line, we see the labels corresponding to each frame drawn, they are colored in green, yellow or red to reflect the performance problems arising in the process of frame rendering. Let's select one of the frames marked in red.

image

Below we will see all relevant notifications for this frame. In this case, we have 3 messages, we have already seen one of them. Let's zoom in on this frame and expand the “Inflation during ListView recycling” notification:

image

We see that in total for this part it took 32ms, because of which the frame drawing lasts longer than 16ms, required to reach 60fps. Here you can find more detailed information on each constituent element in the ListView for this frame - about 6ms were spent on 5 of them. Their description will help us understand the problem and even find a solution. The diagram above shows all the calls, we can enlarge or stretch it to see which parts of the drawing take more time.

Another example of slow frame rendering:

image

After selecting a frame, we can press the “m” key to select it and see how long this part takes. Looking above, we see that it takes 19ms to render the frame. Having opened the notification for this frame, we see the message “Scheduling delay”.

This means that the thread processing this particular frame was not scheduled on the processor for a long time. Thus, it took more time to finish his work. After selecting the longest part, more detailed information is displayed.

image

Wall duration is the time taken from the start to the end of the item. It is called “Wall duration” because it’s like tracking a wall clock from the moment the flow started.

CPU duration is the actual time that the processor spent on this part.
There is a big difference between the two dimensions. While the total work takes 18ms, the CPU spends only 4ms to work with the stream. This is a bit strange, so it would be nice to see what the processor is doing the remaining time:

image

All 4 cores are very busy.

Selecting one of the streams shows us where it appeared, the application is called com.udinic.keepbusyapp. In this case, another application was the reason that the processor was loaded, not paying some time to your application.

While such a scenario is usually temporary, since other applications usually do not steal the processor in the background (... right?), But such threads may come from different processes in your application or even from the main process. Thus, Systrace is a review tool, but there is a limit to how deep it can look. To find what takes our CPU time, we will use another tool called Traceview.

Traceview


Traceview is a profiling tool that shows how long each running method lasts. Let's see what the result of the trace looks like:

image

This tool can be run from Android Device Monitor or from code. More information is here .

Let's look at the following columns.


I opened an app that had scrolling smoothness issues. I started tracing, flipped through a bit and stopped on one line. I stumbled upon the getView () method and opened it, and here is what I saw:

image

This method was called 12 times, the processor spent about 3 ms for each call, but the real time required for each call is 162 ms! Definitely a problem ...

Looking at the calls from this method, we can see how the total time is divided between different methods. Thread.join () takes about 98% of real time. This method is used when we want to wait for the end of another process. One of the other descendants is Thread.start (), which allows me to assume that getView () opens the stream and waits for it to end.

But where is the flow?

We cannot see what this thread is doing, since getView () itself does not do this work. To find it, I searched for the Thread.run () method, which is called when a new thread appears. I followed him until I found the culprit:

image

I found that BgService.doWork () requires about 14 ms per call, and we have about 40 such calls! There is also a chance that each getView () will call it more than once, and this explains why each getView () call takes so much time. This method takes the processor for a long time. Looking at the Exclusive CPU time, we see that it uses 80% of the CPU time!

Sorting by Exclusive CPU time is also a good way to find the most loaded methods that can contribute to the performance problems you experience.

Tracking critical methods such as getView (), View # onDraw () and others will help us find the reason why our application is slow. But sometimes there is something that loads our processor, taking away the precious work cycles of the processor, which can be spent on a smoother drawing of our UI. The garbage collector works occasionally, removing unused objects, and usually has little impact on the application running in the foreground. If the garbage collector works too often, it can slow down our application, and it is quite possible that we ourselves are to blame for this ...

Memory profiling


Android Studio has been improved lately, with more and more tools added to help us find and analyze performance issues. The Memory tab in the Android window will show us the change in the amount of data in the heap over time. Here's what it looks like:

image

Where we see small drops in the graph, the garbage collector collects and deletes unused objects and frees up memory on the heap.

There are 2 tools available on the left side of the chart: Heap dump and Allocation Tracker.

Heap dump

To investigate what is currently in our heap, we can use the button on the left Heap dump. This tool will take a snapshot of what is currently on the heap and show it in a special report screen inside Android Studio:

image

On the left, we can see a histogram of instances of objects on the heap, grouped by class name. For each of them, the total number of objects for which memory is allocated, the size of these objects (Schallow size) and the size of objects stored in memory are indicated. The latter tells us how much memory can be freed if instances of objects are destroyed. This gives us an important insight into the memory footprint of our application, helping to define large data structures and relationships between objects. This information can help us build more efficient data structures, remove connections between objects in order to reduce memory consumption and ultimately reduce memory as much as possible.

Looking at the histogram, we see that MemoryActivity has 39 instances of objects, which seems strange to activate. Selecting one of its instances on the right, expand the list of all references of this object in the base tree below.

image

One of these is part of an array inside the ListenersManager object. Looking at other instances of activations, it turns out that all of them are stored in this object. This explains why an object of this class uses so much memory.

image

Such situations are known to be called “memory leaks,” since the activites were clearly destroyed, but unused memory cannot be cleared by the garbage collector due to this link. We can avoid such situations if we are sure that our objects do not refer to other objects that have survived. In this case, the ListenersManager does not need to save this link after the activation has been destroyed. The solution is to remove the link before the class instance is destroyed in the onDestory () method.

The memory leaks of other large objects take up a lot of space on the heap, reducing the amount of available memory and causing frequent calls to the garbage collector in an attempt to free up more space. These calls will be occupied by the processor, causing the performance degradation of your application. If the amount of available memory is not enough for the application, and the heap size cannot be slightly increased, a more dramatic outcome will occur - OutOfMemoryException, which will cause the application to crash.

A more advanced tool is the Eclipse Memory Analyzer Tool (Eclipse MAT):

image

This tool can do the same thing as Android Studio, in addition it can detect possible memory leaks and provide a more advanced search for objects, such as the search for all Bitmap sizes larger than 2 MB or all empty Rect objects .

Another great tool is the LeakCanary library, which keeps track of all your objects and makes sure that there is no memory leak due to them. If this happens, you will receive a notification to know where and what happened.

image

Allocation tracker

Allocation Tracker can be opened / closed using the button to the left of the memory graph. He will compile a report on all instances of the class allocated in memory for this period of time, grouped into classes:

image

or by methods:

image

There is also a good visualization, showing us the largest specimens.

Using this information, we can find methods that take up too much memory and can spawn many calls to the garbage collector. We can also find many examples of objects of the same class with a short life cycle, in which we can use the Object Pool to reduce memory allocation.

Continued in the second part .

PS
The report was recorded on video and you can watch it here:


Slides are also available.

Source: https://habr.com/ru/post/271761/


All Articles