Headless JS
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Headless JS es una forma de ejecutar tareas en JavaScript mientras tu aplicación está en segundo plano. Se puede utilizar, por ejemplo, para sincronizar datos nuevos, gestionar notificaciones push o reproducir música.
La API de JS
Una tarea es una función asíncrona que registras en AppRegistry, similar a cómo se registran las aplicaciones React:
import {AppRegistry} from 'react-native';
AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName'),
);
Luego, en SomeTaskName.js:
module.exports = async taskData => {
// do stuff
};
Puedes realizar cualquier acción en tu tarea como solicitudes de red, temporizadores, etc., siempre que no interactúe con la interfaz de usuario. Una vez que tu tarea finaliza (es decir, la promesa se resuelve), React Native entrará en modo "pausado" (a menos que haya otras tareas ejecutándose o exista una aplicación en primer plano).
La API de Plataforma
Sí, esto aún requiere algo de código nativo, pero es bastante mínimo. Debes extender HeadlessJsTaskService y sobrescribir getTaskConfig, por ejemplo:
- 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;
}
}
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 // optional: defines whether or not the task is allowed in foreground.
// Default is false
)
}
}
}
Luego agrega el servicio a tu archivo AndroidManifest.xml dentro de la etiqueta application:
<service android:name="com.example.MyTaskService" />
Ahora, cada vez que inicies tu servicio, por ejemplo como tarea periódica o en respuesta a algún evento/broadcast del sistema, JS se activará, ejecutará tu tarea y se desactivará.
Ejemplo:
- Java
- Kotlin
Intent service = new Intent(getApplicationContext(), MyTaskService.class);
Bundle bundle = new Bundle();
bundle.putString("foo", "bar");
service.putExtras(bundle);
getApplicationContext().startForegroundService(service);
val service = Intent(applicationContext, MyTaskService::class.java)
val bundle = Bundle()
bundle.putString("foo", "bar")
service.putExtras(bundle)
applicationContext.startForegroundService(service)
Reintentos
Por defecto, la tarea Headless JS no realiza reintentos. Para habilitarlos, debes crear una HeadlessJsRetryPolicy y lanzar un Error específico.
LinearCountingRetryPolicy es una implementación de HeadlessJsRetryPolicy que permite especificar un número máximo de reintentos con retraso fijo entre cada intento. Si esto no cubre tus necesidades, puedes implementar tu propia HeadlessJsRetryPolicy. Estas políticas se pasan como argumento adicional al constructor de HeadlessJsTaskConfig, ej.:
- Java
- Kotlin
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
);
val retryPolicy: HeadlessJsTaskRetryPolicy =
LinearCountingRetryPolicy(
3, // Max number of retry attempts
1000 // Delay between each retry attempt
)
return HeadlessJsTaskConfig("SomeTaskName", Arguments.fromBundle(extras), 5000, false, retryPolicy)
Un reintento solo ocurrirá cuando se lance un Error específico. Dentro de una tarea Headless JS, puedes importar este error y lanzarlo cuando se requiera un reintento.
Ejemplo:
import {HeadlessJsTaskError} from 'HeadlessJsTask';
module.exports = async taskData => {
const condition = ...;
if (!condition) {
throw new HeadlessJsTaskError();
}
};
Si deseas que todos los errores provoquen reintentos, deberás capturarlos y lanzar el error mencionado.
Advertencias
-
Por defecto, tu aplicación fallará si intentas ejecutar una tarea mientras está en primer plano. Esto evita que los desarrolladores se perjudiquen al realizar trabajo pesado en tareas que ralentizan la UI. Puedes pasar un cuarto argumento
booleanpara controlar este comportamiento. -
Si inicias tu servicio desde un
BroadcastReceiver, asegúrate de llamarHeadlessJsTaskService.acquireWakeLockNow()antes de retornar deonReceive().
Ejemplo de Uso
El servicio puede iniciarse desde la API Java. Primero debes decidir cuándo iniciar el servicio e implementar tu solución acorde. Aquí un ejemplo que reacciona a cambios en la conexión de red.
Las siguientes líneas muestran parte del archivo AndroidManifest.xml para registrar el receptor de broadcast:
<receiver android:name=".NetworkChangeReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
El receptor de broadcast maneja el intent transmitido en la función onReceive. Este es un lugar ideal para verificar si tu app está en primer plano. Si no lo está, preparamos nuestro intent para iniciar, con información opcional adjunta mediante putExtra (recuerda que el bundle solo maneja valores parcelables). Finalmente se inicia el servicio y se adquiere el wakelock.
- Java
- Kotlin
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());
}
}
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
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?) {
/**
* 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 */
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 {
/**
* 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
*/
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 {
// deprecated in 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
}
}
}