2. Testing the effectiveness of different Android Tools in detecting performance issues caused by Android Fibonacci Computation on Main Thread

Abhishek Luthra
10 min readDec 18, 2022

--

2.1 Introduction

The main Thread is responsible for rendering the ui on the screen as well as handling the user input. If an expensive computation is done on the main thread, the main thread will remain busy in the computation and will be unresponsive till the computation is completed. This is a very common cause of UI Jank

2.2 Code Implementation of Test

As part of testing the effectiveness of different tools in detecting UI jank caused by heavy computation on the main thread, we created a test application where we compute the Fibonacci series on the main thread[21]. This operation is quite expensive and blocks the main thread for a considerable period of time. To verify that the tool test results are correct in pinpointing the real cause of UI jank in this case( which is running a heavy computation on the main thread), we ran the same tools with Fibonacci computation on a separate thread using AsyncTask[22].

The Fibonacci Activity ( Code 12 ) consists of a gif of an animated dancing cartoon. At the launch of the activity, the animation is clearly visible smoothly. When a user clicks on ‘Compute Some Fibonacci Numbers’ on the screen, the Fibonacci series computation starts. In one case computation happens on the main thread and on another, computation happens on a separate Async task thread. When the Fibonacci series is being computed, the main thread is completely occupied with computation and the animation is stalled momentarily as the animation also runs on the main thread. In the next sections, we will apply different tools to check their effectiveness in detecting UI jank caused by heavy computation on the main thread and separate thread.

2.2.1 LauncherActivity.java

package com.example.perforamancetest;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.ViewGroup;
import android.widget.Button;
import com.example.performancetest.R;
public class LauncherActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.launcher_main);
ViewGroup rootView = (ViewGroup) findViewById(R.id.main_rootview);
addButton("Fibonacci Test", rootView);
}
public void addButton( String description, ViewGroup parent) {
Button button = new Button(this);
button.setOnClickListener(v -> {
Intent problemIntent = new Intent(LauncherActivity.this, FibonacciActivity.class);
startActivity(problemIntent);
});
button.setText(description);
parent.addView(button);
}
}

Code 10. LauncherActivity.java

2.2.2 launcher_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:id="@+id/main_rootview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.example.perforamancetest.LauncherActivity"
tools:ignore="MergeRootFrame" />

Code 11. launcher_main.xml

2.2.3 FibonacciActivity.java with computeFibnocci on the main thread

package com.example.perforamancetest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Debug;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.widget.Button;
import com.example.performancetest.R;

public class FibonacciActivity extends Activity {
public static final String LOG_TAG = "CachingActivityExercise";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fibonacci);
Button theButtonThatDoesFibonacciStuff = (Button) findViewById(R.id.caching_do_fib_stuff);
theButtonThatDoesFibonacciStuff.setText("Compute some Fibonacci numbers.");
theButtonThatDoesFibonacciStuff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

// Compute the 40th number in the fibonacci sequence, then dump to log output. Note
// how the UI hangs each time you do this.
Log.i(LOG_TAG, String.valueOf(computeFibonacci(30)));

}
});
// It's much easier to see how your decisions affect framerate when there's something
// changing on screen. For entirely serious, educational purposes, a dancing pirate
// will be included with this exercise.
WebView webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.loadUrl("file:///android_asset/dancing_cartoon.gif");
}
/**
* Why store things when you can recurse instead? Don't let evidence, personal experience,
* or rational arguments from your peers fool you. The elegant solution is the best solution.
*
* @param positionInFibSequence The position in the fibonacci sequence to return.
* @return the nth number of the fibonacci sequence. Seriously, try to keep up.
*/
public int computeFibonacci(int positionInFibSequence) {
if (positionInFibSequence <= 2) {
return 1;
} else {
return computeFibonacci(positionInFibSequence - 1)
+ computeFibonacci(positionInFibSequence - 2);
}
}
}

Code 12. FibonacciActivity.java

2.2.4 FibonacciActivity.java with computeFibnocci on a separate thread

package com.example.perforamancetest;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Debug;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.widget.Button;
import com.example.performancetest.R;

public class FibonacciActivity extends Activity {
public static final String LOG_TAG = "CachingActivityExercise";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fibonacci);
Button theButtonThatDoesFibonacciStuff = (Button) findViewById(R.id.caching_do_fib_stuff);
theButtonThatDoesFibonacciStuff.setText("Compute some Fibonacci numbers.");
theButtonThatDoesFibonacciStuff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Compute the 40th number in the fibonacci sequence, then dump to log output. Note
// how the UI hangs each time you do this.
new FibonacciAsyncTask().execute(30);
}
});
// It's much easier to see how your decisions affect framerate when there's something
// changing on screen. For entirely serious, educational purposes, a dancing pirate
// will be included with this exercise.
WebView webView = (WebView) findViewById(R.id.webview);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.loadUrl("file:///android_asset/dancing_cartoon.gif");
}
/**
* Optimized Fibonacci Code
*/

private class FibonacciAsyncTask extends AsyncTask<Integer,Void,Integer>{
int[] fibonacciList ;
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Integer doInBackground(Integer… integers) {
return computeFibonacci(integers[0]);
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
Log.i(LOG_TAG, String.valueOf(integer));
}
public int computeFibonacci(int positionInFibSequence) {
if(fibonacciList == null){
fibonacciList = new int[positionInFibSequence+1];
}
if(positionInFibSequence==0){
return 0;
}
else if(positionInFibSequence ==1){
return 1;
}
else if(fibonacciList[positionInFibSequence]==0){
fibonacciList[positionInFibSequence]= computeFibonacci(positionInFibSequence-1)+computeFibonacci(positionInFibSequence-2);
}
return fibonacciList[positionInFibSequence];
}
}
}

Code 13. FibonacciActivity.java

2.2.5 activity_fibonacci.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/caching_rootview"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.example.perforamancetest.FibonacciActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/caching_do_fib_stuff" />
<WebView
android:layout_width="300dp"
android:layout_height="300dp"
android:id="@+id/webview"
android:layout_gravity="center_horizontal"/>
</LinearLayout>

Code 14. activity_fibonacci.xml

2.3 Tool Test

2.3.1 LayoutInspector Tool Test

2.3.1.1 LayoutInspector on FibonacciActivity.java with Fibonacci Computation on the Main thread

Figure 58. LayoutInspector result for FibonacciActivity.java with Fibonacci Computation on the main thread

2.3.1.2 LayoutInspector on FibonacciActivity.java with Fibonacci Computation on a separate thread

Figure 59. LayoutInspector result for FibonacciActivity.java with Fibonacci Computation on a separate thread

2.3.1.3 LayoutInspector Tool Test Result

It is clearly visible from the component tree of Figure 58 and Figure 59 that the layout hierarchy for FibonacciActivity remains the same irrespective of whether Fibonacci is being computed on the main thread or a separate thread .

This indicates LayoutInspector is ineffective in detecting any UI jank due to heavy computation on the main thread

2.3.2 Debug GPU Overdraw Tool Test

2.3.2.1 Debug GPU Overdraw With Fibonacci Computation on the main thread

Figure 60. Debug GPU overdraw result for FibonacciActivity.java with Fibonacci Computation on the main thread

2.3.2.2 Debug GPU Overdraw with Fibonacci Computation on a separate thread

Figure 61. Debug GPU overdraw result for FibonacciActivity.java with Fibonacci Computation on a separate thread

2.3.2.3 Debug GPU Overdraw Tool Test Result

It is clearly visible from the overdraw areas of FibonacciActivity with Fibonacci Computation on the main thread(Figure 60) and Fibonacci Computation on a separate thread(Figure 61) that there is no difference in output given by Debug GPU overdraw developer option.

2.3.3 Profile GPU rendering Tool Test

2.3.3.1 Profile GPU rendering for FibonacciActivity.java with Fibonacci Computation on Main Thread

Figure 62. Debug GPU overdraw result for FibonacciActivity with Fibonacci Computation on Main Thread

2.3.3.2 Profile GPU rendering for FibonacciActivity.java without Fibonacci Computation

Figure 63. Debug GPU overdraw result for FibonacciActivity with Fibonacci Computation on a separate thread

2.3.2.3 Profile GPU rendering Tool Test Result

It is clearly visible from the rendering bars of FibonacciActivity.java with Fibonacci computation on the main thread(Figure 62) and Fibonacci computation on the separate thread(Figure 63) that the rendering bars with Fibonacci Computation on the separate thread are smaller in height than rendering bars with Fibonacci computation on the main thread. This indicates that the profile GPU rendering is effective in detecting the rendering performance issues caused by heavy computation on the main thread.

Tool used

Table 13. Table of results of Profile GPU rendering tool test on FibonacciActivity.java with Fibonacci computation on the main thread

2.3.4 Show View Updates Tool Test

2.3.4.1 Show View Updates for FibonacciActivity.java with Fibonacci Computation on Main Thread

The result of applying the Show View Updates developer option on FibonacciActivity with Fibonacci Computation on the main thread is shown in Reference [19]

2.3.4.2 Show View Updates for FibonacciActivity.java with Fibonacci Computation on Separate Thread

The result of applying the Show View Updates developer option on FibonacciActivity with Fibonacci Computation on a separate thread is shown in Reference [20]

2.3.4.3 Show View Updates Tool Test Result

It is clearly visible from the video result for FibonacciActivity with Fibonacci Computation on the main thread[19] and Fibonacci Computation on a separate thread[20] that there is a significant difference in the results of applying this tool. The view updates stop when Fibonacci computation starts by pressing the button. This indicates that the Show view updates tool is effective in detecting the rendering performance issues caused by nested view hierarchy.

2.3.5 Android CPU Profiler Tool Test

2.3.5.1 Android CPU Profiler tool test for FibonacciActivity.java with Fibonacci Computation on the main thread
2.3.5.1.1 Android CPU Profiler tool test for FibonacciActivity.java with Fibonacci Computation on the main thread — System Trace

Figure 64. The CPU profiler system trace visualization for FibonacciActivity with Fibonacci computation on the Main thread.

Figure 64 presents some recurring shorter jank frames which are primarily due to recurring steps in the dancing_cartoon animation. In order to focus on the Ui jank introduced with Fibonacci computation on the main thread, we only focus on the UI jank frame coinciding with the user input shown in the user track above.

Figure 65. The CPU profiler system trace — All Frame visualization with Fibonacci Computation on the main thread. This window shows all frames created in the current system trace recording.

Figure 66. The CPU profiler system trace — Janky Frame visualization with Fibonacci computation on the main thread. This window shows all janky frames created in the current system trace recording

In Figure 56, frame 878317 coincides with the user button click which starts Fibonacci computation. The other frames 878382, 878303, and 878462 are recurring jank frames. To keep ourselves focussed on the jank experience introduced only due to Fibonacci computation, we keep frame 878317 under consideration.

From Figure 64, It can be observed that the performance test thread ( thread corresponding to our application) is continuously green ( running) for the duration of frame 878317. This clearly gives an indication to look deeper into the application thread for the window when the button is clicked to see the activity on the thread that may be causing the UI jank.

2.3.5.1.2 Android CPU Profiler tool test for FibonacciActivity.java with Fibonacci Computation on main thread — Java/Kotlin Method Trace

Figure 67. The CPU profiler kotlin/java method trace visualization for FibonacciActivity.java with Fibonacci computation. This window clearly shows that computeFibonacci function takes a lot of time to run after the button click happens. This is the same method that is written within the onClick handler.

Figure 68. The CPU profiler kotlin/java method traces Top Down visualization for button click with Fibonacci computation on the main thread.

It can be clearly seen from Figure 68 that 99.99 percent of the time consumed in the onClick handler comes from running computeFibonacci on the main thread. This clearly indicates that running a heavy computation on the main thread is the main cause of extra UI jank on clicking the button which starts Fibonacci computation on the main thread.

This is additionally visible from the flame chart shown in figure 69.

Figure 69. The CPU profiler kotlin/java method traces Flame chart visualization for onClick on FibonacciActivity with Fibonacci computation on the main thread.

2.3.5.2 Android CPU Profiler tool test for FibonacciActivity.java with Fibonacci Computation on separate thread4.2.3.5.2.1 Android CPU Profiler tool test for FibonacciActivity.java with Fibonacci Computation on a separate thread- System Trace

Figure 70. The CPU profiler system trace visualization for fibonacciActivity with Fibonacci computation on Async task. This window clearly shows no jank frame. This also shows a lot of computation is done on AsyncTask at the bottom of the screenshot

Figure 71. The CPU profiler system trace — All Frame visualization for Fibonacci computation on async task. This window shows all frames created in the current system trace recording.

Figure 72. The CPU profiler system trace — Janky Frame visualization for Fibonacci computation on a separate thread. This window shows none of the frames is janky.

From Figure 72, we see that there is no UI jank when Fibonacci computation is done on a separate thread.

2.3.5.3 Android CPU Profiler Tool Test Result

It is clearly visible from the CPU profiler result for Fibonacci computation on the main thread and separate thread that there is a significant reduction in the number of janks frames itself by using running computation on separate thread rather than running on the main thread. This indicates that the CPU profiler tool is effective in detecting the rendering performance issues caused by running a heavy computation on the main thread.

This clearly shows that there is a 100 percent improvement in the number of jank frames by using a separate thread for heavy computation tasks rather than running on the main thread.

2.3.6 Tool Test Results on expensive operations running on the main thread

--

--