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.

4d2070e800356da57315d427aa2343f4?s=128

François Blavoet

April 07, 2016
Tweet

Transcript

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

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

  3. The Material Transi-on

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

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

    frames per second - Cinema : 24 fps - Video games : usually 30 or 60 fps
  8. 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
  9. 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
  10. None
  11. Let's zoom in

  12. Fortunately, we can get some advice :

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

  14. Don't Use Hierarchy Viewer !

  15. Why ? Sadly, it is way too imprecise. You can

    measure layou6ng 6me directly with overriden onMeasure/onLayout methods
  16. Let's dive in the code !

  17. ViewGroup 2 main methods : protected void onMeasure(int widthMeasureSpec, int

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

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

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

    : • Correctness • Versa,lity Specialized • Performances
  23. A simple example

  24. <RelativeLayout> <ImageView/> <LinearLayout> <TextView/> <TextView/> </LinearLayout> <ImageView/> <ImageView/> </RelativeLayout>

  25. <AlbumView android:layout_width="match_parent" android:layout_height="@dimen/album_height android:paddingLeft="16dp" android:paddingRight="16dp"/> layout infla+on : inflater.inflate(R.layout.album_view, parent,

    false);
  26. 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); } }
  27. 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); }
  28. <merge> <ImageView/> <TextView/> <TextView/> <ImageView/> <ImageView/> </merge>

  29. @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs);

    } @Override protected LayoutParams generateDefaultLayoutParams() { return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); }
  30. @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; }
  31. @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); ... }
  32. @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)); }
  33. None
  34. @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); }
  35. @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); ... }
  36. The result : • Exact same disposi/on • Flat View

    hierarchy • Op/mal measure & layout performances
  37. GfxInfo adb shell dumpsys gfxinfo package.name

  38. ** 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
  39. To Conclude

  40. None
  41. Resources • Nexus 5 mockup • Profiling diagram • Perf

    Colt • Deezer Pixel Droid illustra<on