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

Don't Use Hierarchy Viewer !

Slide 15

Slide 15 text

Why ? Sadly, it is way too imprecise. You can measure layou6ng 6me directly with overriden onMeasure/onLayout methods

Slide 16

Slide 16 text

Let's dive in the code !

Slide 17

Slide 17 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 18

Slide 18 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 19

Slide 19 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 20

Slide 20 text

What about the other ViewGroups ? LinearLayout : Simpler ... except if you use weight Any rela:ve placement is costly

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, widthMeasureSpec), 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

GfxInfo adb shell dumpsys gfxinfo package.name

Slide 38

Slide 38 text

** Graphics info for pid 9070 [deezer.android.app] ** Stats since: 327647766133375ns Total frames rendered: 27544 Janky frames: 721 (2.62%) 90th percentile: 12ms 95th percentile: 14ms 99th percentile: 24ms Number Missed Vsync: 54 Number High input latency: 2 Number Slow UI thread: 228 Number Slow bitmap uploads: 43 Number Slow issue draw commands: 400

Slide 39

Slide 39 text

To Conclude

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

Resources • Nexus 5 mockup • Profiling diagram • Perf Colt • Deezer Pixel Droid illustra