mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-02 09:21:44 +08:00
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:
committed by
Facebook Github Bot 5
parent
22eabe59a2
commit
18394fb179
@@ -96,6 +96,41 @@ var JSTimers = {
|
||||
return newID;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {function} func Callback to be invoked every frame and provided
|
||||
* with time remaining in frame.
|
||||
*/
|
||||
requestIdleCallback: function(func) {
|
||||
if (!RCTTiming.setSendIdleEvents) {
|
||||
console.warn('requestIdleCallback is not currently supported on this platform');
|
||||
return requestAnimationFrame(func);
|
||||
}
|
||||
|
||||
if (JSTimersExecution.requestIdleCallbacks.length === 0) {
|
||||
RCTTiming.setSendIdleEvents(true);
|
||||
}
|
||||
|
||||
var newID = JSTimersExecution.GUID++;
|
||||
var freeIndex = JSTimers._getFreeIndex();
|
||||
JSTimersExecution.timerIDs[freeIndex] = newID;
|
||||
JSTimersExecution.callbacks[freeIndex] = func;
|
||||
JSTimersExecution.types[freeIndex] = JSTimersExecution.Type.requestIdleCallback;
|
||||
JSTimersExecution.requestIdleCallbacks.push(newID);
|
||||
return newID;
|
||||
},
|
||||
|
||||
cancelIdleCallback: function(timerID) {
|
||||
JSTimers._clearTimerID(timerID);
|
||||
var index = JSTimersExecution.requestIdleCallbacks.indexOf(timerID);
|
||||
if (index !== -1) {
|
||||
JSTimersExecution.requestIdleCallbacks.splice(index, 1);
|
||||
}
|
||||
|
||||
if (JSTimersExecution.requestIdleCallbacks.length === 0) {
|
||||
RCTTiming.setSendIdleEvents(false);
|
||||
}
|
||||
},
|
||||
|
||||
clearTimeout: function(timerID) {
|
||||
JSTimers._clearTimerID(timerID);
|
||||
},
|
||||
@@ -127,7 +162,9 @@ var JSTimers = {
|
||||
// See corresponding comment in `callTimers` for reasoning behind this
|
||||
if (index !== -1) {
|
||||
JSTimersExecution._clearIndex(index);
|
||||
if (JSTimersExecution.types[index] !== JSTimersExecution.Type.setImmediate) {
|
||||
var type = JSTimersExecution.types[index];
|
||||
if (type !== JSTimersExecution.Type.setImmediate &&
|
||||
type !== JSTimersExecution.Type.requestIdleCallback) {
|
||||
RCTTiming.deleteTimer(timerID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ const JSTimersExecution = {
|
||||
setInterval: null,
|
||||
requestAnimationFrame: null,
|
||||
setImmediate: null,
|
||||
requestIdleCallback: null,
|
||||
}),
|
||||
|
||||
// Parallel arrays:
|
||||
@@ -38,13 +39,14 @@ const JSTimersExecution = {
|
||||
types: [],
|
||||
timerIDs: [],
|
||||
immediates: [],
|
||||
requestIdleCallbacks: [],
|
||||
|
||||
/**
|
||||
* Calls the callback associated with the ID. Also unregister that callback
|
||||
* if it was a one time timer (setTimeout), and not unregister it if it was
|
||||
* recurring (setInterval).
|
||||
*/
|
||||
callTimer(timerID) {
|
||||
callTimer(timerID, frameTime) {
|
||||
warning(
|
||||
timerID <= JSTimersExecution.GUID,
|
||||
'Tried to call timer with ID %s but no such timer exists.',
|
||||
@@ -65,7 +67,8 @@ const JSTimersExecution = {
|
||||
// Clear the metadata
|
||||
if (type === JSTimersExecution.Type.setTimeout ||
|
||||
type === JSTimersExecution.Type.setImmediate ||
|
||||
type === JSTimersExecution.Type.requestAnimationFrame) {
|
||||
type === JSTimersExecution.Type.requestAnimationFrame ||
|
||||
type === JSTimersExecution.Type.requestIdleCallback) {
|
||||
JSTimersExecution._clearIndex(timerIndex);
|
||||
}
|
||||
|
||||
@@ -77,6 +80,16 @@ const JSTimersExecution = {
|
||||
} else if (type === JSTimersExecution.Type.requestAnimationFrame) {
|
||||
const currentTime = performanceNow();
|
||||
callback(currentTime);
|
||||
} else if (type === JSTimersExecution.Type.requestIdleCallback) {
|
||||
const { Timing } = require('NativeModules');
|
||||
callback({
|
||||
timeRemaining: function() {
|
||||
// TODO: Optimisation: allow running for longer than one frame if
|
||||
// there are no pending JS calls on the bridge from native. This
|
||||
// would require a way to check the bridge queue synchronously.
|
||||
return Math.max(0, Timing.frameDuration - (performanceNow() - frameTime));
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.error('Tried to call a callback with invalid type: ' + type);
|
||||
return;
|
||||
@@ -99,7 +112,7 @@ const JSTimersExecution = {
|
||||
);
|
||||
|
||||
JSTimersExecution.errors = null;
|
||||
timerIDs.forEach(JSTimersExecution.callTimer);
|
||||
timerIDs.forEach((id) => { JSTimersExecution.callTimer(id); });
|
||||
|
||||
const errors = JSTimersExecution.errors;
|
||||
if (errors) {
|
||||
@@ -118,6 +131,35 @@ const JSTimersExecution = {
|
||||
}
|
||||
},
|
||||
|
||||
callIdleCallbacks: function(frameTime) {
|
||||
const { Timing } = require('NativeModules');
|
||||
|
||||
if (Timing.frameDuration - (performanceNow() - frameTime) < Timing.idleCallbackFrameDeadline) {
|
||||
return;
|
||||
}
|
||||
|
||||
JSTimersExecution.errors = null;
|
||||
|
||||
if (JSTimersExecution.requestIdleCallbacks.length > 0) {
|
||||
const passIdleCallbacks = JSTimersExecution.requestIdleCallbacks.slice();
|
||||
JSTimersExecution.requestIdleCallbacks = [];
|
||||
|
||||
for (let i = 0; i < passIdleCallbacks.length; ++i) {
|
||||
JSTimersExecution.callTimer(passIdleCallbacks[i], frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (JSTimersExecution.requestIdleCallbacks.length === 0) {
|
||||
Timing.setSendIdleEvents(false);
|
||||
}
|
||||
|
||||
if (JSTimersExecution.errors) {
|
||||
JSTimersExecution.errors.forEach((error) =>
|
||||
require('JSTimers').setTimeout(() => { throw error; }, 0)
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a single pass over the enqueued immediates. Returns whether
|
||||
* more immediates are queued up (can be used as a condition a while loop).
|
||||
|
||||
Reference in New Issue
Block a user