Slide 1

Slide 1 text

Let's Sprinkle some #PerfMa(ers on your ViewGroups

Slide 2

Slide 2 text

• "@deezer.com • google.com/+FrancoisBlavoet

Slide 3

Slide 3 text

The Material Transi-on

Slide 4

Slide 4 text

From the Material spec 'Mo$on provides meaning' 'Mo$on is meaningful and appropriate, serving to focus a7en$on and maintain con$nuity'

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

The 16 ms target 1000 ms / 16.7 = 60 frames per second - Cinema : 24 fps - Video games : usually 30 or 60 fps

Slide 8

Slide 8 text

What is Jank ? Problema)c blocking of a so2ware applica)on's user interface due to slow opera)ons1. In prac(ce : • Choppy UI • Discon/nuity of mo/on 1 h$ps:/ /en.wik/onary.org/wiki/jank

Slide 9

Slide 9 text

Let's inves*gate ! Our first tool : SysTrace Displays the execu/on /mes of all the system's processes AndroidSdk/platform-tools/systrace/systrace.py

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

Let's zoom in

Slide 12

Slide 12 text

Fortunately, we can get some advice :

Slide 13

Slide 13 text

Next step : measure layou0ng performances AndroidSdk/platform-tools/hierarchyviewer

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

Let's profile this view tree : hit the profile node bu/on :

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

Let's dive in the code !

Slide 18

Slide 18 text

ViewGroup 2 main methods : protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {} protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}

Slide 19

Slide 19 text

Rela%veLayout At its core, Rela.veLayout is designed around collec.ng and applying constraints : private static final int[] RULES_VERTICAL = { ABOVE, BELOW, ALIGN_BASELINE, ALIGN_TOP, ALIGN_BOTTOM }; private static final int[] RULES_HORIZONTAL = { LEFT_OF, RIGHT_OF, ALIGN_LEFT, ALIGN_RIGHT, START_OF, END_OF, ALIGN_START, ALIGN_END };

Slide 20

Slide 20 text

Rela%veLayout In order to apply these constraints, in onMeasure() : • sort the children according to their rela0ve constraints • apply the horizontal constraints • apply the ver0cal constraints • apply baseline, size wrapping, etc

Slide 21

Slide 21 text

One poten(al solu(on : wri(ng our own ViewGroup Framework classes : • Correctness • Versa,lity • Performances

Slide 22

Slide 22 text

One poten(al solu(on : wri(ng our own ViewGroup Custom implementa.on : • Correctness • Versa,lity Specialized • Performances

Slide 23

Slide 23 text

A simple example

Slide 24

Slide 24 text

Slide 25

Slide 25 text

layout infla+on : inflater.inflate(R.layout.album_view, parent, false);

Slide 26

Slide 26 text

public class AlbumView extends ViewGroup { public AlbumView(Context context) { this(context, null); } public AlbumView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public AlbumView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public AlbumView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context, attrs, defStyleAttr, defStyleRes); } }

Slide 27

Slide 27 text

private ImageView cover; private TextView title; private TextView artist; private ImageView heart; private ImageView overflow; private void init(Context context) { LayoutInflater.from(context).inflate(R.layout.album_view_internal, this, true); cover = (ImageView) findViewById(R.id.cover); title = (TextView) findViewById(R.id.title); artist = (TextView) findViewById(R.id.artist); heart = (ImageView) findViewById(R.id.artist); overflow = (ImageView) findViewById(R.id.artist); }

Slide 28

Slide 28 text

Slide 29

Slide 29 text

@Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); }

Slide 30

Slide 30 text

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthUsed = 0; int heightUsed = 0; measureChildWithMargins(cover, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed); widthUsed += getMeasuredWidthWithMargins(mCoverView); ... } protected static int getMeasuredWidthWithMargins(View child) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); return child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; }

Slide 31

Slide 31 text

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthUsed = 0; int heightUsed = 0; measureChildWithMargins(cover, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed); widthUsed += getMeasuredWidthWithMargins(mCoverView); // same operation for a second child : measureChildWithMargins(overflow, widthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed); widthUsed += getMeasuredWidthWithMargins(mCoverView); ... }

Slide 32

Slide 32 text

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthUsed = 0; int heightUsed = 0; measureChildWithMargins(cover, ...); widthUsed += getMeasuredWidthWithMargins(mCoverView); measureChildWithMargins(overflow, ...); widthUsed += getMeasuredWidthWithMargins(mCoverView); measureChildWithMargins(heart, ...); widthUsed += getMeasuredWidthWithMargins(mCoverView); measureChildWithMargins(title, ...); measureChildWithMargins(artist, ...); widthUsed += getMeasuredWidthWithMargins(mCoverView); setMeasuredDimension(resolveSize(widthUsed, measureSpec), MeasureSpec.getSize(heightMeasureSpec)); }

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); final int paddingRight = getPaddingRight(); int spaceConsumedLeft = paddingLeft; //layout the first view on the left : layoutView(cover, spaceConsumedLeft, paddingTop, cover.getMeasuredWidth(), cover.getMeasuredHeight()); ... } private void layoutView(View view, int l, int t, int width, int height) { final MarginLayoutParams margins = (MarginLayoutParams) view.getLayoutParams(); final int leftWithMargins = l + margins.leftMargin; final int topWithMargins = t + margins.topMargin; view.layout(leftWithMargins, topWithMargins, leftWithMargins + width, topWithMargins + height); }

Slide 35

Slide 35 text

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int paddingLeft = getPaddingLeft(); final int paddingTop = getPaddingTop(); final int paddingRight = getPaddingRight(); int spaceConsumedLeft = paddingLeft; //layout the first view on the left : layoutView(cover,...); spaceConsumedLeft += getMeasuredWidthWithMargin(cover); //layout the overflow button on the right, centered vertically: layoutView(overflow, r - l - paddingRight - overflow.getMeasuredWidth(), (b - t - overflow.getMeasuredHeight()) / 2, overflow.getMeasuredWidth(), overflow.getMeasuredHeight()); int spaceConsumedRight += getMeasuredWidthWithMargin(overflow); ... }

Slide 36

Slide 36 text

The result : • Exact same disposi/on • Flat View hierarchy • Op/mal measure & layout performances

Slide 37

Slide 37 text

Let's dive deeper !

Slide 38

Slide 38 text

Another poten*al bo.leneck : TextView In some cases, TextView can have unsa4sfactory performances : • Long text to display • Combina4on of several Spans

Slide 39

Slide 39 text

public class AlbumView extends ViewGroup { private TextLayoutHelper textHelper; @Override public boolean willNotDraw() { return false; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); textHelper.draw(canvas); } }

Slide 40

Slide 40 text

How to draw a text ?

Slide 41

Slide 41 text

TextView : 10 000 lines of code BUT does not know how to draw a text. It is delegated to an abstract class : android.text.Layout 3 implementa+ons : • BoringLayout • Sta.cLayout • DynamicLayout

Slide 42

Slide 42 text

Did you say boring ? BoringLayout.Metrics boring = BoringLayout.isBoring(mText, mPaint); if (boring != null) { mLayout = BoringLayout.make(mText, mPaint, width, Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, boring, true, TextUtils.TruncateAt.END, width); }

Slide 43

Slide 43 text

Did you say boring ? BoringLayout.Metrics boring = BoringLayout.isBoring(mText, mPaint); if (boring != null) { // this is boring ! if (mSavedLayout != null) { mLayout = mSavedLayout.replaceOrMake(mText, ...); } else { mLayout = BoringLayout.make(mText, ...); } mSavedLayout = (BoringLayout) mLayout;

Slide 44

Slide 44 text

protected final void createLayout(final int width, final int hOffset) { BoringLayout.Metrics boring = BoringLayout.isBoring(mText, mPaint); if (boring != null) { // this is boring ! if (mSavedLayout != null) { mLayout = mSavedLayout.replaceOrMake(mText, ...); } else { mLayout = BoringLayout.make(mText, ...); } mSavedLayout = (BoringLayout) mLayout; } else { mLayout = new StaticLayout(mText, 0, mText.length(), mPaint, width, Layout.Alignment.ALIGN_NORMAL, SPACING_MULT, SPACING_ADD, true, TextUtils.TruncateAt.END, width, MAX_LINES); }

Slide 45

Slide 45 text

Finally, let's draw : public final void draw(final @NonNull Canvas canvas) { if (TextUtils.isEmpty(mText)) return; final int saveCount = canvas.save(); canvas.clipRect(mClipBounds); canvas.translate(mClipBounds.left, mClipBounds.top); mLayout.draw(canvas); canvas.restoreToCount(saveCount); }

Slide 46

Slide 46 text

Advantages : • Total control over the drawing logic, allowing even more customiza7on than with Spans • Allows to mul7thread the text layou7ng logic : instead of crea7ng the text layouts on the main thread, we can create them on a Worker Thread and cache the result.

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

To Conclude

Slide 49

Slide 49 text

Resources • Nexus 5 mockup • Profiling diagram • Perf Colt