Android App Profiling

Tram Ho

Have you ever asked yourself the question: When users are experiencing, communicating with the application they feel lag when using an application? The answer is not always clear, but most of the time it has to do with heavy CPU tasks, and there are also cases where these types of performance problems are related to memory. Of course, you can print logs to help you troubleshoot, but you need to be quite familiar with the codebase to place logs where appropriate.

If you want to explore another option that doesn’t rely solely on logs, then use Android Profiler , introduced in Android Studio 3.0. Developers can use this tool to monitor CPU usage and memory usage, block response networks and even monitor power consumption. Based on the data provided by Android Profiler, we can gain a better understanding of how our applications use CPU and other memory resources. These are the last points that can lead us to the root cause of the problem. In this article, we will discuss methods to solve performance issues systematically using Android Profiler.

1. Memory Problems

Let’s review Android memory management so we can understand why inappropriate memory usage can contribute to performance issues. An Android application, like any other software application, runs in a memory-limited environment, in which the Android OS assigns a limited but very flexible (flexible) memory heap to each application. is launched. Throughout the life of an application, the Android OS allocates memory to the application to store instructions and program data. The amount of required memory varies in different application use cases. For example, an application needs more memory to display full-screen bitmaps than full-screen text.

When a memory is no longer needed, the Android OS automatically recovers this memory resource so that it can be reused to serve new memory allocation requests. This process is often called Garbage Collection .

Garbage Collection usually does not affect the performance of the application, because the application downtime caused by a Garbage Collection process is negligible. However, if too many Garbage Collection events occur in a short period of time, users will begin to experience a slow user experience in the application.

2. Memory Profiling with Android Profiler

The prerequisites for Android Profiler are a version of Android Studio 3.0 or higher and a connected test device (physical device) or emulator (virtual machine) is running at least Android SDK Level 26. Please click Click the Profiler tab in the bottom panel to launch Android Profiler.

Run your application in debug mode and you will see Android Profiler displaying real-time metrics for CPU, Memory, Network and Energy.

Clicking on the Memory section to see details of memory usage data, Android Profiler provides a visual overview of memory usage over time.

Android Profiler memory profiling

As you can see in the diagram above, there is a spike at first when the application is launched for the first time, followed by a drop drop and finally a flat line. This is typical behavior of a simple hello world application, because there’s so much going on here. A flat memory chart (falt) means stable memory usage and it is the ideal memory situation that we want to achieve.

3. Android Profiler in Action

Let’s look at a few sample diagrams to signal memory problems. You can use the source code from this GitHub repo to reproduce the problem.

3.1. A growing graph (A growing graph)

If you observe a trend line that only goes up and rarely goes down, it’s probably due to a memory leak, which means that some part of the memory cannot be freed. Or simply not enough memory to deal with the application. When the application has reached the memory limit and the Android operating system cannot allocate additional memory to the application, OutOfMemoryError will occur.

A growing memory usage graph

This problem can be reproduced in the High Memory Usage example from the demo application. This example basically creates a large number of rows (100k) and adds these rows to linearLayout. (Of course in practice, this is not a common thing in Android, but I just want to show an extreme case when creating multiple views can cause memory problems, as shown in source code below.)

This activity does not use any Adapter or RecyclerView to recycle item views. Therefore, it takes 100 thousand views to complete the implementation of addRowsOfTextView ().

Now, click the Start button and monitor memory usage in Android Studio. The memory usage continues to increase and eventually the application crashes. This is the behavior we would expect it to happen.

The solution to this problem is very simple. By simply applying RecyclerView to reuse item views, it can greatly reduce the number of create views. The diagram below is the memory usage for displaying 100,000 item views and you can see an improvement in the Low Memory Usage example with RecyclerView.

Memory optimization by using RecyclerView

3.2. Turbulence in a short period of time (Turbulence)

Turbulence is an indicator of instability and this also applies to Android memory usage. When we look at this model, there are often a lot of expensive objects created and thrown in their short lifespan. The CPU is wasting a lot of performance performing garbage collection, but not doing actual work for the application. Users may experience sluggish user interfaces and we should definitely optimize memory usage in this case.

A memory usage turbulence

To reproduce this problem, run the GC Number example from the demo application. This example uses RecyclerView to display two alternate bitmaps: one large bitmap image with 1000 x 1000 resolution and one smaller than 256 x 256. Move RecyclerView and you will see obvious disturbances in the Program. Memory creation and sluggish user experience in mobile apps.

Laggy RecyclerView

In this case, the sample code is implementing RecyclerView but we still have memory issues. Although RecyclerView is the solution to previous memory problems, it is not a bullet that solves all memory problems. To find the root cause, we need more information to analyze.

Click the Record button in the Memory Profiler, scroll RecyclerView for a while and click the stop button. Profiler will show you a detailed memory usage list sorted by object type.

The detailed memory usage

Sorting the list by Shallow size and the top item is the same byte array, so we know that most memory allocations are attributed to the byte array creation actions. There are only 32 allocations for the byte array and its total size is 577,871,888 bits, which is 72.23 MB. To find out more, click one of the instances in the instance view and view allocation call stack. The highlighted method is the NumerousGCRecyclerViewAdapter’s onBindViewHolder (), but we haven’t created any byte arrays explicitly with this method.

Take a closer look at the onBindViewHolder () method in the stack. Method calls go from decodeResource () -> decodeResourceStream () -> decodeStream () -> nativeDecodeAsset () and finally to newNonMovableArray (). The documentation for this method says:

Returns an array allocated in an area of ​​the Java heap where it will never be moved. This is used to implement native allocations on the Java heap, such as DirectByteBuffers and Bitmaps.

Means: Returns an allocated array in an area of ​​the Java heap where it will never be moved. This is used to make native allocations on the Java heap, such as DirectByteBuffers and Bitmap.

Therefore, we can conclude that a lot of byte arrays are created right here using the nativeDecodeAsset () method. Each time we call BitmapFactory.decodeResource (), a new version of the bitmap object is created and therefore the byte array data is beneath it. If we can reduce the frequency of the BitmapFactory.decodeResource () requests, we can avoid the need to allocate additional memory and thus reduce the appearance of the Garbage Collection.

This is an improved version of the RecyclerViewAdapter, only creating bitmap instances once, storing them and reusing these bitmaps for imageView in the onBindViewHolder () method. Any other unnecessary memory allocation is avoided. Please review at the memory chart after improvement.

We see a flat memory usage graph and there is only one allocation for a byte array and its size is insignificant. In addition, the application can now scroll smoothly without any slowness. Smooth RecyclerView


Through this article, I hope it gives you an idea of ​​how to use profiling tools to analyze performance issues. And always pay attention to the use of memory in the application and avoid unnecessary memory resource allocation.

Thanks for reading!


Chia sẻ bài viết ngay

Nguồn bài viết : Viblo