mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-30 14:02:44 +08:00
Android: add support for headless js tasks
Summary: Provide a base `HeadlessJsTaskService` class that can be extended to run JS in headless mode in response to some event. Added `HeadlessJsTaskEventListener` for modules that are interested in background lifecycle events, and `HeadlessJsTaskContext` that basically extends `ReactContext` without touching it. The react instance is shared with the rest of the app (e.g. activities) through the `ReactNativeHost`. Reviewed By: astreet Differential Revision: D3225753 fbshipit-source-id: 2c5e7679636f31e0e7842d8a67aeb95baf47c563
This commit is contained in:
committed by
Facebook Github Bot
parent
542ab8643e
commit
3080b8d26c
@@ -0,0 +1,139 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.jstasks;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.common.LifecycleState;
|
||||
import com.facebook.react.uimanager.AppRegistry;
|
||||
|
||||
/**
|
||||
* Helper class for dealing with JS tasks. Handles per-ReactContext active task tracking, starting /
|
||||
* stopping tasks and notifying listeners.
|
||||
*/
|
||||
public class HeadlessJsTaskContext {
|
||||
|
||||
private static final WeakHashMap<ReactContext, HeadlessJsTaskContext> INSTANCES =
|
||||
new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Get the task helper instance for a particular {@link ReactContext}. There is only one instance
|
||||
* per context.
|
||||
* <p>
|
||||
* <strong>Note:</strong> do not hold long-lived references to the object returned here, as that
|
||||
* will cause memory leaks. Instead, just call this method on-demand.
|
||||
*/
|
||||
public static HeadlessJsTaskContext getInstance(ReactContext context) {
|
||||
HeadlessJsTaskContext helper = INSTANCES.get(context);
|
||||
if (helper == null) {
|
||||
helper = new HeadlessJsTaskContext(context);
|
||||
INSTANCES.put(context, helper);
|
||||
}
|
||||
return helper;
|
||||
}
|
||||
|
||||
private final ReactContext mReactContext;
|
||||
private final Set<HeadlessJsTaskEventListener> mHeadlessJsTaskEventListeners =
|
||||
new CopyOnWriteArraySet<>();
|
||||
private final AtomicInteger mLastTaskId = new AtomicInteger(0);
|
||||
private final Handler mHandler = new Handler();
|
||||
private final Set<Integer> mActiveTasks = new CopyOnWriteArraySet<>();
|
||||
private final SparseArray<Runnable> mTaskTimeouts = new SparseArray<>();
|
||||
|
||||
private HeadlessJsTaskContext(ReactContext reactContext) {
|
||||
mReactContext = reactContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a task lifecycle event listener.
|
||||
*/
|
||||
public void addTaskEventListener(HeadlessJsTaskEventListener listener) {
|
||||
mHeadlessJsTaskEventListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a task lifecycle event listener.
|
||||
*/
|
||||
public void removeTaskEventListener(HeadlessJsTaskEventListener listener) {
|
||||
mHeadlessJsTaskEventListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether there are any running JS tasks at the moment.
|
||||
*/
|
||||
public boolean hasActiveTasks() {
|
||||
return mActiveTasks.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a JS task. Handles invoking {@link AppRegistry#startHeadlessTask} and notifying
|
||||
* listeners.
|
||||
*
|
||||
* @return a unique id representing this task instance.
|
||||
*/
|
||||
public synchronized int startTask(final HeadlessJsTaskConfig taskConfig) {
|
||||
UiThreadUtil.assertOnUiThread();
|
||||
if (mReactContext.getLifecycleState() == LifecycleState.RESUMED &&
|
||||
!taskConfig.isAllowedInForeground()) {
|
||||
throw new IllegalStateException(
|
||||
"Tried to start task " + taskConfig.getTaskKey() +
|
||||
" while in foreground, but this is not allowed.");
|
||||
}
|
||||
final int taskId = mLastTaskId.incrementAndGet();
|
||||
mReactContext.getJSModule(AppRegistry.class)
|
||||
.startHeadlessTask(taskId, taskConfig.getTaskKey(), taskConfig.getData());
|
||||
if (taskConfig.getTimeout() > 0) {
|
||||
scheduleTaskTimeout(taskId, taskConfig.getTimeout());
|
||||
}
|
||||
mActiveTasks.add(taskId);
|
||||
for (HeadlessJsTaskEventListener listener : mHeadlessJsTaskEventListeners) {
|
||||
listener.onHeadlessJsTaskStart(taskId);
|
||||
}
|
||||
return taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish a JS task. Doesn't actually stop the task on the JS side, only removes it from the list
|
||||
* of active tasks and notifies listeners.
|
||||
*
|
||||
* @param taskId the unique id returned by {@link #startTask}.
|
||||
*/
|
||||
public synchronized void finishTask(final int taskId) {
|
||||
Assertions.assertCondition(
|
||||
mActiveTasks.remove(taskId),
|
||||
"Tried to finish non-existent task with id " + taskId + ".");
|
||||
Runnable timeout = mTaskTimeouts.get(taskId);
|
||||
if (timeout != null) {
|
||||
mHandler.removeCallbacks(timeout);
|
||||
mTaskTimeouts.remove(taskId);
|
||||
}
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (HeadlessJsTaskEventListener listener : mHeadlessJsTaskEventListeners) {
|
||||
listener.onHeadlessJsTaskFinish(taskId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleTaskTimeout(final int taskId, long timeout) {
|
||||
Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finishTask(taskId);
|
||||
}
|
||||
};
|
||||
mTaskTimeouts.append(taskId, runnable);
|
||||
mHandler.postDelayed(runnable, timeout);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user