6. Testing the effectiveness of different Android Tools in detecting performance issues caused by Animation without Hardware Layers

Abhishek Luthra
9 min readDec 18, 2022

--

6.1 Introduction

When animations are run on a device, frames are drawn in every frame. Starting with API level 11, hardware acceleration is supported by the Android 2D rendering pipeline. When hardware acceleration is enabled, drawing operations which are usually performed using View’s canvas, use the GPU for drawing. When hardware layers are used, layers get drawn once in an off-screen buffer and are later reused when the same views need to be redrawn. It saves on extra drawing operations on the main thread

6.2 Code Implementation of Test

In order to test the performance degradation in running animations without hardware layers, we created a UI that runs animations by moving one layout out and another layout in. We will run our tools with both hardware acceleration on and off. Ui is shown below

Figure 116. Screenshot for first layout

6.2.1 MainActivity.java

package com.example.perforamancetest.overdraw_custom_view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.StringRes;
import com.example.performancetest.R;

public class MainActivity extends Activity {

private View mLayout1;
private View mLoremIpsum1;
private View mLayout2;
private View mLoremIpsum2;
// When checked, this enables using hardware layers on the main layouts
private CheckBox mHwLayerCheckbox;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHwLayerCheckbox = (CheckBox) findViewById(R.id.hw_layers_checkbox);
mLayout1 = configureLayout(R.id.layout_1, R.drawable.bg_layout_1, R.string.layout1_title);
mLoremIpsum1 = mLayout1.findViewById(R.id.lorem_ipsum);
mLayout2 = configureLayout(R.id.layout_2, R.drawable.bg_layout_2, R.string.layout2_title);
mLoremIpsum2 = mLayout2.findViewById(R.id.lorem_ipsum);
// Start with one of the two hidden
mLayout2.setAlpha(0);
findViewById(R.id.animate_button).setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
animate();
}
});
}
private View configureLayout(@IdRes int id, @DrawableRes int background, @StringRes int title) {
View layout = findViewById(id);
layout.setBackgroundResource(background);
TextView titleView = (TextView) layout.findViewById(R.id.title);
titleView.setText(title);
return layout;
}
private void animate() {
float layout1Alpha = mLayout1.getAlpha();
if (layout1Alpha != 0 && layout1Alpha != 1) {
// Mid-animation; don't animate
return;
}
final View layoutIn = layout1Alpha == 0 ? mLayout1 : mLayout2;
final View layoutOut = layoutIn == mLayout1 ? mLayout2 : mLayout1;
int widthPixels = getResources().getDisplayMetrics().widthPixels;
float scale = .5f;
layoutIn.setTranslationX(widthPixels / 2);
layoutIn.setScaleX(scale);
layoutIn.setScaleY(scale);
layoutIn.animate()
.alpha(1)
.translationX(0)
.scaleX(1)
.scaleY(1)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (mHwLayerCheckbox.isChecked()) {
layoutIn.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (mHwLayerCheckbox.isChecked()) {
layoutIn.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
})
.start();
layoutOut.animate()
.alpha(0)
.translationX(-widthPixels / 2)
.scaleX(scale)
.scaleY(scale)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
if (mHwLayerCheckbox.isChecked()) {
layoutOut.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
}
@Override
public void onAnimationEnd(Animator animation) {
if (mHwLayerCheckbox.isChecked()) {
layoutOut.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
})
.start();
final View ipsumIn = layoutIn == mLayout1 ? mLoremIpsum1 : mLoremIpsum2;
final View ipsumOut = layoutOut == mLayout2 ? mLoremIpsum2 : mLoremIpsum1;
int heightPixels = getResources().getDisplayMetrics().heightPixels;
int target = heightPixels / 2;
ipsumIn.setTranslationY(target);
ipsumIn.setAlpha(0);
ipsumIn.animate()
.translationY(0)
.alpha(1)
.start();
ipsumOut.animate()
.translationY(target)
.alpha(0)
.start();
}
}

Code 28. MainActivity.java

6.2.2 activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_droid_cards_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.example.perforamancetest.overdraw_custom_view.ImageActivity">
<Button
android:id="@+id/btn_add_cards_View"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="Show Cards View"/>
<ImageView
android:id="@+id/image_view"
android:layout_width="900px"
android:layout_height="600px"
app:layout_constraintTop_toBottomOf="@id/btn_add_cards_View"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout> android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="horizontal"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:orientation="vertical">
<CheckBox
android:id="@+id/hw_layers_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/use_hw_layers" />
</LinearLayout>

<Button
android:id="@+id/animate_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:text="@string/animate" />
</RelativeLayout>

Code 29. activity_main.xml

6.2.3 include_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="8dp"
tools:background="@drawable/bg_layout_1">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="24sp"
android:textStyle="bold"
tools:text="@string/layout1_title" />
<TextView
android:id="@+id/lorem_ipsum"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_weight="1"
android:text="@string/lorem_ipsum" />
</LinearLayout>

Code 30. include_layout.xml

6.2.3 bg_layout_1.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="#88FF88" />
</shape>

Code 31. bg_layout_1.xml

6.2.4 bg_layout_2.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="#88FF88" />
</shape>

Code 32. bg_layout_2.xml

6.3 Tool Test

6.3.1 LayoutInspector Tool Test

6.3.1.1 LayoutInspector on the first layout without hardware layers

Figure 117. LayoutInspector result on the first layout without hardware layers

6.3.1.2 LayoutInspector on the first layout with hardware layers

Figure 118. LayoutInspector result on the first layout with hardware layers

6.3.1.3 LayoutInspector for the second layout without hardware layers

Figure 119. LayoutInspector result for the second layout without hardware layers

6.3.1.4 LayoutInspector for the second layout with hardware layers

Figure 120. LayoutInspector result for the second layout with hardware layers

6.3.1.3 LayoutInspector Tool Test Result

It is clearly visible from the component tree of Figure 117 and Figure 118 that the layout hierarchy for layout 1 remains the same with and without hardware layers. Similarly, Figure 119 and Figure 120 show that the layout hierarchy for layout 2 remains the same. This indicates LayoutInspector is ineffective in detecting any UI performance degradation due to rendering animations without hardware layers.

6.3.2 Debug GPU Overdraw Tool Test

6.3.2.1 Debug GPU Overdraw on the first layout in animation without hardware layers

Figure 121. Debug Gpu overdraw result for the first layout without hardware layers

6.3.2.2 Debug GPU Overdraw on the second layout in animation without hardware layers

Figure 122. Debug Gpu overdraw result for the second layout without hardware layers

6.3.2.3 Debug GPU Overdraw on the second layout in animation with hardware layers

Figure 123. Debug Gpu overdraw result for the second layout with hardware layers

6.3.2.4 Debug GPU Overdraw on second layout in animation with hardware layers

Figure 124. Debug Gpu overdraw result for the second layout with hardware layers

6.3.2.3 Debug GPU Overdraw Tool Test Result

It is clearly visible from the overdraw areas of both layouts without and with hardware rendering there is no difference in results of applying Debug GPU on both Ui . This proves that Debug GPU overdraw tool is ineffective in detecting performance degradation due to animations without hardware layers.

6.3.3 Profile GPU rendering Tool Test

6.3.3.1 Profile GPU rendering on animation without hardware layers

Figure 125. Profile GPU rendering result for animation without Hardware layers

6.3.3.2 Profile GPU rendering on animation with hardware layers

Figure 126. Profile GPU rendering result for animation with Hardware layers

6.3.2.3 Profil GPU rendering Tool Test Result

It is clearly visible from the rendering bars of animation without hardware layers ( Figure 125) and with hardware layers(Figure 126) that the rendering bars for animation with hardware layers are smaller in height than rendering bars without hardware layers. Moreover, the rendering bars for animation with hardware layers are below the green marker line which denotes 16.6 ms frame time. The increase in height of rendering bars without hardware layers is primarily because of the red and yellow lines. The red bars correspond to the command issue which represents the time spent by Android’s 2D renderer issuing commands to OpenGL to draw and redraw display lists. The height of this bar is directly proportional to the sum of the time it takes each display list to execute — more display lists equals a taller red bar. The yellow bar corresponds to Swap Buffers which represents the time the CPU is waiting for the GPU to finish its work.

The above indicates that the profile GPU rendering is effective in detecting the rendering performance issues caused by animation without hardware layers.

6.3.4 Show View Updates Tool Test

6.3.4.1 Show View Updates for Animation without Hardware Layers

The result of applying Show View Updates developer option on animation without hardware layers [31]

6.3.4.2 Show View Updates for Animation with Hardware Layers

The result of applying Show View Updates developer option on animation with hardware layers [32]

6.3.4.3 Show View Updates Tool Test Result

It is clearly visible from the video result for animation without hardware layers[31] and animation with hardware layers[32] that there is no noticeable difference to the human eye in results of applying this tool. This indicates that the Show view updates tool is ineffective in detecting the rendering performance issues caused by animation without hardware layers.

6.3.5 Android CPU Profiler Tool Test

As part of this test, we will click on ‘Animate button’’to play the animation both with and without hardware layers. The same animation will play as shown in [31] and [32]

6.3.5.1 Android CPU Profiler tool test for Animation without Hardware layers
6.3.5.1.1 System Trace CPU Profiler output

Figure 128. The CPU profiler system trace visualization for animation without hardware layers

Figure 128 presents that most of the frames are janky for animation without hardware layers

Figure 129. The CPU profiler system trace — All Frame visualization for animation without hardware layers

Figure 130. The CPU profiler system trace — Janky Frame visualization for animation without hardware layers

In Figure 130, It can be seen that all the frames for animation without hardware layers are janky.

Figure 131. The CPU profiler system trace — This shows that expensive draw calls for every frame for animation without hardware layers

6.3.5.2 Android CPU Profiler tool test for animation with hardware layers
6.3.5.2.1 System Trace

Figure 132. The CPU profiler system trace visualization for animation with hardware layers

Figure 133. The CPU profiler system trace — All Frame visualization for animation with hardware layers

Figure 134. The CPU profiler system trace — Janky Frame visualization for animation with hardware layers

From Figure 134, we see that there is no UI jank when animation is run with hardware layers.

6.3.5.3 Android CPU Profiler Tool Test Result

It is clearly visible from the CPU profiler result for animation without hardware layers and with hardware layers that there is significant reduction in number of jank frames itself by using hardware layers for animation to avoid drawing same view in each frame. This indicates that the CPU profiler tool is effective in detecting the rendering performance issues caused by animation without hardware layers.

This clearly shows that there is 100 percent improvement in number of jank frames by avoiding the rendering of oversize image in imageview.

6.3.6 Tool Test Results on oversize image rendering

--

--