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

Layout Traversals (Droidcon Turin 2015)

Lucas Rocha
April 10, 2015
580

Layout Traversals (Droidcon Turin 2015)

Presented at Droidcon Turin 2015

Lucas Rocha

April 10, 2015
Tweet

Transcript

  1. LAYOUT
    TRAVERSALS
    Droidcon Turin, 2015

    View Slide

  2. LUCAS ROCHA
    +LucasRocha | @lucasratmundo

    View Slide

  3. View Slide

  4. BE DELIBERATE
    Less handwavy UI code

    View Slide

  5. THE BASICS
    From inside out.

    View Slide

  6. UI TOOLKITS
    Layout + Rendering + Input Events

    View Slide

  7. OLD SCHOOL
    Nested boxes, rudimentary motion API.

    View Slide

  8. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. 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

    View Slide

  13. ViewRootImpl
    Connects WindowManager with the
    View framework

    View Slide

  14. THE ROOT
    *
    * SurfaceFlinger
    ViewRootImpl

    View Slide

  15. VIEWS
    Measure + Layout + Draw

    View Slide

  16. TRAVERSAL
    * performTraversals()
    f2
    performTraversals()

    View Slide

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

    View Slide

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

    View Slide

  19. 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

    View Slide

  20. 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

    View Slide

  21. 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()

    View Slide

  22. 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

    View Slide

  23. 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()

    View Slide

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

    View Slide

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

    View Slide

  26. 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”

    View Slide

  27. 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());
    }
    });
    }
    ...
    }

    View Slide

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

    View Slide

  29. TREE OBSERVER
    Use OnPreDrawListener!

    View Slide

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

    View Slide

  31. 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.
    }
    }

    View Slide

  32. CHANGES
    Resize, redraw & animate.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  36. 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

    View Slide

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

    View Slide

  38. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. 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);
    }
    }

    View Slide

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

    View Slide

  46. QUESTIONS?
    +LucasRocha | @lucasratmundo

    View Slide