进阶 reactnative.dev 2026-10-11 06:04:21 · 22 阅读

第48章 Headless JS与React Native

版本:0.77 Headless JS 允许应用在后台状态下运行 JavaScript 任务。你可以用它来同步最新数据、处理推送通知或播放音乐。 JS API 任务是一个异步函数,你将其注册到 AppRegistry,这与注册 React 应用类似: React TSX import {AppRegistry} from 'react-native'; AppRegistry.registerHeadlessTask('SomeTaskName', () => require('SomeTaskName'), ); 接着,在 SomeTaskName.js 中: React TSX module.exports = async taskData => { // do stuff }; 任务中可以执行任何操作,如网络请求、定时器等,前提是不涉及 UI。当任务完成(即 Promise 被 resolve)后,React Native 将进入“暂停”模式(除非还有其他任务正在运行,或前台有应用)。 平台 API 是的,这仍然需要一些原生代码,但很简洁。你需要扩展 HeadlessJsTaskService 并覆写 getTaskConfig,例如: Java Kotlin 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; } } Kotlin package com.your_application_name; import android.content.Intent import com.facebook.react.HeadlessJsTaskService import com.facebook.react.bridge.Arguments import com.facebook.react.jstasks.HeadlessJsTaskConfig class MyTaskService : HeadlessJsTaskService() { override fun getTaskConfig(intent: Intent): HeadlessJsTaskConfig? { return intent.extras?.let { HeadlessJsTaskConfig( "SomeTaskName", Arguments.fromBundle(it), 5000, // timeout for the task

false // 可选:定义该任务是否允许在前台运行。
// 默认值为 false

}

}

}


然后,在 AndroidManifest.xml 文件的 application 标签内添加该 service 声明:

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

现在,每当你的 service 启动时(例如作为周期性任务响应,或由系统事件/广播触发),JS 引擎会启动、执行任务,然后关闭。

示例:

Java/Kotlin
Java:
Intent service = new Intent(getApplicationContext(), MyTaskService.class);
Bundle bundle = new Bundle();

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

getApplicationContext().startForegroundService(service);

Kotlin:
val service = Intent(applicationContext, MyTaskService::class.java)
val bundle = Bundle()

bundle.putString("foo", "bar")

service.putExtras(bundle)

applicationContext.startForegroundService(service)

重试机制

默认情况下,Headless JS 任务不会执行任何重试。如果需要重试功能,你需要创建 HeadlessJsRetryPolicy 并抛出特定的 Error。

LinearCountingRetryPolicy 是 HeadlessJsRetryPolicy 的一种实现,它允许你指定最大重试次数以及每次尝试之间的固定延迟。如果这不符合你的需求,你可以自行实现 HeadlessJsRetryPolicy。这些策略可以作为额外参数传递给 HeadlessJsTaskConfig 构造函数,例如:

Java/Kotlin
Java:
HeadlessJsRetryPolicy retryPolicy = new LinearCountingRetryPolicy(
  3, // 最大重试次数
  1000 // 每次重试之间的延迟
);

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

Kotlin:
val retryPolicy: HeadlessJsTaskRetryPolicy =
    LinearCountingRetryPolicy(
        3, // 最大重试次数
        1000 // 每次重试之间的延迟
    )

return HeadlessJsTaskConfig("SomeTaskName", Arguments.fromBundle(extras), 5000, false, retryPolicy)

只有当抛出特定 Error 时,才会触发重试尝试。在 Headless JS 任务内部,你可以导入该 error,并在需要重试时抛出它。

示例:

React TSX
import {HeadlessJsTaskError} from 'HeadlessJsTask';

module.exports = async taskData => {
  const condition = ...;
  if (!condition) {
    throw new HeadlessJsTaskError();
  }
};
如果你希望所有错误都能触发重试,就需要自己捕获这些错误,并抛出上面那个错误。 注意事项 默认情况下,如果应用处于前台时尝试运行任务,应用会直接崩溃。这是为了防止开发者在任务中执行大量工作,拖慢 UI。你可以传入第四个布尔值参数来控制这一行为。 如果你是通过 BroadcastReceiver 启动服务,请确保在 onReceive() 返回之前调用 HeadlessJsTaskService.acquireWakeLockNow()。 使用示例 服务可以通过 Java API 启动。首先你需要确定在什么时机启动服务,并据此实现相应的方案。下面这个示例会响应网络连接变化。 以下几行展示了 Android manifest 文件中注册 broadcast receiver 的部分。 XML




Broadcast receiver 会在 onReceive 函数中处理广播出来的 intent。这里很适合用来判断应用是否在前台。如果应用不在前台,我们就可以准备启动 intent,可以不带附加信息,也可以通过 putExtra 携带额外信息(注意 bundle 只能承载 parcelable 类型的值)。最后启动服务并获取 wakelock。 JavaKotlinJavaimport 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) {
/
网络连接每次发生变化时都会调用这段代码
例如:已连接 -> 未连接
/
if (!isAppOnForeground((context))) {
/
我们将启动服务,并发送关于网络连接的额外信息
/
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) {
/
我们需要检查应用是否在前台,否则应用会崩溃。
https://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
/
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List 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());
}
}
Kotlin
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
该片段主要为 Kotlin 代码,其中仅包含几处英文注释。根据翻译规则,代码部分需保留原样,仅对注释部分进行意译: import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import com.facebook.react.HeadlessJsTaskService

class NetworkChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
/
* 当网络连接状态发生变化(例如从已连接变为未连接)时,将调用此部分
*/
if (!isAppOnForeground(context)) {
/
我们将启动服务并传递网络连接相关的附加信息 */
val hasInternet = isNetworkAvailable(context)
val serviceIntent = Intent(context, MyTaskService::class.java)
serviceIntent.putExtra("hasInternet", hasInternet)
context.startForegroundService(serviceIntent)
HeadlessJsTaskService.acquireWakeLockNow(context)
}
}

private fun isAppOnForeground(context: Context): Boolean {
/
* 需要检查应用是否在前台,否则应用会崩溃。
* https://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
*/
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val appProcesses = activityManager.runningAppProcesses ?: return false
val packageName: String = context.getPackageName()
for (appProcess in appProcesses) {
if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
appProcess.processName == packageName
) {
return true
}
}
return false
}

companion object {
fun isNetworkAvailable(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
var result = false

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val networkCapabilities = cm.activeNetwork ?: return false

val actNw = cm.getNetworkCapabilities(networkCapabilities) ?: return false

result =
when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}

return result
} else {
cm.run {
// 在 API level 29 中已废弃
cm.activeNetworkInfo?.run {
result =
when (type) {
ConnectivityManager.TYPE_WIFI -> true
ConnectivityManager.TYPE_MOBILE -> true
ConnectivityManager.TYPE_ETHERNET -> true
else -> false
}
}
}
}
return result
}
}
}

评论 (0)