Slide 1

Slide 1 text

LAYOUT TRAVERSALS Droidcon Turin, 2015

Slide 2

Slide 2 text

LUCAS ROCHA +LucasRocha | @lucasratmundo

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

BE DELIBERATE Less handwavy UI code

Slide 5

Slide 5 text

THE BASICS From inside out.

Slide 6

Slide 6 text

UI TOOLKITS Layout + Rendering + Input Events

Slide 7

Slide 7 text

OLD SCHOOL Nested boxes, rudimentary motion API.

Slide 8

Slide 8 text

PRE-JELLY BEAN public final class ViewRoot extends Handler ... { ... public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; sendEmptyMessage(DO_TRAVERSAL); } } ... public void handleMessage(Message msg) { ... case DO_TRAVERSAL: performTraversals(); ... } } ViewRoot.java

Slide 9

Slide 9 text

MODERNIZE Predictable notion of time that drives input, layout, and motion.

Slide 10

Slide 10 text

TICK TOCK VSYNC sets the pace. No tearing, no extra work.

Slide 11

Slide 11 text

CHOREOGRAPHER f1 f2 f3 f4 f5 . . . . . . VSYNC (60fps) Resize view Redraw view Input Events

Slide 12

Slide 12 text

CHOREOGRAPHER public void onVsync(long timestampNanos, int builtInDisplayId, int frame) { ... scheduleVsync(); ... } ... void doFrame(long frameTimeNanos, int frame) { ... if (!mFrameScheduled) { return; } ... doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); ... } Choreographer.java

Slide 13

Slide 13 text

ViewRootImpl Connects WindowManager with the View framework

Slide 14

Slide 14 text

THE ROOT * * SurfaceFlinger ViewRootImpl

Slide 15

Slide 15 text

VIEWS Measure + Layout + Draw

Slide 16

Slide 16 text

TRAVERSAL * performTraversals() f2 performTraversals()

Slide 17

Slide 17 text

MEASURE * measure(int, int) f2 M M M M M M → onMeasure(int, int) measureHierarchy(...)

Slide 18

Slide 18 text

LAZY MEASURE Multi-MeasureSpec cache, invalidated in requestLayout()

Slide 19

Slide 19 text

LAZY MEASURE public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... int cacheIndex = mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } ... } View.java

Slide 20

Slide 20 text

LAZY MEASURE public void layout(int l, int t, int r, int b) { if ((flags & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } ... } View.java

Slide 21

Slide 21 text

LAYOUT * f2 M L M L M L M L M L M L layout(int, int, int, int) → onLayout(boolean, int, int, int, int) performLayout()

Slide 22

Slide 22 text

FRAME IT public void layout(int l, int t, int r, int b) { ... boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); ... } View.java

Slide 23

Slide 23 text

DRAW * f2 M L D M L D M L D M L D M L D M L D draw(Canvas) → onDraw(Canvas) performDraw()

Slide 24

Slide 24 text

DISPLAY LISTS private DisplayList getDisplayList(...) { ... final HardwareCanvas canvas = displayList.start(width, height); ... draw(canvas); ... displayList.end(); ... } View.java

Slide 25

Slide 25 text

INVARIANTS Layout means measurement is done. Drawing means layout is done.

Slide 26

Slide 26 text

SMELLS 1. getMeasured*() calls outside onLayout() 2. New allocations during traversal · onLayout: ok-ish · onMeasure: avoid · onDraw: nope 3. post(Runnable) to mean “after layout”

Slide 27

Slide 27 text

HAPPENS TO WORK public final class MyActivity extends Activity { ... @Override public void onCreate() { final View myView = findViewById(R.id.some_id); myView.post(new Runnable() { @Override public void run() { Log.d(“LOGTAG”, myView.getWidth()); } }); } ... }

Slide 28

Slide 28 text

LOOPER BARRIERS void scheduleTraversals() { ... mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); ... } ViewRootImpl.java

Slide 29

Slide 29 text

TREE OBSERVER Use OnPreDrawListener!

Slide 30

Slide 30 text

TRANSITIONS http://lucasr.org/?p=3902

Slide 31

Slide 31 text

OnPreDrawListener // 1. Save layout state and wait for next frame. getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { @Override public boolean onPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); // 2. Restore original layout state. // 3. Trigger animators towards new layout state. } }

Slide 32

Slide 32 text

CHANGES Resize, redraw & animate.

Slide 33

Slide 33 text

* requestLayout() f1 f2 f3 f4 f5 . . . . . .

Slide 34

Slide 34 text

requestLayout() public void requestLayout() { ... if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } ... } void scheduleTraversals() { ... mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ... } View.java ViewRootImpl.java

Slide 35

Slide 35 text

* Invalidate(...) f1 f2 f3 f4 f5 . . . . . .

Slide 36

Slide 36 text

invalidate(...) public void invalidateInternal(...) { ... mPrivateFlags |= PFLAG_INVALIDATED; ... if (mParent != null && mAttachInfo != null && l < r && t < b) { final Rect damage = mAttachInfo.mTmpInvalRect; damage.set(l, t, r, b); mParent.invalidateChild(this, damage); } ... } boolean draw(...) { ... mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) == PFLAG_INVALIDATED; ... } View.java

Slide 37

Slide 37 text

* postOnAnimation() f1 f2 f3 f4 f5 . . . . . . ValueAnimator ...

Slide 38

Slide 38 text

postOnAnimation() public void postOnAnimation(Runnable action) { ... attachInfo.mViewRootImpl.mChoreographer.postCallback( Choreographer.CALLBACK_ANIMATION, action, null); ... } void doFrame(long frameTimeNanos, int frame) { ... doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); ... } View.java Choreographer.java

Slide 39

Slide 39 text

BASIC TIPS 1. No layout requests during layout 2. No layout requests during animations 3. Invalidate regions when possible

Slide 40

Slide 40 text

PERFORMANCE 1. Simplify your view hierarchy 2. Avoid multi-pass measurement

Slide 41

Slide 41 text

GO CUSTOM Simplify view hierarchy, performance, missing features. http://lucasr.org/?p=3920

Slide 42

Slide 42 text

PROBE IT! Intercept view methods. Dissect layout traversals. https://github.com/lucasr/probe

Slide 43

Slide 43 text

WRAP VIEW METHODS public class LogRequestLayout extends Interceptor { @Override public void requestLayout(View view) { super.requestLayout(view); Log.d(LOGTAG, “requestLayout() on ” + view); } }

Slide 44

Slide 44 text

DEPLOY public final class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { Probe.deploy(this, new DrawGreen(), new Filter.ViewId(R.id.view2)); super.onCreate(savedInstanceState); setContentView(R.id.main_activity); } }

Slide 45

Slide 45 text

FANCY HACKING ON DEEP LAYOUT STUFF? We're hiring :-)

Slide 46

Slide 46 text

QUESTIONS? +LucasRocha | @lucasratmundo