Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Let's Sprinkle some #PerfMatters on your ViewGroups - Droidcon Turin 2016

Let's Sprinkle some #PerfMatters on your ViewGroups - Droidcon Turin 2016

Framework classes like RelativeLayout are extremely powerful but their first goal is to be versatile. This comes with significant performances costs that can prevent your app from being fast and smooth, especially in constrained areas like list scrolling. Let's see how we can remedy this by writing our own layouting and drawing logic where it matters.

François Blavoet

April 07, 2016
Tweet

More Decks by François Blavoet

Other Decks in Programming

Transcript

  1. From the Material spec 'Mo$on provides meaning' 'Mo$on is meaningful

    and appropriate, serving to focus a7en$on and maintain con$nuity'
  2. The 16 ms target 1000 ms / 16.7 = 60

    frames per second - Cinema : 24 fps - Video games : usually 30 or 60 fps
  3. 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
  4. 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
  5. Why ? Sadly, it is way too imprecise. You can

    measure layou6ng 6me directly with overriden onMeasure/onLayout methods
  6. ViewGroup 2 main methods : protected void onMeasure(int widthMeasureSpec, int

    heightMeasureSpec) {} protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}
  7. 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 };
  8. 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
  9. What about the other ViewGroups ? LinearLayout : Simpler ...

    except if you use weight Any rela:ve placement is costly
  10. One poten(al solu(on : wri(ng our own ViewGroup Framework classes

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

    : • Correctness • Versa,lity Specialized • Performances
  12. 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); } }
  13. 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); }
  14. @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs);

    } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); }
  15. @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; }
  16. @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); ... }
  17. @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)); }
  18. @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); }
  19. @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); ... }
  20. The result : • Exact same disposi/on • Flat View

    hierarchy • Op/mal measure & layout performances
  21. ** 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
  22. Resources • Nexus 5 mockup • Profiling diagram • Perf

    Colt • Deezer Pixel Droid illustra<on