跳至主内容

无界面 JS

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

无界面 JS(Headless JS)允许应用在后台运行时执行 JavaScript 任务。典型应用场景包括同步最新数据、处理推送通知或播放音乐等。

JS API

任务是一个异步函数,需要在 AppRegistry 中注册,类似于注册 React 应用的方式:

tsx
import {AppRegistry} from 'react-native';
AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName'),
);

SomeTaskName.js 文件中:

tsx
module.exports = async taskData => {
// do stuff
};

任务中可以执行网络请求、定时器等任意操作(只要不涉及 UI)。当任务完成(即 Promise 状态变为 resolved)后,React Native 会进入"暂停"模式(除非有其他任务正在运行或应用处于前台)。

平台 API

此功能仍需要少量原生代码支持。你需要继承 HeadlessJsTaskService 并重写 getTaskConfig 方法,例如:

java
package com.your_application_name;

import android.content.Intent;
import android.os.Bundle;
import com.facebook.react.HeadlessJsTaskService;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
import javax.annotation.Nullable;

public class MyTaskService extends HeadlessJsTaskService {

@Override
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
return new HeadlessJsTaskConfig(
"SomeTaskName",
Arguments.fromBundle(extras),
5000, // timeout in milliseconds for the task
false // optional: defines whether or not the task is allowed in foreground. Default is false
);
}
return null;
}
}

然后在 AndroidManifest.xml 文件的 application 标签内注册该服务:

xml
<service android:name="com.example.MyTaskService" />

此后,当你启动服务时(例如作为周期性任务或响应系统事件/广播),JS 引擎会启动并执行任务,完成后自动关闭。

示例:

java
Intent service = new Intent(getApplicationContext(), MyTaskService.class);
Bundle bundle = new Bundle();

bundle.putString("foo", "bar");
service.putExtras(bundle);

getApplicationContext().startForegroundService(service);

重试机制

默认情况下,无界面 JS 任务不会自动重试。如需启用重试,需创建 HeadlessJsRetryPolicy 并抛出特定 Error

LinearCountingRetryPolicyHeadlessJsRetryPolicy 的实现类,支持设置最大重试次数和固定重试间隔。如需自定义策略,可自行实现 HeadlessJsRetryPolicy。这些策略可作为额外参数传递给 HeadlessJsTaskConfig 构造函数,例如:

java
HeadlessJsRetryPolicy retryPolicy = new LinearCountingRetryPolicy(
3, // Max number of retry attempts
1000 // Delay between each retry attempt
);

return new HeadlessJsTaskConfig(
'SomeTaskName',
Arguments.fromBundle(extras),
5000,
false,
retryPolicy
);

仅当抛出特定 Error 时才会触发重试。在无界面 JS 任务中,可导入该错误并在需要重试时抛出:

示例:

tsx
import {HeadlessJsTaskError} from 'HeadlessJsTask';

module.exports = async taskData => {
const condition = ...;
if (!condition) {
throw new HeadlessJsTaskError();
}
};

若需所有错误都触发重试,需捕获错误并手动抛出上述特定错误。

注意事项

  • 默认情况下,应用在前台运行时尝试执行任务会导致崩溃。此机制防止开发者因任务负载过大导致 UI 卡顿。可通过第四个 boolean 参数控制此行为。

  • BroadcastReceiver 启动服务时,务必在 onReceive() 返回前调用 HeadlessJsTaskService.acquireWakeLockNow()

使用示例

可通过 Java API 启动服务。首先需确定服务启动时机并实现相应逻辑。以下示例演示响应网络连接变化:

在 AndroidManifest.xml 中注册广播接收器的部分代码:

xml
<receiver android:name=".NetworkChangeReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>

广播接收器在 onReceive 函数中处理广播的 Intent。此处可检测应用是否处于前台:若非前台状态,可准备启动服务的 Intent(通过 putExtra 捆绑附加信息,注意仅支持 Parcelable 类型值)。最后启动服务并获取唤醒锁。

java
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;

import com.facebook.react.HeadlessJsTaskService;

public class NetworkChangeReceiver extends BroadcastReceiver {

@Override
public void onReceive(final Context context, final Intent intent) {
/**
This part will be called every time network connection is changed
e.g. Connected -> Not Connected
**/
if (!isAppOnForeground((context))) {
/**
We will start our service and send extra info about
network connections
**/
boolean hasInternet = isNetworkAvailable(context);
Intent serviceIntent = new Intent(context, MyTaskService.class);
serviceIntent.putExtra("hasInternet", hasInternet);
context.startForegroundService(serviceIntent);
HeadlessJsTaskService.acquireWakeLockNow(context);
}
}

private boolean isAppOnForeground(Context context) {
/**
We need to check if app is in foreground otherwise the app will crash.
https://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
**/
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses =
activityManager.getRunningAppProcesses();
if (appProcesses == null) {
return false;
}
final String packageName = context.getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.importance ==
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
appProcess.processName.equals(packageName)) {
return true;
}
}
return false;
}

public static boolean isNetworkAvailable(Context context) {
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network networkCapabilities = cm.getActiveNetwork();

if(networkCapabilities == null) {
return false;
}

NetworkCapabilities actNw = cm.getNetworkCapabilities(networkCapabilities);

if(actNw == null) {
return false;
}

if(actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
return true;
}

return false;
}

// deprecated in API level 29
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return (netInfo != null && netInfo.isConnected());
}
}