Initial implementation of requestIdleCallback on Android

Summary:
This is a follow up of the work by brentvatne in #5052. This addresses the feedback by astreet.

- Uses ReactChoreographer with a new callback type
- Callback dispatch logic moved to JS
- Only calls into JS when needed, when there are pending callbacks, it even removes the Choreographer listener when no JS context listen for idle events.

** Test plan **
Tested by running a background task that burns all remaining idle time (see new UIExplorer example) and made sure that UI and JS fps stayed near 60 on a real device (Nexus 6) with dev mode disabled. Also tried adding a JS driven animation and it stayed smooth.

Tested that native only calls into JS when there are pending idle callbacks.

Also tested that timers are executed before idle callback.
```
requestIdleCallback(() => console.log(1));
setTimeout(() => console.log(2), 100);
burnCPU(1000);
// 2
// 1
```

I did *not* test with webworkers but it should work as I'm using executor tokens.
Closes https://github.com/facebook/react-native/pull/8569

Differential Revision: D3558869

Pulled By: astreet

fbshipit-source-id: 61fa82eb26001d2b8c2ea69c35bf3eb5ce5454ba
This commit is contained in:
Janic Duplessis
2016-07-13 18:43:27 -07:00
committed by Facebook Github Bot 5
parent 22eabe59a2
commit 18394fb179
13 changed files with 437 additions and 29 deletions

View File

@@ -52,8 +52,9 @@ public class TimingModuleTest {
private static final long FRAME_TIME_NS = 17 * 1000 * 1000; // 17 ms
private Timing mTiming;
private ReactChoreographer mChoreographerMock;
private ReactChoreographer mReactChoreographerMock;
private PostFrameCallbackHandler mPostFrameCallbackHandler;
private PostFrameIdleCallbackHandler mIdlePostFrameCallbackHandler;
private long mCurrentTimeNs;
private JSTimersExecution mJSTimersMock;
private ExecutorToken mExecutorTokenMock;
@@ -73,12 +74,13 @@ public class TimingModuleTest {
});
PowerMockito.mockStatic(SystemClock.class);
when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000);
when(SystemClock.currentTimeMillis()).thenReturn(mCurrentTimeNs / 1000000);
when(SystemClock.nanoTime()).thenReturn(mCurrentTimeNs);
mChoreographerMock = mock(ReactChoreographer.class);
mReactChoreographerMock = mock(ReactChoreographer.class);
PowerMockito.mockStatic(ReactChoreographer.class);
when(ReactChoreographer.getInstance()).thenReturn(mChoreographerMock);
when(ReactChoreographer.getInstance()).thenReturn(mReactChoreographerMock);
CatalystInstance reactInstance = mock(CatalystInstance.class);
ReactApplicationContext reactContext = mock(ReactApplicationContext.class);
@@ -86,26 +88,49 @@ public class TimingModuleTest {
mCurrentTimeNs = 0;
mPostFrameCallbackHandler = new PostFrameCallbackHandler();
mIdlePostFrameCallbackHandler = new PostFrameIdleCallbackHandler();
doAnswer(mPostFrameCallbackHandler)
.when(mChoreographerMock)
.when(mReactChoreographerMock)
.postFrameCallback(
eq(ReactChoreographer.CallbackType.TIMERS_EVENTS),
any(Choreographer.FrameCallback.class));
doAnswer(mIdlePostFrameCallbackHandler)
.when(mReactChoreographerMock)
.postFrameCallback(
eq(ReactChoreographer.CallbackType.IDLE_EVENT),
any(Choreographer.FrameCallback.class));
mTiming = new Timing(reactContext, mock(DevSupportManager.class));
mJSTimersMock = mock(JSTimersExecution.class);
mExecutorTokenMock = mock(ExecutorToken.class);
when(reactContext.getJSModule(mExecutorTokenMock, JSTimersExecution.class)).thenReturn(mJSTimersMock);
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
((Runnable)invocation.getArguments()[0]).run();
return null;
}
}).when(reactContext).runOnJSQueueThread(any(Runnable.class));
mTiming.initialize();
}
private void stepChoreographerFrame() {
Choreographer.FrameCallback callback = mPostFrameCallbackHandler.getAndResetFrameCallback();
Choreographer.FrameCallback idleCallback = mIdlePostFrameCallbackHandler.getAndResetFrameCallback();
mCurrentTimeNs += FRAME_TIME_NS;
when(SystemClock.uptimeMillis()).thenReturn(mCurrentTimeNs / 1000000);
if (callback != null) {
callback.doFrame(mCurrentTimeNs);
}
if (idleCallback != null) {
idleCallback.doFrame(mCurrentTimeNs);
}
}
@Test
@@ -170,6 +195,33 @@ public class TimingModuleTest {
verify(mJSTimersMock).callTimers(JavaOnlyArray.of(100));
}
@Test
public void testIdleCallback() {
mTiming.onHostResume();
mTiming.setSendIdleEvents(mExecutorTokenMock, true);
stepChoreographerFrame();
verify(mJSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis());
}
private static class PostFrameIdleCallbackHandler implements Answer<Void> {
private Choreographer.FrameCallback mFrameCallback;
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
mFrameCallback = (Choreographer.FrameCallback) args[1];
return null;
}
public Choreographer.FrameCallback getAndResetFrameCallback() {
Choreographer.FrameCallback callback = mFrameCallback;
mFrameCallback = null;
return callback;
}
}
private static class PostFrameCallbackHandler implements Answer<Void> {
private Choreographer.FrameCallback mFrameCallback;