Part1: Native Android Memory Profiling

Mobile applications, unlike cloud applications, run on an embedded hardware device that is limited in resources. Resources such as memory, CPU, battery and bandwidth are the pillars in mobile application profiling. Mobile developers, especially web developers who are new to mobile applications, tend to delay or totally ignore mobile application profiling until an issue arises. Issues such as slow application response, memory crashes, bloated code size and many more can arise. A sound and good practice is to constantly profile your mobile application and pre-empt any issue before it happens. Mobile application profiling is better understood and easier to tune when performed periodically.

This blog is the first part of a series aiming to explore the different tools and methods for mobile application profiling. In this post, we will focus on Android application profiling. Red Hat Mobile Application Platform supports Android mobile applications by providing Android mobile application templates, building Android applications on Red Hat Mobile Application Platform build farm and downloading Android applications to be installed on actual hardware devices.

Android Studio has come a long way with regards to tools for profiling, which we will cover in this blog series. There are other existing tools to analyze memory of Android apps, but in this post, we will focus on Android Studio version 2.2.2.2.

Memory Leaks

Android Studio provides a tool to investigate memory in the Monitor Tab as seen in the picture below. Create a new Android application or download one of Red Hat Mobile Application Platform’s Android templates and run it on a device, then choose the Monitor tab. At the top of the view you will see the Memory tools.

The blue graph shows how much memory your application allocated and freed. Any dip in the graph will show Java Garbage Collection (GC) in action. You can also force a GC by clicking on the "Initiate GC" button (to the right of the pause button). It is good practice to constantly monitor this graph for new releases of your application to find unusual memory allocations or dips and peaks in the graph.

Finding Memory Leaks

The next tool we will investigate allows us to dump the Java Heap (.hprof) at a specific time. This file contains a lot of information and it is hard to understand everything. However, it is an excellent tool to find memory leaks. Android native applications are developed using Java, and yes, Java GC takes care of cleaning up unused allocated memory - however, there are some instances when GC does not clean memory because there is a strong reference to it. For example in Android, activities have a context and a lifecycle. They are prone to cause memory leaks when onDestroy() is called and not all memory references have been cleared. For example, when using the Android system resources to register a listener and then calling onDestroy without cleaning the listener and unregistering it, a memory leak will occur.

Adding a Memory Leak

We will forcefully add a memory leak to the new application we just created. In this memory leak we will register a sensor listener and never unregister. We will also add a button that will launch a new activity and destroy the main one.

After creating a new application, add the following code to your MainActivity.java code:

Extend your activity to implement SensorEventListener Interface

public class MainActivity extends AppCompatActivity implements SensorEventListener

Declare variables for sensor and sensor manager

private static SensorManager sensorManager; private static Sensor sensor;

Add a button to your activity_main.xml or any other layout your are using

<Button android:text="Launch New Activity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/buttonlaunchActivity" android:layout_below="@+id/textView" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_marginRight="187dp" android:layout_marginEnd="187dp" android:layout_marginTop="45dp" />

Handle this button clicks from your MainActivity.java code

// Handle Button clicks Button launchActivity = (Button) findViewById(R.id.buttonlaunchActivity); launchActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Launch new Activity Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); startActivity(intent); finish(); } });

Create a blank new Activity called SecondActivity, this will be launched on button click.

Create a function to register a sensor listener

void registerListener() { sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST); Log.v(TAG, "Registered Listener"); }

Call this function from onCreate in MainActivity.java

registerListener();

Remember not to call unregisterListener from anywhere on purpose so we can see the memory leak in the memory analyzer.

Dumping Memory Java Heap

Now that we have all the code in, we will build and run this application on an Android device. When it starts on the device, open the Android Monitor and scroll up to see the Memory graph, it should look something like this:

Next, we will induce a memory leak by clicking on the button in our activity to launch second activity. After the new activity launches, we will dump the Java heap by clicking on the tool above the memory graph. We should now see where the heap is being collected by the mark on the graph and a new file with a .hprof extension should open in our window, as seen below.

Now let’s run the Analyzer on the right sidebar, and click the play button on the top right corner that detects Leaked Activities and Duplicate strings. After running it, the Analyzer should find at least one leaked Activity as shown below:

If we select the MaintActivity Leaked instance, we can see in the Reference Tree that our SystemSensorManager in our mContext leaked. You can right click and jump to source, that will take you to the MainActivity, it does not really point to the exact line of code. However, it is easy to deduce what is causing it. Of course, as a simple test, you can remove all our leaky code and re-run the test, it should clear the leaked Activity. As you can see, the Reference Tree contains a massive amount of information that is hard to understand and trace. However, with time, and after running this several times on your application, you will become more familiar with the tree.

Memory Allocation

Android Studio’s second tool for memory profiling is Memory allocation. This tool dumps a .alloc file that shows all the memory allocated within your application within a specific time period. This tool must be started and stopped within a certain time while your application is running. Again, this is not something that is clear the first time your run it, there is a lot of information in the .alloc file that needs to be understood over time.

We will add some code to allocate a chunk of memory within a thread, then run the tool to observe the memory allocation. Before we start, please clean up all the memory leak code and follow these steps.

Let’s create a thread (it shows up as a separate thread in the .alloc file) that allocates a byte in a loop that runs 200 times.

void createAThread(){ new Thread(new Runnable() { public void run() { for(int i=0; i<200; i++) { byte[] ba1 = new byte[1]; } } }).start(); }

Now, using the same button, use ‘on button click’ to call this method and create the thread and run it.

// Handle Button clicks Button launchActivity = (Button) findViewById(R.id.buttonlaunchActivity); launchActivity.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { createAThread(); } });

Now build and run the application on a device. Expand the Memory Monitor tool and start the memory allocation tool by clicking on the icon. You should see something like this below.

Now click the button that should start the thread, wait a few seconds then click the stop memory allocating button. Now you should see a .alloc file opening in your view looking like this below.

As you can see, we have three threads. In this case, if we expand <Thread 15> we will see the byte allocation for 200 times as seen below.

Seems like each byte array allocation in code is stored in 16 bits giving it a total of 3200 bits of memory allocated. If you right click and select "Jump to Source" it will take you to the top of MainActivity file and not the actual line of code. Please consider this just as an example to show memory allocation in action.

In part 1 of this blog series, we explored memory profiling for Android native applications using Android Studio tools. As you saw, memory profiling can be a daunting task with information overload, but if applied consistently during your mobile application development lifecycle, it will become a crucial task in understanding and preventing memory leaks, bloating and over use of mobile resources.





Juana Nakfour is a senior mobile Technical Account Manager with more than 15 years advanced R&D experience in the telecommunications industry. Juana’s focus is broad and includes wireless communication networks, embedded/mobile devices and microservices.

Innovation is only possible because of the people behind it. Join us at Red Hat Summit, May 2-4, to hear from TAMs and other Red Hat experts in person! Register now for only US$1,000 using code CEE17.

A Red Hat Technical Account Manager (TAM) is a specialized product expert who works collaboratively with IT organizations to strategically plan for successful deployments and help realize optimal performance and growth. The TAM is part of Red Hat’s world-class Customer Experience and Engagement organization and provides proactive advice and guidance to help you identify and address potential problems before they occur. Should a problem arise, your TAM will own the issue and engage the best resources to resolve it as quickly as possible with minimal disruption to your business.