support api 15 (use Handler-backed ui driven).

Summary:
Android API 15 still have 1.5~2.0% distribution (refer: [Dashboard - Android Developer](https://developer.android.com/ndk/guides/standalone_toolchain.html#creating_the_toolchain)).

React Native is a good tec but many companies cannot endure loose their consumer. [Choreographer](https://developer.android.com/reference/android/view/Choreographer.html) triggered UI operation is the only reason that React Native Android sdk use minSdkVersion 16, so we can use a backward solution **only in API 15**: [Handler](https://developer.android.com/reference/android/os/Handler.html).

In this PR, the biggest change is :

- Make core operation of ReactChoreographer to an interface: ReactUIDriver;
- Impl ReactUIDriver by Handler => UIDriverHandlerImpl, refactor ReactChoreographer to UIDriverChoreographerImpl;
- Let UIDriverFactory to choose which one impl would be in use. (Only use handler in api 15).
Closes https://github.com/facebook/react-native/pull/12396

Reviewed By: AaaChiuuu

Differential Revision: D4588399

Pulled By: astreet

fbshipit-source-id: 76408e53664314dd926e6a553cde6bafbd37779e
This commit is contained in:
desmondyao
2017-02-27 04:20:51 -08:00
committed by Facebook Github Bot
parent 8b01508410
commit 20ad2b3fbb
18 changed files with 260 additions and 149 deletions

View File

@@ -23,8 +23,8 @@ import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.uimanager.GuardedChoreographerFrameCallback;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.GuardedFrameCallback;
import com.facebook.react.uimanager.UIManagerModule;
import java.util.ArrayList;
@@ -79,7 +79,7 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
}
private final Object mOperationsCopyLock = new Object();
private @Nullable GuardedChoreographerFrameCallback mAnimatedFrameCallback;
private @Nullable GuardedFrameCallback mAnimatedFrameCallback;
private @Nullable ReactChoreographer mReactChoreographer;
private ArrayList<UIThreadOperation> mOperations = new ArrayList<>();
private volatile @Nullable ArrayList<UIThreadOperation> mReadyOperations = null;
@@ -97,7 +97,7 @@ public class NativeAnimatedModule extends ReactContextBaseJavaModule implements
UIManagerModule uiManager = reactCtx.getNativeModule(UIManagerModule.class);
final NativeAnimatedNodesManager nodesManager = new NativeAnimatedNodesManager(uiManager);
mAnimatedFrameCallback = new GuardedChoreographerFrameCallback(reactCtx) {
mAnimatedFrameCallback = new GuardedFrameCallback(reactCtx) {
@Override
protected void doFrameGuarded(final long frameTimeNanos) {

View File

@@ -19,6 +19,7 @@ android_library(
react_native_target("java/com/facebook/react/common/network:network"),
react_native_target("java/com/facebook/react/devsupport:interfaces"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target('java/com/facebook/react/modules/core:core'),
react_native_target("java/com/facebook/react/modules/debug:debug"),
react_native_target("java/com/facebook/react/modules/debug:interfaces"),
react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo"),

View File

@@ -12,7 +12,6 @@ package com.facebook.react.devsupport;
import java.util.Locale;
import android.annotation.TargetApi;
import android.view.Choreographer;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -20,6 +19,7 @@ import com.facebook.common.logging.FLog;
import com.facebook.react.R;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.debug.FpsDebugFrameCallback;
/**
@@ -41,7 +41,7 @@ public class FpsView extends FrameLayout {
super(reactContext);
inflate(reactContext, R.layout.fps_view, this);
mTextView = (TextView) findViewById(R.id.fps_text);
mFrameCallback = new FpsDebugFrameCallback(Choreographer.getInstance(), reactContext);
mFrameCallback = new FpsDebugFrameCallback(ChoreographerCompat.getInstance(), reactContext);
mFPSMonitorRunnable = new FPSMonitorRunnable();
setCurrentFPS(0, 0, 0, 0);
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) 2013, 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.
*
* This file was pulled from the facebook/rebound repository.
*/
package com.facebook.react.modules.core;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.view.Choreographer;
/**
* Wrapper class for abstracting away availability of the JellyBean Choreographer. If Choreographer
* is unavailable we fallback to using a normal Handler.
*/
public class ChoreographerCompat {
private static final long ONE_FRAME_MILLIS = 17;
private static final boolean IS_JELLYBEAN_OR_HIGHER =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
private static final ChoreographerCompat INSTANCE = new ChoreographerCompat();
private Handler mHandler;
private Choreographer mChoreographer;
public static ChoreographerCompat getInstance() {
return INSTANCE;
}
private ChoreographerCompat() {
if (IS_JELLYBEAN_OR_HIGHER) {
mChoreographer = getChoreographer();
} else {
mHandler = new Handler(Looper.getMainLooper());
}
}
public void postFrameCallback(FrameCallback callbackWrapper) {
if (IS_JELLYBEAN_OR_HIGHER) {
choreographerPostFrameCallback(callbackWrapper.getFrameCallback());
} else {
mHandler.postDelayed(callbackWrapper.getRunnable(), 0);
}
}
public void postFrameCallbackDelayed(FrameCallback callbackWrapper, long delayMillis) {
if (IS_JELLYBEAN_OR_HIGHER) {
choreographerPostFrameCallbackDelayed(callbackWrapper.getFrameCallback(), delayMillis);
} else {
mHandler.postDelayed(callbackWrapper.getRunnable(), delayMillis + ONE_FRAME_MILLIS);
}
}
public void removeFrameCallback(FrameCallback callbackWrapper) {
if (IS_JELLYBEAN_OR_HIGHER) {
choreographerRemoveFrameCallback(callbackWrapper.getFrameCallback());
} else {
mHandler.removeCallbacks(callbackWrapper.getRunnable());
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private Choreographer getChoreographer() {
return Choreographer.getInstance();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void choreographerPostFrameCallback(Choreographer.FrameCallback frameCallback) {
mChoreographer.postFrameCallback(frameCallback);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void choreographerPostFrameCallbackDelayed(
Choreographer.FrameCallback frameCallback,
long delayMillis) {
mChoreographer.postFrameCallbackDelayed(frameCallback, delayMillis);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void choreographerRemoveFrameCallback(Choreographer.FrameCallback frameCallback) {
mChoreographer.removeFrameCallback(frameCallback);
}
/**
* This class provides a compatibility wrapper around the JellyBean FrameCallback with methods
* to access cached wrappers for submitting a real FrameCallback to a Choreographer or a Runnable
* to a Handler.
*/
public static abstract class FrameCallback {
private Runnable mRunnable;
private Choreographer.FrameCallback mFrameCallback;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
Choreographer.FrameCallback getFrameCallback() {
if (mFrameCallback == null) {
mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
FrameCallback.this.doFrame(frameTimeNanos);
}
};
}
return mFrameCallback;
}
Runnable getRunnable() {
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
doFrame(System.nanoTime());
}
};
}
return mRunnable;
}
/**
* Just a wrapper for frame callback, see {@link android.view.Choreographer.FrameCallback#doFrame(long)}.
*/
public abstract void doFrame(long frameTimeNanos);
}
}

View File

@@ -11,8 +11,6 @@ package com.facebook.react.modules.core;
import java.util.ArrayDeque;
import android.view.Choreographer;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.infer.annotation.Assertions;
@@ -25,7 +23,7 @@ import com.facebook.react.common.ReactConstants;
*/
public class ReactChoreographer {
public static enum CallbackType {
public enum CallbackType {
/**
* For use by perf markers that need to happen immediately after draw
@@ -75,15 +73,15 @@ public class ReactChoreographer {
return sInstance;
}
private final Choreographer mChoreographer;
private final ChoreographerCompat mChoreographer;
private final ReactChoreographerDispatcher mReactChoreographerDispatcher;
private final ArrayDeque<Choreographer.FrameCallback>[] mCallbackQueues;
private final ArrayDeque<ChoreographerCompat.FrameCallback>[] mCallbackQueues;
private int mTotalCallbacks = 0;
private boolean mHasPostedCallback = false;
private ReactChoreographer() {
mChoreographer = Choreographer.getInstance();
mChoreographer = ChoreographerCompat.getInstance();
mReactChoreographerDispatcher = new ReactChoreographerDispatcher();
mCallbackQueues = new ArrayDeque[CallbackType.values().length];
for (int i = 0; i < mCallbackQueues.length; i++) {
@@ -91,7 +89,7 @@ public class ReactChoreographer {
}
}
public void postFrameCallback(CallbackType type, Choreographer.FrameCallback frameCallback) {
public void postFrameCallback(CallbackType type, ChoreographerCompat.FrameCallback frameCallback) {
UiThreadUtil.assertOnUiThread();
mCallbackQueues[type.getOrder()].addLast(frameCallback);
mTotalCallbacks++;
@@ -102,7 +100,7 @@ public class ReactChoreographer {
}
}
public void removeFrameCallback(CallbackType type, Choreographer.FrameCallback frameCallback) {
public void removeFrameCallback(CallbackType type, ChoreographerCompat.FrameCallback frameCallback) {
UiThreadUtil.assertOnUiThread();
if (mCallbackQueues[type.getOrder()].removeFirstOccurrence(frameCallback)) {
mTotalCallbacks--;
@@ -120,7 +118,7 @@ public class ReactChoreographer {
}
}
private class ReactChoreographerDispatcher implements Choreographer.FrameCallback {
private class ReactChoreographerDispatcher extends ChoreographerCompat.FrameCallback {
@Override
public void doFrame(long frameTimeNanos) {

View File

@@ -22,7 +22,6 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import android.util.SparseArray;
import android.view.Choreographer;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Arguments;
@@ -81,7 +80,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
}
}
private class TimerFrameCallback implements Choreographer.FrameCallback {
private class TimerFrameCallback extends ChoreographerCompat.FrameCallback {
// Temporary map for constructing the individual arrays of timers per ExecutorToken
private final HashMap<ExecutorToken, WritableArray> mTimersToCall = new HashMap<>();
@@ -131,7 +130,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
}
}
private class IdleFrameCallback implements Choreographer.FrameCallback {
private class IdleFrameCallback extends ChoreographerCompat.FrameCallback {
@Override
public void doFrame(long frameTimeNanos) {
@@ -248,14 +247,14 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
@Override
public void onHostPause() {
isPaused.set(true);
clearChoreographerCallback();
maybeClearChoreographerIdleCallback();
clearFrameCallback();
maybeIdleCallback();
}
@Override
public void onHostDestroy() {
clearChoreographerCallback();
maybeClearChoreographerIdleCallback();
clearFrameCallback();
maybeIdleCallback();
}
@Override
@@ -281,14 +280,14 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
HeadlessJsTaskContext.getInstance(getReactApplicationContext());
if (!headlessJsTaskContext.hasActiveTasks()) {
isRunningTasks.set(false);
clearChoreographerCallback();
maybeClearChoreographerIdleCallback();
clearFrameCallback();
maybeIdleCallback();
}
}
@Override
public void onCatalystInstanceDestroy() {
clearChoreographerCallback();
clearFrameCallback();
clearChoreographerIdleCallback();
HeadlessJsTaskContext headlessJsTaskContext =
HeadlessJsTaskContext.getInstance(getReactApplicationContext());
@@ -303,9 +302,9 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
}
}
private void maybeClearChoreographerIdleCallback() {
private void maybeIdleCallback() {
if (isPaused.get() && !isRunningTasks.get()) {
clearChoreographerCallback();
clearFrameCallback();
}
}
@@ -318,7 +317,7 @@ public final class Timing extends ReactContextBaseJavaModule implements Lifecycl
}
}
private void clearChoreographerCallback() {
private void clearFrameCallback() {
HeadlessJsTaskContext headlessJsTaskContext =
HeadlessJsTaskContext.getInstance(getReactApplicationContext());
if (mFrameCallbackPosted && isPaused.get() &&

View File

@@ -13,8 +13,6 @@ import javax.annotation.Nullable;
import java.util.Locale;
import android.os.Build;
import android.view.Choreographer;
import android.widget.Toast;
import com.facebook.common.logging.FLog;
@@ -24,6 +22,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.debug.interfaces.DeveloperSettings;
/**
@@ -60,10 +59,9 @@ public class AnimationsDebugModule extends ReactContextBaseJavaModule {
if (mFrameCallback != null) {
throw new JSApplicationCausedNativeException("Already recording FPS!");
}
checkAPILevel();
mFrameCallback = new FpsDebugFrameCallback(
Choreographer.getInstance(),
ChoreographerCompat.getInstance(),
getReactApplicationContext());
mFrameCallback.startAndRecordFpsAtEachFrame();
}
@@ -78,7 +76,6 @@ public class AnimationsDebugModule extends ReactContextBaseJavaModule {
if (mFrameCallback == null) {
return;
}
checkAPILevel();
mFrameCallback.stop();
@@ -116,11 +113,4 @@ public class AnimationsDebugModule extends ReactContextBaseJavaModule {
mFrameCallback = null;
}
}
private static void checkAPILevel() {
if (Build.VERSION.SDK_INT < 16) {
throw new JSApplicationCausedNativeException(
"Animation debugging is not supported in API <16");
}
}
}

View File

@@ -13,6 +13,7 @@ android_library(
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/module/annotations:annotations"),
react_native_target('java/com/facebook/react/modules/core:core'),
react_native_target("java/com/facebook/react/modules/debug:interfaces"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
],

View File

@@ -9,11 +9,10 @@
package com.facebook.react.modules.debug;
import android.view.Choreographer;
import com.facebook.react.bridge.ReactBridge;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.common.LongArray;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
@@ -22,7 +21,7 @@ import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugL
* to calculate whether JS was able to update the UI during a given frame. After being installed
* on a {@link ReactBridge} and a {@link UIManagerModule},
* {@link #getDidJSHitFrameAndCleanup} should be called once per frame via a
* {@link Choreographer.FrameCallback}.
* {@link ChoreographerCompat.FrameCallback}.
*/
public class DidJSUpdateUiDuringFrameDetector implements NotThreadSafeBridgeIdleDebugListener,
NotThreadSafeViewHierarchyUpdateDebugListener {
@@ -56,7 +55,7 @@ public class DidJSUpdateUiDuringFrameDetector implements NotThreadSafeBridgeIdle
}
/**
* Designed to be called from a {@link Choreographer.FrameCallback#doFrame} call.
* Designed to be called from a {@link ChoreographerCompat.FrameCallback#doFrame} call.
*
* There are two 'success' cases that will cause {@link #getDidJSHitFrameAndCleanup} to
* return true for a given frame:

View File

@@ -14,10 +14,8 @@ import javax.annotation.Nullable;
import java.util.Map;
import java.util.TreeMap;
import android.annotation.TargetApi;
import android.view.Choreographer;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.infer.annotation.Assertions;
@@ -30,11 +28,8 @@ import com.facebook.infer.annotation.Assertions;
* Also records the JS FPS, i.e. the frames per second with which either JS updated the UI or was
* idle and not trying to update the UI. This is different from the FPS above since JS rendering is
* async.
*
* TargetApi 16 for use of Choreographer.
*/
@TargetApi(16)
public class FpsDebugFrameCallback implements Choreographer.FrameCallback {
public class FpsDebugFrameCallback extends ChoreographerCompat.FrameCallback {
public static class FpsInfo {
@@ -66,7 +61,7 @@ public class FpsDebugFrameCallback implements Choreographer.FrameCallback {
private static final double EXPECTED_FRAME_TIME = 16.9;
private final Choreographer mChoreographer;
private final ChoreographerCompat mChoreographer;
private final ReactContext mReactContext;
private final UIManagerModule mUIManagerModule;
private final DidJSUpdateUiDuringFrameDetector mDidJSUpdateUiDuringFrameDetector;
@@ -81,7 +76,7 @@ public class FpsDebugFrameCallback implements Choreographer.FrameCallback {
private boolean mIsRecordingFpsInfoAtEachFrame = false;
private @Nullable TreeMap<Long, FpsInfo> mTimeToFps;
public FpsDebugFrameCallback(Choreographer choreographer, ReactContext reactContext) {
public FpsDebugFrameCallback(ChoreographerCompat choreographer, ReactContext reactContext) {
mChoreographer = choreographer;
mReactContext = reactContext;
mUIManagerModule = reactContext.getNativeModule(UIManagerModule.class);

View File

@@ -9,20 +9,19 @@
package com.facebook.react.uimanager;
import android.view.Choreographer;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.ChoreographerCompat;
/**
* Abstract base for a Choreographer FrameCallback that should have any RuntimeExceptions it throws
* handled by the {@link com.facebook.react.bridge.NativeModuleCallExceptionHandler} registered if
* the app is in dev mode.
*/
public abstract class GuardedChoreographerFrameCallback implements Choreographer.FrameCallback {
public abstract class GuardedFrameCallback extends ChoreographerCompat.FrameCallback {
private final ReactContext mReactContext;
protected GuardedChoreographerFrameCallback(ReactContext reactContext) {
protected GuardedFrameCallback(ReactContext reactContext) {
mReactContext = reactContext;
}

View File

@@ -722,8 +722,7 @@ public class UIViewOperationQueue {
final int reactTag,
final Callback callback) {
mOperations.add(
new MeasureInWindowOperation(reactTag, callback)
);
new MeasureInWindowOperation(reactTag, callback));
}
public void enqueueFindTargetForTouch(
@@ -862,7 +861,7 @@ public class UIViewOperationQueue {
* Using a Choreographer callback (which runs immediately before traversals), we guarantee we run
* before the next traversal.
*/
private class DispatchUIFrameCallback extends GuardedChoreographerFrameCallback {
private class DispatchUIFrameCallback extends GuardedFrameCallback {
private static final int MIN_TIME_LEFT_IN_FRAME_TO_SCHEDULE_MORE_WORK_MS = 8;
private static final int FRAME_TIME_MS = 16;

View File

@@ -17,13 +17,13 @@ import java.util.Comparator;
import java.util.Map;
import android.util.LongSparseArray;
import android.view.Choreographer;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.systrace.Systrace;
@@ -251,7 +251,7 @@ public class EventDispatcher implements LifecycleEventListener {
(((long) coalescingKey) & 0xffff) << 48;
}
private class ScheduleDispatchFrameCallback implements Choreographer.FrameCallback {
private class ScheduleDispatchFrameCallback extends ChoreographerCompat.FrameCallback {
private volatile boolean mIsPosted = false;
private boolean mShouldStop = false;