4. Testing the effectiveness of different Android Tools in detecting performance issues caused by Overdraw in Custom Views

Abhishek Luthra
15 min readDec 18, 2022

--

4.1 Introduction

Many times, a view is drawn which is obscured by another view on top of it. This leads to extra overdrawing and which is wasted processing power. For the default widgets available in the android framework, the code handles avoiding overdrawing when the widget is obscured by another view. This is called clipping in Android.

When custom views are created in Android, the Android framework is not aware of how drawing is going to happen and completely relies on code inside onDraw call inside view to draw the view on the screen. If hidden parts of the custom view are also drawn, it leads to extra CPU and GPU processing power and can contribute to UI jank.

4.2 Code Implementation of Test

Figure 86 shows a custom view drawing of a stack of 30 cards with names and bitmap images. A major portion of all the cards is obscured apart from last card. . If Android goes on to draw all the cards completely, the system wastes a lot of CPU and GPU processing in drawing those cards. This contributes to UI jank as a lot of CPU and GPU time is spent in rendering the first two cards which is not even needed.

In order to test this pattern, we run tests using tools on two different views rendering the same visualization on the screen. One view will draw all the thirty cards completely[86] and the other view will just draw areas of the cards which are visible on the screen[87]. We will run our tools against the overdrawn view to check their effectiveness in detecting UI rendering performance degradation due to overdrawing in custom views. We will then verify our hypothesis of performance degradation due to overdrawing in custom views, by running the tools against an optimized view in which only visible portions of the cards are drawn.

4.2.1 CardsActivity.java

package com.example.perforamancetest.overdraw_custom_view;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.example.performancetest.R;

/**
* Demonstration Activity to present a view showing overlapping stack of cards. This will demonstrate difference in UI rendering performance
* for both View with extra drawing and another with clipping to avoid drawing areas of the screen which are not visible
*/
public class CardsActivity extends Activity {
public static final String TAG = "droid-cards-activity";
protected static final float DROID_IMAGE_WIDTH = 420f;
protected static final float CARD_SPACING = DROID_IMAGE_WIDTH / 20;
private FrameLayout containerView;

private ConstraintLayout mdroidCardsContainer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cards);
mdroidCardsContainer = findViewById(R.id.activity_droid_cards_container);
containerView = findViewById(R.id.container_cards_view);
CardModel[] cardModels = new CardModel[30];
for(int index =0; index❤0; index++){
if(index%3 ==0){
cardModels[index] = new CardModel("Joanna", R.color.joanna_color, R.drawable.joanna);
}
else if(index%3 ==1){
cardModels[index] = new CardModel("Shailen", R.color.shailen_color, R.drawable.shailen);
}
else {
cardModels[index] = new CardModel("Chris", R.color.chris_color, R.drawable.chris);
}
}
final UnOptimizedCardsView droidCardView = new UnOptimizedCardsView(
this,
cardModels,
DROID_IMAGE_WIDTH,
CARD_SPACING
);
droidCardView.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.WRAP_CONTENT));
findViewById(R.id.btn_add_cards_View).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
containerView.addView(droidCardView);
}
});

}
}

Code 20. CardsActivity.java

4.2.2 activity_cards.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.CardsActivity">
<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"/>
<FrameLayout
android:id="@+id/container_cards_view"
android:layout_width="0dp"
android:layout_height="0dp"
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>

Code 21. activity_cards.xml

4.2.3 CardModel.java

package com.example.perforamancetest.overdraw_custom_view;
public class CardModel {
private String name;

private int color;

private int imageId;

public CardModel(String name, int color) {
this.name = name;
this.color = color;
}
public CardModel(String name, int color, int imageId) {
this.name = name;
this.color = color;
this.imageId = imageId;
}
public String getName() {return name;}
public int getColor() {return color;}
public int getAvatarId() {return imageId;}
}

Code 22. CardModel.java.

4.2.4 Card.java

package com.example.perforamancetest.overdraw_custom_view;
import android.graphics.Bitmap;

class Card {
protected static final float SPACE_AROUND_IMAGE = 20f;
protected static final float BITMAP_HEIGHT_HEADER_HEIGHT_RATIO = .25f;
private CardModel mCardModel;
private Bitmap mBitmap;
private float mHeaderHeight;
private float mBodyHeight;
private float mTitleSize;
Card(CardModel cardModel, Bitmap bitmap) {
mCardModel = cardModel;
mBitmap = bitmap;
mBodyHeight = mBitmap.getHeight() + SPACE_AROUND_IMAGE;
mHeaderHeight = mBitmap.getHeight() * BITMAP_HEIGHT_HEADER_HEIGHT_RATIO;
mTitleSize = mHeaderHeight / 2;
}
private String logDimensions() {
return "mBodyHeight = " + mBodyHeight +
", mHeaderHeight = " + mHeaderHeight +
", mTitleSize = " + mTitleSize +
", getWidth() = " + String.valueOf(getWidth());
}
protected float getWidth() {
return mBitmap.getWidth() + (2 * SPACE_AROUND_IMAGE);
}
protected float getBodyHeight() {
return mBodyHeight;
}
protected float getHeaderHeight() {
return mHeaderHeight;
}
protected float getHeight() {
return getBodyHeight() + getHeaderHeight();
}
protected float getTitleSize() {
return mTitleSize;
}
protected Bitmap getBitmap() {
return mBitmap;
}
protected CardModel getCard() {
return mCardModel;
}
protected float getTitleXOffset() {
return SPACE_AROUND_IMAGE;
}
protected float getTitleYOffset() {
return SPACE_AROUND_IMAGE;
}
}

Code 23. Card.java

4.2.5 UnoptimizedCardsView.java with complete drawing of overlapping cards

package com.example.perforamancetest.overdraw_custom_view;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.view.View;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* A custom view that presents a stacked overlapping collection of cards. OnDraw method draws the all cards
* completely irrespective of whether the entire card is visible or not
*/
class UnOptimizedCardsView extends View {
/**
* Array of cards to be displayed in the view.
*/
private CardModel[] mCardModels;
/**
* The width of the card that needs to be shown
*/
float mImageWidth;
/**
* THe difference in left edge positions of two consecutive image cards.
*/
protected float mCardSpacing;
/**
* The position of the left edge of each card in the view
*/
private float mCardLeft;
/**
* List of cards which needs to be shown in the view
*/
private ArrayList<Card> mCards = new ArrayList<Card>();
/**
*
* @param context application context .
* @param cardModels card models for each card to be displayed.
* @param imageWidth The width of each card which is shown
* @param cardSpacing The difference in left edge positions of two consecutive image cards.
*/
public UnOptimizedCardsView(Context context, CardModel[] cardModels, float imageWidth,
float cardSpacing) {
super(context);
mCardModels = cardModels;
mImageWidth = imageWidth;
mCardSpacing = cardSpacing;
// Fire AsyncTasks to fetch and scale the bitmaps.
for (int i = 0; i < mCardModels.length; i++) {
new CardWorkerTask().execute(mCardModels[i]);
}
}
/**
* Drawing implementation to draw all the cards completely .
*/
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Don't draw anything until all the Asynctasks are done and all the Cards are ready.
if (mCardModels.length > 0 && mCards.size() == mCardModels.length) {
// Loop over all the cards, except the last one.
for (int i = 0; i < mCards.size(); i++) {
// Each card is laid out a little to the right of the previous one.
mCardLeft = i * mCardSpacing;
drawCard(canvas, mCards.get(i), mCardLeft, 0);
}
}
// Invalidate the whole view. Doing this calls onDraw() if the view is visible.
invalidate();
}
/**
* Drawing implementation to draw one card
*/
protected void drawCard(Canvas canvas, Card card, float left, float top) {
Paint paint = new Paint();
Bitmap bm = card.getBitmap();
CardModel cardModel = card.getCard();
// Draw outer rectangle.
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
Rect cardRect = new Rect(
(int)left,
(int)top,
(int)left + (int) card.getWidth(),
(int)top + (int) card.getHeight()
);
canvas.drawRect(cardRect, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.DKGRAY);
canvas.drawRect(cardRect, paint);
canvas.drawBitmap(
bm,
(cardRect.left + (card.getWidth() / 2)) - (bm.getWidth() / 2),
(cardRect.top + (int) card.getHeaderHeight() + (card.getBodyHeight() / 2)
- (bm.getHeight() / 2)),
null
);
paint.setTextSize(card.getTitleSize());
paint.setColor(getResources().getColor(cardModel.getColor()));
paint.setStyle(Paint.Style.STROKE);
int titleLeftOffset = cardRect.left + (int) card.getTitleXOffset();
int titleTopOffset = cardRect.top + (int) card.getTitleYOffset() +
(int) card.getTitleSize();
canvas.drawText(cardModel.getName(), titleLeftOffset, titleTopOffset, paint);
}
/**
* converts drawable resource to bitmap
*/
public Bitmap makeBitmap(Resources res, int resId, int reqWidth) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize.
options.inSampleSize = calculateInSampleSize(options, reqWidth);
// Decode bitmap with inSampleSize set.
options.inJustDecodeBounds = false;
// Decode bitmap.
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* Returns a bitmap scaled to the mentioned width.
*/
private Bitmap getScaledBitmap(Bitmap bitmap, float width) {
float scale = width / bitmap.getWidth();
return Bitmap.createScaledBitmap(bitmap, (int) (bitmap.getWidth() * scale),
(int) (bitmap.getHeight() * scale), false);
}
/**
* this methods subsamples the original size image to a smaller image as per the requirement. This saves memory.
*/
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth) {
// Get the raw width of image.
final int width = options.outWidth;
int inSampleSize = 1;
// Calculate the best inSampleSize.
if (width > reqWidth) {
final int halfWidth = width / 2;
while ((halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* Async task which downloads bitmap on another thread and create cards out of it
*/
class CardWorkerTask extends AsyncTask<CardModel, Void, Bitmap> {
CardModel cardModel;
private final WeakReference<ArrayList<Card>> mCardsReference;
public CardWorkerTask() {
mCardsReference = new WeakReference<ArrayList<Card>>(mCards);
}
@Override
protected Bitmap doInBackground(CardModel… params) {
cardModel = params[0];
//
return getScaledBitmap(
makeBitmap(getResources(), cardModel.getAvatarId(), (int) mImageWidth),
mImageWidth
);
}
/**
* Adds cards created using bitmap fetched
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
// Check that the list and bitmap are not null.
if (mCardsReference != null && bitmap != null) {
// Create a new Card.
mCards.add(new Card(cardModel, bitmap));
}
}
}
}

Code 24. UnoptimizedCardsView.java

4.2.5 OptimizedCardsView.java with only drawing of visible parts of the cards

package com.example.perforamancetest.overdraw_custom_view;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.AsyncTask;
import android.view.View;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
* A custom view that presents a stacked overlapping collection of cards. OnDraw method draws only the portion
* of the card that is visible on the screen
*/
class OptimizedCardsView extends View {
/**
* Array of cards to be displayed in the view.
*/
private CardModel[] mCardModels;
/**
* The width of the card that needs to be shown
*/
float mImageWidth;
/**
* THe difference in left edge positions of two consecutive image cards.
*/
protected float mCardSpacing;
/**
* The position of the left edge of each card in the view
*/
private float mCardLeft;
/**
* List of cards which needs to be shown in the view
*/
private ArrayList<Card> mCards = new ArrayList<Card>();
/**
*
* @param context application context .
* @param cardModels card models for each card to be displayed.
* @param imageWidth The width of each card which is shown
* @param cardSpacing The difference in left edge positions of two consecutive image cards.
*/
public OptimizedCardsView(Context context, CardModel[] cardModels, float imageWidth,
float cardSpacing) {
super(context);
mCardModels = cardModels;
mImageWidth = imageWidth;
mCardSpacing = cardSpacing;
// Fire AsyncTasks to fetch and scale the bitmaps.
for (int i = 0; i < mCardModels.length; i++) {
new CardWorkerTask().execute(mCardModels[i]);
}
}
/**
* Drawing implementation to draw all the cards completely .
*/
protected void onDraw(Canvas canvas) {
// Don't draw anything until all the Asynctasks are done and all the DroidCards are ready.
if (mCardModels.length > 0 && mCards.size() == mCardModels.length) {
// Loop over all the droids, except the last one.
int i;
for (i = 0; i < mCards.size() - 1; i++) {
// Each card is laid out a little to the right of the previous one.
mCardLeft = i * mCardSpacing;
// Save the canvas state.
canvas.save();
// Restrict the drawing area to only what will be visible.
canvas.clipRect(
mCardLeft,
0,
mCardLeft + mCardSpacing,
mCards.get(i).getHeight()
);
// Draw the card. Only the parts of the card that lie within the bounds defined by
// the clipRect() get drawn.
drawCard(canvas, mCards.get(i), mCardLeft, 0);
// Revert canvas to non-clipping state.
canvas.restore();
}
// Draw the final card. This one doesn't get clipped.
drawCard(canvas, mCards.get(mCards.size() - 1),
mCardLeft + mCardSpacing, 0);
}
// Invalidate the whole view. Doing this calls onDraw() if the view is visible.
invalidate();
}
/**
* Drawing implementation to draw one card
*/
protected void drawCard(Canvas canvas, Card card, float left, float top) {
Paint paint = new Paint();
Bitmap bm = card.getBitmap();
CardModel cardModel = card.getCard();
// Draw outer rectangle.
paint.setAntiAlias(true);
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
Rect cardRect = new Rect(
(int)left,
(int)top,
(int)left + (int) card.getWidth(),
(int)top + (int) card.getHeight()
);
canvas.drawRect(cardRect, paint);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.DKGRAY);
canvas.drawRect(cardRect, paint);
canvas.drawBitmap(
bm,
(cardRect.left + (card.getWidth() / 2)) - (bm.getWidth() / 2),
(cardRect.top + (int) card.getHeaderHeight() + (card.getBodyHeight() / 2)
- (bm.getHeight() / 2)),
null
);
paint.setTextSize(card.getTitleSize());
paint.setColor(getResources().getColor(cardModel.getColor()));
paint.setStyle(Paint.Style.STROKE);
int titleLeftOffset = cardRect.left + (int) card.getTitleXOffset();
int titleTopOffset = cardRect.top + (int) card.getTitleYOffset() +
(int) card.getTitleSize();
canvas.drawText(cardModel.getName(), titleLeftOffset, titleTopOffset, paint);
}
/**
* converts drawable resource to bitmap
*/
public Bitmap makeBitmap(Resources res, int resId, int reqWidth) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize.
options.inSampleSize = calculateInSampleSize(options, reqWidth);
// Decode bitmap with inSampleSize set.
options.inJustDecodeBounds = false;
// Decode bitmap.
return BitmapFactory.decodeResource(res, resId, options);
}
/**
* Returns a bitmap scaled to the mentioned width.
*/
private Bitmap getScaledBitmap(Bitmap bitmap, float width) {
float scale = width / bitmap.getWidth();
return Bitmap.createScaledBitmap(bitmap, (int) (bitmap.getWidth() * scale),
(int) (bitmap.getHeight() * scale), false);
}
/**
* this methods subsamples the original size image to a smaller image as per the requirement. This saves memory.
*/
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth) {
// Get the raw width of image.
final int width = options.outWidth;
int inSampleSize = 1;
// Calculate the best inSampleSize.
if (width > reqWidth) {
final int halfWidth = width / 2;
while ((halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* Async task which downloads bitmap on another thread and create cards out of it
*/
class CardWorkerTask extends AsyncTask<CardModel, Void, Bitmap> {
CardModel cardModel;
private final WeakReference<ArrayList<Card>> mCardsReference;
public CardWorkerTask() {
mCardsReference = new WeakReference<ArrayList<Card>>(mCards);
}
@Override
protected Bitmap doInBackground(CardModel… params) {
cardModel = params[0];
//
return getScaledBitmap(
makeBitmap(getResources(), cardModel.getAvatarId(), (int) mImageWidth),
mImageWidth
);
}
/**
* Adds cards created using bitmap fetched
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
// Check that the list and bitmap are not null.
if (mCardsReference != null && bitmap != null) {
// Create a new Card.
mCards.add(new Card(cardModel, bitmap));
}
}
}
}

Code 25. OptimizedCardsView.java

4.3 Tool Test

4.3.1 LayoutInspector Tool Test

4.3.1.1 LayoutInspector on UnoptimizedCardView.java

Figure 86. LayoutInspector result for UnoptimizedCardView.java

4.3.1.2 LayoutInspector on OptimizedCardView.java

Figure 87. LayoutInspector result for UnoptimizedCardView.java

4.3.1.3 LayoutInspector Tool Test Result

It is clearly visible from the component tree of Figure 86 and Figure 87 that the layout hierarchy for UnOptimizedCardsView.java and OptimizedCardsView.java is the same.

This indicates LayoutInspector is ineffective in detecting any UI performance degradation due to CustomView Overdrawing.

Table 25. : Table of results of Layout Inspector tool test on UnOptimizedCardsView.java and OptimizedCardsView.java

4.4.3.2 Debug GPU Overdraw Tool Test

4.4.3.2.1 Debug GPU Overdraw on RecyclerView with expensive onBind call

Figure 88. Debug Gpu overdraw result for UnOptimizedCardsView.java

4.4.3.2.2 Debug GPU Overdraw on RecyclerView with efficient onBind call

Figure 89. Debug Gpu overdraw result for OptimizedCardsView.java

4.4.3.2.3 Debug GPU Overdraw Tool Test Result

It is clearly visible from the overdraw areas of UnOptimizedView(Figure 88) and OptimizedView(Figure 89) that Debug GPU overdraw tool clearly highlights inefficient draw operations in UnoptimizedView where drawing for invisible sections of the cards is also done.

Table 26. Table of results of Debug GPU overdraw tool test on UnOptimizedCardsView.java and OptimizedCardsView.java

4.4.3.3 Profile GPU rendering Tool Test

4.4.3.3.1 Profile GPU rendering on UnOptimizedCardsView.java

Figure 90. Profile GPU rendering result for UnOptimizedCardsView.java

4.4.3.3.2 Profile GPU rendering on OptimizedCardsView.java

Figure 91. Profile GPU rendering result for OptimizedCardsView.java

4.4.3.2.3 Profil GPU rendering Tool Test Result

It is clearly visible from the rendering bars of UnOptimizedCardsView.java ( Figure 90) and OptimizedCardsView.java(Figure 91) that the rendering bars with OptimizedCardsView are smaller in height than rendering bars with UnOptimizedCardsView. Moreover, the increase in height of rendering bars for UnOptimizedCardsView is primarily because of the red and orange lines. The red bars correspond to 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 correspond to Swap Buffers which represents the time the CPU is waiting for the GPU to finish its work. If this bar gets tall, it means the app is doing too much work on the GPU.

Above indicates that the profile GPU rendering is effective in detecting the rendering performance issues caused by custom view overdrawing.

4.4.3.4 Show View Updates Tool Test

4.4.3.4.1 Show View Updates for Custom View with Overdraw

The result of applying Show View Updates developer option on Custom View with overdraw is shown in Reference [27]

4.4.3.4.2 Show View Updates for Custom View without Overdraw

The result of applying Show View Updates developer option on Custom View without overdraw is shown in Reference [28]

4.4.3.4.3 Show View Updates Tool Test Result

It is clearly visible from the video result for custom view with overdraw[27] and custom view without overdraw[28] that there is significant difference in results of applying this tool. The view updates slow down when custom view with overdraw is drawn. This indicates that the Show view updates tool is effective in detecting the rendering performance issues caused by overdrawing in custom views.

4.4.3.5 Android CPU Profiler Tool Test

As part of this test, we will click on ‘Show Cards View’ button to inflate and add Cards View for both OptimizedCardsView and UnOptimizedCards. The state at the end of the test looks like

Figure 92. The end state of clicking on ‘SHOW CARDS VIEW’ button.

4.4.3.5.1 Android CPU Profiler tool test for UnOptimizedCardsView.java
4.4.3.5.1.1 System Trace CPU Profiler output

Figure 93. The CPU profiler system trace visualization for UnOptimizedCardsView.java with overdraw of overlapped areas

Figure 93 presents that most of the frames are janky after rendering UnOptimizedCardsView.java

Figure 94. The CPU profiler system trace — All Frame visualization for UnOptimizedCardsView.java with overdraw of overlapped areas

Figure 95. The CPU profiler system trace — Janky Frame visualization for UnOptimizedCardsView.java with overdraw of overlapped areas.

In Figure 95, it can be observed all the frames for UnOptimizedCardsView.java are janky.

Figure 96. The CPU profiler system trace visualization for UnOptimizedCardsView with custom view overdrawing.

In Figure 96, It can be seen that all the jank frames are caused by multiple extended draw calls . This shows that the janks are caused primarily because of overdrawing which is needed to show all the overlapped cards completely

4.4.3.5.2 Android CPU Profiler tool test for OptimizedCardsView.java
4.4.3.5.2.1 System Trace

Figure 97. The CPU profiler system trace visualization for OptimizedCardsView.java without overdraw of overlapped areas

Figure 98. The CPU profiler system trace — All Frame visualization for OptimizedCardsView.java without overdraw of overlapped areas.

Figure 99. The CPU profiler system trace — Janky Frame visualization for for OptimizedCardsView.java without overdraw of overlapped areas.

From Figure 99, we see that there is no UI jank when OptimizedCardsView is rendered on the screen.

4.4.3.5.3 Android CPU Profiler Tool Test Result

It is clearly visible from the CPU profiler result for UnOptimizedCardsView.java and OptimizedCardsView.java that there is significant reduction in number of jank frames itself by using clipRect() in drawing CustomViews to avoid overdrawing overlapped areas of custom views which are not shown on the screen. This indicates that the CPU profiler tool is effective in detecting the rendering performance issues caused by Custom View overdrawing.

This clearly shows that there is 100 percent improvement in number of jank frames by reducing the overdraw caused by complete drawing of overlapped cards.

4.4.3.6 Tool Test Results on overdraw in custom views

--

--