From b67d4a20d73f00dd42806b6c275f0982832462ac Mon Sep 17 00:00:00 2001 From: Nathan Spaun Date: Fri, 13 May 2016 11:22:27 -0700 Subject: [PATCH] Add FpsListener to React Scroll Views Summary: We want to give people the ability to log scroll performance (including Fb). This adds an interface that can be enabled and disabled from the react scroll views. This is a prerequisite to implementing the actual framerate logger that will log dropped frames while scrolling in prod. Reviewed By: astreet Differential Revision: D3283588 fbshipit-source-id: ed9736cb9ed3f441511647f36b1460092bd91e56 --- Libraries/Components/ScrollView/ScrollView.js | 9 +++++ .../react/views/scroll/FpsListener.java | 35 ++++++++++++++++++ .../scroll/ReactHorizontalScrollView.java | 35 +++++++++++++++++- .../ReactHorizontalScrollViewManager.java | 23 +++++++++++- .../react/views/scroll/ReactScrollView.java | 37 ++++++++++++++++++- .../views/scroll/ReactScrollViewManager.java | 23 +++++++++++- 6 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/views/scroll/FpsListener.java diff --git a/Libraries/Components/ScrollView/ScrollView.js b/Libraries/Components/ScrollView/ScrollView.js index f0f0b96f565..fdca28932f2 100644 --- a/Libraries/Components/ScrollView/ScrollView.js +++ b/Libraries/Components/ScrollView/ScrollView.js @@ -327,6 +327,15 @@ const ScrollView = React.createClass({ * @platform android */ endFillColor: ColorPropType, + + /** + * Tag used to log scroll performance on this scroll view. Will force + * momentum events to be turned on (see sendMomentumEvents). This doesn't do + * anything out of the box and you need to implement a custom native + * FpsListener for it to be useful. + * @platform android + */ + scrollPerfTag: PropTypes.string, }, mixins: [ScrollResponder.Mixin], diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/FpsListener.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/FpsListener.java new file mode 100644 index 00000000000..dfff3149a0f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/FpsListener.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.views.scroll; + +public interface FpsListener { + + /** + * Clients should call this method when they want the listener to begin recording data. + * + * @param tag + */ + void enable(String tag); + + /** + * Clients should call this method when they want the listener to stop recording data. + * The listener will then report the data it collected. + * + * Calling disable on a listener that has already been disabled is a no-op. + * + * @param tag + */ + void disable(String tag); + + /** + * Reports whether this listener is recording data. + */ + boolean isEnabled(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 71dc9089f83..17a97a90e2e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -44,11 +44,22 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements private boolean mRemoveClippedSubviews; private boolean mScrollEnabled = true; private boolean mSendMomentumEvents; + private @Nullable FpsListener mFpsListener = null; + private @Nullable String mScrollPerfTag; private @Nullable Drawable mEndBackground; private int mEndFillColor = Color.TRANSPARENT; public ReactHorizontalScrollView(Context context) { + this(context, null); + } + + public ReactHorizontalScrollView(Context context, @Nullable FpsListener fpsListener) { super(context); + mFpsListener = fpsListener; + } + + public void setScrollPerfTag(@Nullable String scrollPerfTag) { + mScrollPerfTag = scrollPerfTag; } @Override @@ -117,6 +128,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements NativeGestureUtil.notifyNativeGestureStarted(this, ev); ReactScrollViewHelper.emitScrollBeginDragEvent(this); mDragging = true; + enableFpsListener(); return true; } @@ -193,6 +205,26 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements } } + private void enableFpsListener() { + if (isScrollPerfLoggingEnabled()) { + Assertions.assertNotNull(mFpsListener); + Assertions.assertNotNull(mScrollPerfTag); + mFpsListener.enable(mScrollPerfTag); + } + } + + private void disableFpsListener() { + if (isScrollPerfLoggingEnabled()) { + Assertions.assertNotNull(mFpsListener); + Assertions.assertNotNull(mScrollPerfTag); + mFpsListener.disable(mScrollPerfTag); + } + } + + private boolean isScrollPerfLoggingEnabled() { + return mFpsListener != null && mScrollPerfTag != null && !mScrollPerfTag.isEmpty(); + } + @Override public void draw(Canvas canvas) { if (mEndFillColor != Color.TRANSPARENT) { @@ -214,7 +246,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements @TargetApi(16) private void handlePostTouchScrolling() { // If we aren't going to do anything (send events or snap to page), we can early out. - if (!mSendMomentumEvents && !mPagingEnabled) { + if (!mSendMomentumEvents && !mPagingEnabled && !isScrollPerfLoggingEnabled()) { return; } @@ -253,6 +285,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView implements ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactHorizontalScrollView.this); } ReactHorizontalScrollView.this.mPostTouchRunnable = null; + disableFpsListener(); } else { ReactHorizontalScrollView.this.postOnAnimationDelayed(this, ReactScrollViewHelper.MOMENTUM_DELAY); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java index ba29b980c27..f143803e166 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java @@ -30,6 +30,15 @@ public class ReactHorizontalScrollViewManager implements ReactScrollViewCommandHelper.ScrollCommandHandler { private static final String REACT_CLASS = "AndroidHorizontalScrollView"; + private @Nullable FpsListener mFpsListener = null; + + public ReactHorizontalScrollViewManager() { + this(null); + } + + public ReactHorizontalScrollViewManager(@Nullable FpsListener fpsListener) { + mFpsListener = fpsListener; + } @Override public String getName() { @@ -38,7 +47,7 @@ public class ReactHorizontalScrollViewManager @Override public ReactHorizontalScrollView createViewInstance(ThemedReactContext context) { - return new ReactHorizontalScrollView(context); + return new ReactHorizontalScrollView(context, mFpsListener); } @ReactProp(name = "scrollEnabled", defaultBoolean = true) @@ -69,6 +78,18 @@ public class ReactHorizontalScrollViewManager view.setSendMomentumEvents(sendMomentumEvents); } + /** + * Tag used for logging scroll performance on this scroll view. Will force momentum events to be + * turned on (see setSendMomentumEvents). + * + * @param view + * @param scrollPerfTag + */ + @ReactProp(name = "scrollPerfTag") + public void setScrollPerfTag(ReactHorizontalScrollView view, String scrollPerfTag) { + view.setScrollPerfTag(scrollPerfTag); + } + @ReactProp(name = "pagingEnabled") public void setPagingEnabled(ReactHorizontalScrollView view, boolean pagingEnabled) { view.setPagingEnabled(pagingEnabled); diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index 19014159f6b..7b0926228cc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -45,17 +45,28 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou private boolean mRemoveClippedSubviews; private boolean mScrollEnabled = true; private boolean mSendMomentumEvents; + private @Nullable FpsListener mFpsListener = null; + private @Nullable String mScrollPerfTag; private @Nullable Drawable mEndBackground; private int mEndFillColor = Color.TRANSPARENT; public ReactScrollView(Context context) { + this(context, null); + } + + public ReactScrollView(Context context, @Nullable FpsListener fpsListener) { super(context); + mFpsListener = fpsListener; } public void setSendMomentumEvents(boolean sendMomentumEvents) { mSendMomentumEvents = sendMomentumEvents; } + public void setScrollPerfTag(String scrollPerfTag) { + mScrollPerfTag = scrollPerfTag; + } + public void setScrollEnabled(boolean scrollEnabled) { mScrollEnabled = scrollEnabled; } @@ -118,6 +129,7 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou NativeGestureUtil.notifyNativeGestureStarted(this, ev); ReactScrollViewHelper.emitScrollBeginDragEvent(this); mDragging = true; + enableFpsListener(); return true; } @@ -134,6 +146,7 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou if (action == MotionEvent.ACTION_UP && mDragging) { ReactScrollViewHelper.emitScrollEndDragEvent(this); mDragging = false; + disableFpsListener(); } return super.onTouchEvent(ev); } @@ -175,14 +188,16 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou @Override public void fling(int velocityY) { super.fling(velocityY); - if (mSendMomentumEvents) { + if (mSendMomentumEvents || isScrollPerfLoggingEnabled()) { mFlinging = true; + enableFpsListener(); ReactScrollViewHelper.emitScrollMomentumBeginEvent(this); Runnable r = new Runnable() { @Override public void run() { if (mDoneFlinging) { mFlinging = false; + disableFpsListener(); ReactScrollViewHelper.emitScrollMomentumEndEvent(ReactScrollView.this); } else { mDoneFlinging = true; @@ -194,6 +209,26 @@ public class ReactScrollView extends ScrollView implements ReactClippingViewGrou } } + private void enableFpsListener() { + if (isScrollPerfLoggingEnabled()) { + Assertions.assertNotNull(mFpsListener); + Assertions.assertNotNull(mScrollPerfTag); + mFpsListener.enable(mScrollPerfTag); + } + } + + private void disableFpsListener() { + if (isScrollPerfLoggingEnabled()) { + Assertions.assertNotNull(mFpsListener); + Assertions.assertNotNull(mScrollPerfTag); + mFpsListener.disable(mScrollPerfTag); + } + } + + private boolean isScrollPerfLoggingEnabled() { + return mFpsListener != null && mScrollPerfTag != null && !mScrollPerfTag.isEmpty(); + } + @Override public void draw(Canvas canvas) { if (mEndFillColor != Color.TRANSPARENT) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java index aa5de96fb78..964027d22a4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewManager.java @@ -33,6 +33,15 @@ public class ReactScrollViewManager implements ReactScrollViewCommandHelper.ScrollCommandHandler { private static final String REACT_CLASS = "RCTScrollView"; + private @Nullable FpsListener mFpsListener = null; + + public ReactScrollViewManager() { + this(null); + } + + public ReactScrollViewManager(@Nullable FpsListener fpsListener) { + mFpsListener = fpsListener; + } @Override public String getName() { @@ -41,7 +50,7 @@ public class ReactScrollViewManager @Override public ReactScrollView createViewInstance(ThemedReactContext context) { - return new ReactScrollView(context); + return new ReactScrollView(context, mFpsListener); } @ReactProp(name = "scrollEnabled", defaultBoolean = true) @@ -72,6 +81,18 @@ public class ReactScrollViewManager view.setSendMomentumEvents(sendMomentumEvents); } + /** + * Tag used for logging scroll performance on this scroll view. Will force momentum events to be + * turned on (see setSendMomentumEvents). + * + * @param view + * @param scrollPerfTag + */ + @ReactProp(name = "scrollPerfTag") + public void setScrollPerfTag(ReactScrollView view, String scrollPerfTag) { + view.setScrollPerfTag(scrollPerfTag); + } + /** * When set, fills the rest of the scrollview with a color to avoid setting a background and * creating unnecessary overdraw.