Как обрабатывать фоновые задачи в реагировании натив с помощью безголового JS
Что такое безголовый JS в React Native?
Headless JS - это встроенная функция React Native, которая позволяет выполнять код JavaScript в фоновом режиме, даже когда пользовательский интерфейс приложения не активен. В отличие от выполнения на переднем плане, где приложение видимо и интерактивно, Headless JS запускает задачи как отдельные процессы, которые не зависят от основного потока пользовательского интерфейса. Это важно для операций, которые должны продолжаться после того, как пользователь закроет приложение или до того, как он откроет его - например, синхронизация данных, обработка входящих push-уведомлений или обработка критически важных по времени оповещений. Headless JS сокращает разрыв между временем выполнения React Native и нативными уровнями Android и iOS, позволяя писать кроссплатформенную фоновую логику с минимальным нативным кодом.
Случаи общего использования для безголовых JS
Понимание того, когда использовать Headless JS, помогает вам решить, подходит ли он вашему проекту.
- Синхронизация данных — загрузка журналов или загрузка нового контента, когда приложение находится в фоновом режиме.
- Обработка push-уведомлений — выполнение пользовательской логики при поступлении уведомления (например, обновление значка, хранение данных локально) перед показом оповещения.
- Обновления местоположения — опрос GPS-координат в фоновом режиме для геозонирования или отслеживания в реальном времени.
- Задачи, основанные на времени (FLT:0) — выполнение работы с запланированным интервалом (например, ежедневная уборка), даже если пользователь не открыл приложение.
- Быстрые действия с домашнего экрана — выполнение короткой операции, когда пользователь нажимает 3D Touch или виджет действия.
Безголовый JS не предназначен для длительных или тяжелых вычислений - система может убить ваш фоновый процесс, если это займет слишком много времени. Для расширенной фоновой работы объедините его с нативными службами, такими как WorkManager (Android) или BackgroundTasks (iOS).
Настройка безголового JS в React Native: Step-by-Step
Внедрение Headless JS включает две основные части: создание обработчика JavaScript и регистрация его в родном коде для каждой платформы.
1.Создать JavaScript Background Handler
Начните с создания нового файла, например . Этот файл будет содержать функцию, которая запускается при запуске фоновой задачи. Функция получает объект , который может удерживать параметры, переданные с нативной стороны.
// BackgroundTask.js
import { AppRegistry } from 'react-native';
const backgroundTask = async (taskData) => {
console.log('Background task started with data:', taskData);
// Your background logic goes here
// For example, fetch new data from an API
try {
const response = await fetch('https://api.example.com/sync');
const data = await response.json();
// Store data in AsyncStorage or a local database
console.log('Background sync complete');
} catch (error) {
console.error('Background task failed:', error);
// Optionally throw an error to let the system know
}
};
AppRegistry.registerHeadlessTask('BackgroundTask', () => backgroundTask);
Обратите внимание, что обработчик должен быть функцией, которая возвращает Обещание (или асинхронную функцию). Система будет ждать, пока Обещание будет решено, прежде чем рассматривать задачу как завершенную. Если Обещание отклоняется, нативная сторона может рассматривать его как сбой.
2.Зарегистрируйте задачу в исходном коде Android
Android требует, чтобы вы зарегистрировали задачу без головы в своем (или в зависимости от версии React Native). Кроме того, вам нужен HeadlessJsTaskService для управления жизненным циклом задачи.
Шаг А — Обновление MainApplication.java
// android/app/src/main/java/com/yourapp/MainApplication.java
import com.facebook.react.HeadlessJsTaskService;
public class MainApplication extends Application implements ReactApplication {
// ... existing code
@Override
public void onCreate() {
super.onCreate();
// Initialize ReactNative instance as usual
// The task will be registered via AppRegistry in JavaScript
}
}
Для самой задачи не требуется явной регистрации в Java, если вы используете — React Native автоматически отображает обработчик JavaScript на родную задачу.
Шаг B — Создайте службу без головы
Создайте новый класс Java в том же пакете:
// android/app/src/main/java/com/yourapp/BackgroundTaskService.java
package com.yourapp;
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;
public class BackgroundTaskService extends HeadlessJsTaskService {
@Override
protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
return new HeadlessJsTaskConfig(
"BackgroundTask",
Arguments.fromBundle(extras),
5000, // timeout in ms
true // allows task to run in foreground as well
);
}
return null;
}
}
Этот сервис извлекает любые дополнительные данные из намерения (например, данные из уведомления) и передает их обработчику JavaScript.
Шаг C — Добавьте сервис на AndroidManifest.xml
<service android:name=".BackgroundTaskService" />
Теперь любой нативный модуль может запустить эту услугу с помощью Intent, или вы можете использовать широковещательный приемник, чтобы запустить его из push-уведомления.
3.Настройка возможностей резервного копирования iOS
Для использования Headless JS на iOS вы должны включить соответствующие фоновые режимы и использовать нативное планирование (например, push-уведомления или фоновый выбор).
Шаг А — Включить фоновые режимы
Откройте свой проект Xcode, перейдите в Signing & Capabilities и добавьте Background Modes возможность. Проверьте режимы, необходимые вашему приложению, например, «Background fetch» или «Удалённые уведомления».
Шаг B — Внедрение методов делегирования приложений
В , реализовать необходимые методы делегата, чтобы получить фоновые события и начать безголовую задачу.
// AppDelegate.m (Objective-C)
#import <React/RCTAppDelegate.h>
#import "AppDelegate.h"
#import <React/RCTBridge.h>
#import <React/RCTRootView.h>
@implementation AppDelegate
- (void)application:(UIApplication *)application
performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// You can get the bridge from your ReactNative root view
RCTBridge *bridge = [self.rootView bridge];
[bridge.eventDispatcher sendAppEventWithName:@"BackgroundFetch"
body:@{@"taskName": @"BackgroundTask"}];
completionHandler(UIBackgroundFetchResultNewData);
}
@end
Со стороны JavaScript нужно прослушать событие , а затем вручную запустить задачу без головы.Однако, Headless JS на iOS чаще используется вместе с push-уведомлениями — когда приходит уведомление, вы можете выполнить код без головы перед показом оповещения.
Для push-уведомлений, введите в AppDelegate и отправьте событие в React Native.
Пробные задачи Background Tasks
После настройки вы можете запустить задачу безголового JS из различных источников:
- Пусковые уведомления (Android & iOS) — Используйте пользовательский приемник вещания на Android или делегат приложения на iOS, чтобы запустить сервис, когда поступает тихое уведомление.
- Нативные модули — Создайте нативный модуль, который вызывает на Android или публикует уведомление на iOS.
- Background fetch (iOS) — iOS периодически вызывает делегата вашего приложения; вы можете использовать эту возможность для запуска безголовой задачи.
- Следует использовать — или для планирования периодического исполнения.
Пример: Запуск службы Android с приемника вещания, который слушает пользовательские действия:
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context, BackgroundTaskService.class);
serviceIntent.putExtra("source", "alarm");
context.startService(serviceIntent);
}
}
Затем зарегистрируйте приемник в AndroidManifest.xml и запланируйте сигнализацию с помощью .
Лучшие практики для надежной фоновой операции
Фоновые задачи по своей природе хрупки - ОС может убить ваш процесс в любое время. Следуйте этим рекомендациям, чтобы сделать ваши задачи без головы надежными:
- Сохраняйте короткие задачи. Стремитесь к выполнению менее 30 секунд. Используйте тайм-ауты (в нашем примере 5000 мс) для предотвращения висящих задач.
- Ошибки ручной работы изящно. Оберните свою логику в пробу/лову. Если задача не срабатывает, рассмотрите возможность локального входа в систему с помощью репортера сбоев (например, Sentry) и не блокируйте пользовательский интерфейс.
- Использовать офлайн-первые шаблоны. Поскольку фоновые задачи часто выполняются без сетевых гарантий, реализовать повторные запросы и хранить неудачные операции на более поздний срок.
- Избегайте управления тяжелым состоянием. Не полагайтесь на состояние Redux или React — фоновые задачи выполняются в отдельном контексте JavaScript. Используйте AsyncStorage, SQLite (react-native-sqlite-storage) или безопасное хранение для сохранения данных.
- Тест на реальных устройствах. Эмулятор Android и симулятор iOS ведут себя иначе, чем физическое оборудование. Тест с выключенным экраном устройства и в режиме Doze (Android) или в режиме низкой мощности (iOS).
- Уважайте оптимизацию батареи. Используйте с блокировкой пробуждения на Android, чтобы предотвратить засыпание процессора, но быстро выпустите блокировку. На iOS интервалы фонового вывода управляются системой — соблюдайте предоставленное время.
Отладка безголовых JS-задач
Отладка фонового кода сложна, потому что вы не можете использовать Chrome DevTools инспектор, когда пользовательский интерфейс не активен.
- Logcat (Android) — Добавить высказывания; они появляются в logcat с тегом «ReactNativeJS».
- Локальные журналы файлов — Записывайте записи журнала в файл на устройстве и извлекайте их позже.
- Flipper (FLT:0) — React Native Flipper может захватывать журналы консоли из безголовых задач (обеспечьте, чтобы вы установили в своем плагине Flipper).
- Нативная запись — используйте в вашем сервисе Java для отслеживания, когда задача начинается и заканчивается.
Ограничения и соображения
Безголовый JS не серебряная пуля. Поймите его ограничения, чтобы избежать сюрпризов:
- Никакого взаимодействия с пользовательским интерфейсом. Вы не можете отображать просмотры или показывать оповещения в безголовой задаче. Используйте push-уведомления для информирования пользователя о результатах.
- Одна задача на процесс. Сервис React Native без головы выполняет только одну задачу JavaScript за раз.Если вам нужно несколько параллельных фоновых операций, рассмотрите возможность использования нативных сервисов (WorkManager на Android, BGTaskScheduler на iOS).
- iOS ограничения. iOS сильно замедляет фоновое исполнение. Безголовый JS на iOS надежен только для коротких задач, вызванных push-уведомлениями или фоновым извлечением — система может задержать или предотвратить выполнение, если пользователь заставит приложение.
- Никакого магазина Redux. Как упоминалось ранее, безголовый контекст JavaScript не разделяет тот же магазин Redux, что и приложение переднего плана. Используйте постоянное хранилище для передачи данных между двумя контекстами.
- Расторжение приложения. Если пользователь принудительно закрывает приложение на Android 12+ или iOS, фоновые задачи могут не выполняться, пока пользователь снова не откроет приложение.
Сравнение с другими фоновыми библиотеками
Безголовый JS от React Native является встроенным механизмом самого низкого уровня. Для многих случаев использования пакеты сообщества обеспечивают более удобный API:
| Library | Platform | Features |
|---|---|---|
| react-native-background-fetch | Android & iOS | Periodic background fetching with system‑managed scheduling; supports Headless JS internally. |
| react-native-background-actions | Android & iOS | Runs a long‑running foreground service (with persistent notification) – can execute heavy tasks. |
| WorkManager (Android only via native bridge) | Android | Deferrable, reliable background work with constraints (network, battery); does not use Headless JS by default. |
| Headless JS (vanilla) | Android & iOS | Simple, built‑in, no extra dependencies; best for short tasks triggered by push or alarms. |
Если ваша задача должна выполняться даже после перезагрузки или вам нужно сложное планирование, рассмотрите возможность обертывания реализации нативного WorkManager и разоблачения его с помощью модуля React Native. Headless JS остается отличным для легких «огненных» заданий.
Полный пример: Синхронизация данных с Push-уведомлением
Давайте все объединим с реалистичным сценарием: когда на Android поступает тихое push-уведомление, приложение загружает последние данные из API и хранит их локально. Пользователь никогда не видит изменения пользовательского интерфейса - данные готовы, когда они в следующий раз откроют приложение.
Android — Push-получатель
Создайте приемник вещания, который слушает действие толчка (например, из FCM):
public class PushReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent serviceIntent = new Intent(context, BackgroundTaskService.class);
serviceIntent.putExtra("action", "syncData");
context.startService(serviceIntent);
}
}
Зарегистрируйте его в AndroidManifest.xml с помощью фильтра намерений для вашего push-действия (или используйте FirebaseMessagingService , чтобы запустить сервис напрямую).
JavaScript Handler
// BackgroundTask.js (expanded)
import { AppRegistry } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
const backgroundTask = async (taskData) => {
const { action } = taskData;
if (action === 'syncData') {
try {
const response = await fetch('https://api.example.com/latest-data');
const json = await response.json();
await AsyncStorage.setItem('cachedData', JSON.stringify(json));
console.log('Data synced successfully');
} catch (error) {
// Write error to a local log
const errorLog = await AsyncStorage.getItem('errorLog');
const newLog = (errorLog ? errorLog + '\n' : '') + new Date().toISOString() + ': ' + error.message;
await AsyncStorage.setItem('errorLog', newLog);
}
}
};
AppRegistry.registerHeadlessTask('BackgroundTask', () => backgroundTask);
Когда пользователь вновь открывает приложение, передний план может читать и немедленно обновлять пользовательский интерфейс, создавая бесшовный офлайн-первый опыт.
Производительность и влияние батареи
Головные задачи выполняются в том же движке JavaScript, что и основное приложение, но в отдельном контексте. Каждая задача раскручивает новый экземпляр JS без головы, который потребляет память и процессор. Чтобы минимизировать разрядку батареи:
- Используйте самый короткий тайм-аут, который все еще позволяет выполнить работу.
- Не проводите частые опросы – предпочитайте триггеры на основе толчка.
- На Android, звоните из службы после завершения задачи (HeadlessJsTaskService делает это автоматически, когда Обещание решается).
- Объедините с API, которые не требуют использования батарей, такими как WorkManager для Android и BGTaskScheduler для iOS, если вам нужна периодическая работа.
Заключение
Headless JS является мощным инструментом в поясе разработчика React Native для обработки коротких фоновых задач, которые должны выполняться независимо от видимости приложения. Настраивая обработчик JavaScript и соединяя его с нативными фоновыми триггерами - такими как push-уведомления, тревоги или фоновый вывод - вы можете создавать адаптивные приложения, управляемые данными, которые всегда ощущаются. Не забудьте следовать лучшим практикам в отношении краткости, обработки ошибок и ограничений для платформы, чтобы гарантировать, что ваши фоновые операции являются надежными и удобными для батареи. Для расширенного планирования или более длительных задач дополните Headless JS выделенными библиотеками, такими как или нативный WorkManager.
Для дальнейшего чтения обратитесь к официальной документации React Native по Headless JS (Android) и Headless JS (iOS) . Также проверьте библиотеку react-native-background-fetch для периодического управления задачами.