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

@@ -9,8 +9,6 @@
package com.facebook.react.modules.timing;
import android.view.Choreographer;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ExecutorToken;
import com.facebook.react.bridge.ReactApplicationContext;
@@ -18,6 +16,7 @@ import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.common.SystemClock;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.JSTimersExecution;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.modules.core.Timing;
@@ -39,8 +38,8 @@ import static org.mockito.Mockito.*;
/**
* Tests for {@link Timing}.
*/
// DISABLED, BROKEN https://circleci.com/gh/facebook/react-native/12068
// t=13905097
// DISABLED, BROKEN https://circleci.com/gh/facebook/react-native/12068
// t=13905097
@PrepareForTest({Arguments.class, SystemClock.class, ReactChoreographer.class})
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@RunWith(RobolectricTestRunner.class)
@@ -63,12 +62,12 @@ public class TimingModuleTest {
public void prepareModules() {
PowerMockito.mockStatic(Arguments.class);
when(Arguments.createArray()).thenAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return new JavaOnlyArray();
}
});
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return new JavaOnlyArray();
}
});
PowerMockito.mockStatic(SystemClock.class);
when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000);
@@ -88,16 +87,16 @@ public class TimingModuleTest {
mIdlePostFrameCallbackHandler = new PostFrameIdleCallbackHandler();
doAnswer(mPostFrameCallbackHandler)
.when(mReactChoreographerMock)
.postFrameCallback(
eq(ReactChoreographer.CallbackType.TIMERS_EVENTS),
any(Choreographer.FrameCallback.class));
.when(mReactChoreographerMock)
.postFrameCallback(
eq(ReactChoreographer.CallbackType.TIMERS_EVENTS),
any(ChoreographerCompat.FrameCallback.class));
doAnswer(mIdlePostFrameCallbackHandler)
.when(mReactChoreographerMock)
.postFrameCallback(
eq(ReactChoreographer.CallbackType.IDLE_EVENT),
any(Choreographer.FrameCallback.class));
.when(mReactChoreographerMock)
.postFrameCallback(
eq(ReactChoreographer.CallbackType.IDLE_EVENT),
any(ChoreographerCompat.FrameCallback.class));
mTiming = new Timing(reactContext, mock(DevSupportManager.class));
mJSTimersMock = mock(JSTimersExecution.class);
@@ -107,7 +106,7 @@ public class TimingModuleTest {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((Runnable)invocation.getArguments()[0]).run();
((Runnable) invocation.getArguments()[0]).run();
return null;
}
}).when(reactContext).runOnJSQueueThread(any(Runnable.class));
@@ -116,8 +115,8 @@ public class TimingModuleTest {
}
private void stepChoreographerFrame() {
Choreographer.FrameCallback callback = mPostFrameCallbackHandler.getAndResetFrameCallback();
Choreographer.FrameCallback idleCallback = mIdlePostFrameCallbackHandler.getAndResetFrameCallback();
ChoreographerCompat.FrameCallback callback = mPostFrameCallbackHandler.getAndResetFrameCallback();
ChoreographerCompat.FrameCallback idleCallback = mIdlePostFrameCallbackHandler.getAndResetFrameCallback();
mCurrentTimeNs += FRAME_TIME_NS;
when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000);
@@ -259,17 +258,17 @@ public class TimingModuleTest {
private static class PostFrameIdleCallbackHandler implements Answer<Void> {
private Choreographer.FrameCallback mFrameCallback;
private ChoreographerCompat.FrameCallback mFrameCallback;
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
mFrameCallback = (Choreographer.FrameCallback) args[1];
mFrameCallback = (ChoreographerCompat.FrameCallback) args[1];
return null;
}
public Choreographer.FrameCallback getAndResetFrameCallback() {
Choreographer.FrameCallback callback = mFrameCallback;
public ChoreographerCompat.FrameCallback getAndResetFrameCallback() {
ChoreographerCompat.FrameCallback callback = mFrameCallback;
mFrameCallback = null;
return callback;
}
@@ -277,17 +276,17 @@ public class TimingModuleTest {
private static class PostFrameCallbackHandler implements Answer<Void> {
private Choreographer.FrameCallback mFrameCallback;
private ChoreographerCompat.FrameCallback mFrameCallback;
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
mFrameCallback = (Choreographer.FrameCallback) args[1];
mFrameCallback = (ChoreographerCompat.FrameCallback) args[1];
return null;
}
public Choreographer.FrameCallback getAndResetFrameCallback() {
Choreographer.FrameCallback callback = mFrameCallback;
public ChoreographerCompat.FrameCallback getAndResetFrameCallback() {
ChoreographerCompat.FrameCallback callback = mFrameCallback;
mFrameCallback = null;
return callback;
}

View File

@@ -14,7 +14,6 @@ import java.util.Arrays;
import java.util.List;
import android.graphics.Color;
import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -29,6 +28,8 @@ import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactTestHelper;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.views.text.ReactRawTextManager;
import com.facebook.react.views.text.ReactTextShadowNode;
import com.facebook.react.views.text.ReactTextViewManager;
@@ -70,7 +71,7 @@ public class UIManagerModuleTest {
private ReactApplicationContext mReactContext;
private CatalystInstance mCatalystInstanceMock;
private ArrayList<Choreographer.FrameCallback> mPendingChoreographerCallbacks;
private ArrayList<ChoreographerCompat.FrameCallback> mPendingFrameCallbacks;
@Before
public void setUp() {
@@ -91,17 +92,17 @@ public class UIManagerModuleTest {
});
PowerMockito.when(ReactChoreographer.getInstance()).thenReturn(choreographerMock);
mPendingChoreographerCallbacks = new ArrayList<>();
mPendingFrameCallbacks = new ArrayList<>();
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
mPendingChoreographerCallbacks
.add((Choreographer.FrameCallback) invocation.getArguments()[1]);
mPendingFrameCallbacks
.add((ChoreographerCompat.FrameCallback) invocation.getArguments()[1]);
return null;
}
}).when(choreographerMock).postFrameCallback(
any(ReactChoreographer.CallbackType.class),
any(Choreographer.FrameCallback.class));
any(ChoreographerCompat.FrameCallback.class));
mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
@@ -139,7 +140,7 @@ public class UIManagerModuleTest {
JavaOnlyMap.of(ReactTextShadowNode.PROP_TEXT, "New text"));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(textView.getText().toString()).isEqualTo("New text");
}
@@ -182,7 +183,7 @@ public class UIManagerModuleTest {
null);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(rootView.getChildCount()).isEqualTo(1);
@@ -213,7 +214,7 @@ public class UIManagerModuleTest {
null);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertChildrenAreExactly(
hierarchy.nativeRootView,
@@ -240,7 +241,7 @@ public class UIManagerModuleTest {
JavaOnlyArray.of(0, 3));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertChildrenAreExactly(
hierarchy.nativeRootView,
@@ -266,7 +267,7 @@ public class UIManagerModuleTest {
JavaOnlyArray.of(1));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertChildrenAreExactly(
hierarchy.nativeRootView,
@@ -289,7 +290,7 @@ public class UIManagerModuleTest {
JavaOnlyArray.of(3));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
}
@Test(expected = IllegalViewOperationException.class)
@@ -306,7 +307,7 @@ public class UIManagerModuleTest {
JavaOnlyArray.of(3, 3));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
}
@Test
@@ -335,7 +336,7 @@ public class UIManagerModuleTest {
null);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(hierarchy.nativeRootView.getChildCount()).isEqualTo(5);
assertThat(hierarchy.nativeRootView.getChildAt(0)).isEqualTo(expectedViewAt0);
@@ -363,7 +364,7 @@ public class UIManagerModuleTest {
null);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertChildrenAreExactly(
hierarchy.nativeRootView,
@@ -392,7 +393,7 @@ public class UIManagerModuleTest {
JavaOnlyArray.of(1));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertChildrenAreExactly(
hierarchy.nativeRootView,
@@ -424,7 +425,7 @@ public class UIManagerModuleTest {
null);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
View newView = hierarchy.nativeRootView.getChildAt(4);
assertThat(newView.getLeft()).isEqualTo(10);
@@ -472,7 +473,7 @@ public class UIManagerModuleTest {
JavaOnlyArray.of(4));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(hierarchy.nativeRootView.getChildCount()).isEqualTo(4);
}
@@ -518,7 +519,7 @@ public class UIManagerModuleTest {
ReactViewManager.REACT_CLASS,
JavaOnlyMap.of("left", 10.0, "top", 20.0, "width", 30.0, "height", 40.0));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(view0.getLeft()).isGreaterThan(2);
// verify that the layout doesn't get updated when we update style property not affecting the
@@ -529,7 +530,7 @@ public class UIManagerModuleTest {
ReactViewManager.REACT_CLASS,
JavaOnlyMap.of("backgroundColor", Color.RED));
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(view0.getLeft()).isEqualTo(1);
}
@@ -565,7 +566,7 @@ public class UIManagerModuleTest {
uiManagerModule.removeAnimation(hierarchy.rootView, 1000);
uiManagerModule.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
verify(callbackMock, times(1)).invoke(false);
verify(mockAnimation).run();
@@ -591,7 +592,7 @@ public class UIManagerModuleTest {
uiManager.replaceExistingNonRootView(hierarchy.view2, newViewTag);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(hierarchy.nativeRootView.getChildCount()).isEqualTo(4);
assertThat(hierarchy.nativeRootView.getChildAt(2)).isInstanceOf(ReactViewGroup.class);
@@ -640,7 +641,7 @@ public class UIManagerModuleTest {
addChild(uiManager, containerTag, containerTag + 3, 1);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(rootView.getChildCount()).isEqualTo(2);
assertThat(((ViewGroup) rootView.getChildAt(0)).getChildCount()).isEqualTo(2);
@@ -648,7 +649,7 @@ public class UIManagerModuleTest {
uiManager.removeSubviewsFromContainerWithID(containerTag);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
assertThat(rootView.getChildCount()).isEqualTo(2);
assertThat(((ViewGroup) rootView.getChildAt(0)).getChildCount()).isEqualTo(0);
@@ -693,7 +694,7 @@ public class UIManagerModuleTest {
null);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
return rootView;
}
@@ -744,7 +745,7 @@ public class UIManagerModuleTest {
addChild(uiManager, hierarchy.viewWithChildren1, hierarchy.childView1, 1);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
return hierarchy;
}
@@ -803,11 +804,11 @@ public class UIManagerModuleTest {
}
}
private void executePendingChoreographerCallbacks() {
ArrayList<Choreographer.FrameCallback> callbacks =
new ArrayList<>(mPendingChoreographerCallbacks);
mPendingChoreographerCallbacks.clear();
for (Choreographer.FrameCallback frameCallback : callbacks) {
private void executePendingFrameCallbacks() {
ArrayList<ChoreographerCompat.FrameCallback> callbacks =
new ArrayList<>(mPendingFrameCallbacks);
mPendingFrameCallbacks.clear();
for (ChoreographerCompat.FrameCallback frameCallback : callbacks) {
frameCallback.doFrame(0);
}
}

View File

@@ -23,7 +23,6 @@ import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.UnderlineSpan;
import android.view.Choreographer;
import android.widget.TextView;
import com.facebook.react.ReactRootView;
@@ -32,7 +31,8 @@ import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactTestHelper;
import com.facebook.react.uimanager.ReactChoreographer;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
@@ -68,32 +68,32 @@ public class ReactTextTest {
@Rule
public PowerMockRule rule = new PowerMockRule();
private ArrayList<Choreographer.FrameCallback> mPendingChoreographerCallbacks;
private ArrayList<ChoreographerCompat.FrameCallback> mPendingFrameCallbacks;
@Before
public void setUp() {
PowerMockito.mockStatic(Arguments.class, ReactChoreographer.class);
ReactChoreographer choreographerMock = mock(ReactChoreographer.class);
ReactChoreographer uiDriverMock = mock(ReactChoreographer.class);
PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return new JavaOnlyMap();
}
});
PowerMockito.when(ReactChoreographer.getInstance()).thenReturn(choreographerMock);
PowerMockito.when(ReactChoreographer.getInstance()).thenReturn(uiDriverMock);
mPendingChoreographerCallbacks = new ArrayList<>();
mPendingFrameCallbacks = new ArrayList<>();
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
mPendingChoreographerCallbacks
.add((Choreographer.FrameCallback) invocation.getArguments()[1]);
mPendingFrameCallbacks
.add((ChoreographerCompat.FrameCallback) invocation.getArguments()[1]);
return null;
}
}).when(choreographerMock).postFrameCallback(
}).when(uiDriverMock).postFrameCallback(
any(ReactChoreographer.CallbackType.class),
any(Choreographer.FrameCallback.class));
any(ChoreographerCompat.FrameCallback.class));
}
@Test
@@ -411,15 +411,15 @@ public class ReactTextTest {
null);
uiManager.onBatchComplete();
executePendingChoreographerCallbacks();
executePendingFrameCallbacks();
return rootView;
}
private void executePendingChoreographerCallbacks() {
ArrayList<Choreographer.FrameCallback> callbacks =
new ArrayList<>(mPendingChoreographerCallbacks);
mPendingChoreographerCallbacks.clear();
for (Choreographer.FrameCallback frameCallback : callbacks) {
private void executePendingFrameCallbacks() {
ArrayList<ChoreographerCompat.FrameCallback> callbacks =
new ArrayList<>(mPendingFrameCallbacks);
mPendingFrameCallbacks.clear();
for (ChoreographerCompat.FrameCallback frameCallback : callbacks) {
frameCallback.doFrame(0);
}
}

View File

@@ -13,7 +13,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.view.Choreographer;
import android.widget.EditText;
import com.facebook.react.ReactRootView;
@@ -22,8 +21,8 @@ import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactTestHelper;
import com.facebook.react.uimanager.ReactChoreographer;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.modules.core.ChoreographerCompat;
import com.facebook.react.modules.core.ReactChoreographer;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
@@ -58,7 +57,7 @@ public class TextInputTest {
@Rule
public PowerMockRule rule = new PowerMockRule();
private ArrayList<Choreographer.FrameCallback> mPendingChoreographerCallbacks;
private ArrayList<ChoreographerCompat.FrameCallback> mPendingChoreographerCallbacks;
@Before
public void setUp() {
@@ -78,12 +77,12 @@ public class TextInputTest {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
mPendingChoreographerCallbacks
.add((Choreographer.FrameCallback) invocation.getArguments()[1]);
.add((ChoreographerCompat.FrameCallback) invocation.getArguments()[1]);
return null;
}
}).when(choreographerMock).postFrameCallback(
any(ReactChoreographer.CallbackType.class),
any(Choreographer.FrameCallback.class));
any(ChoreographerCompat.FrameCallback.class));
}
@Test
@@ -169,10 +168,10 @@ public class TextInputTest {
}
private void executePendingChoreographerCallbacks() {
ArrayList<Choreographer.FrameCallback> callbacks =
ArrayList<ChoreographerCompat.FrameCallback> callbacks =
new ArrayList<>(mPendingChoreographerCallbacks);
mPendingChoreographerCallbacks.clear();
for (Choreographer.FrameCallback frameCallback : callbacks) {
for (ChoreographerCompat.FrameCallback frameCallback : callbacks) {
frameCallback.doFrame(0);
}
}