Headless JS
Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →
Headless JS to mechanizm uruchamiania zadań w JavaScript podczas pracy aplikacji w tle. Może być używany np. do synchronizowania świeżych danych, obsługi powiadomień push lub odtwarzania muzyki.
API JavaScript
Zadanie to funkcja asynchroniczna rejestrowana w AppRegistry, podobnie jak aplikacje React:
import {AppRegistry} from 'react-native';
AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName'),
);
Następnie w pliku SomeTaskName.js:
module.exports = async taskData => {
// do stuff
};
W zadaniu możesz wykonywać dowolne operacje, takie jak żądania sieciowe, timery itp., pod warunkiem że nie dotyczą one interfejsu użytkownika. Po zakończeniu zadania (tj. rozwiązaniu promise), React Native przejdzie w tryb "pauzy" (o ile nie działają inne zadania lub aplikacja nie jest na pierwszym planie).
API platformy
Tak, nadal wymaga to kodu natywnego, ale jest go niewiele. Musisz rozszerzyć HeadlessJsTaskService i nadpisać metodę getTaskConfig, np.:
- 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
)
}
}
}
Następnie dodaj usługę do pliku AndroidManifest.xml wewnątrz tagu application:
<service android:name="com.example.MyTaskService" />
Teraz, gdy tylko uruchomisz swoją usługę, np. jako zadanie okresowe lub w odpowiedzi na zdarzenie systemowe/broadcast, JS zostanie uruchomiony, wykona zadanie i zostanie zamknięty.
Przykład:
- 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)
Ponawianie prób
Domyślnie zadanie Headless JS nie będzie ponawiać prób. Aby to umożliwić, należy utworzyć HeadlessJsRetryPolicy i zgłosić określony błąd (Error).
LinearCountingRetryPolicy to implementacja HeadlessJsRetryPolicy, która pozwala określić maksymalną liczbę ponownych prób ze stałym opóźnieniem między próbami. Jeśli to nie spełnia twoich potrzeb, możesz zaimplementować własny HeadlessJsRetryPolicy. Te polityki można przekazać jako dodatkowy argument do konstruktora HeadlessJsTaskConfig, np.
- 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)
Ponowna próba zostanie podjęta tylko w przypadku zgłoszenia określonego błędu (Error). Wewnątrz zadania Headless JS możesz zaimportować ten błąd i zgłosić go, gdy wymagana jest ponowna próba.
Przykład:
import {HeadlessJsTaskError} from 'HeadlessJsTask';
module.exports = async taskData => {
const condition = ...;
if (!condition) {
throw new HeadlessJsTaskError();
}
};
Jeśli chcesz, aby wszystkie błędy powodowały ponowienie próby, musisz je przechwycić i zgłosić powyższy błąd.
Ograniczenia
-
Domyślnie aplikacja ulegnie awarii, jeśli spróbujesz uruchomić zadanie, gdy aplikacja jest na pierwszym planie. Ma to zapobiec spowolnieniu interfejsu przez zbyt intensywne zadania. Możesz przekazać czwarty argument
boolean, aby kontrolować to zachowanie. -
Jeśli uruchamiasz usługę z
BroadcastReceiver, pamiętaj aby wywołaćHeadlessJsTaskService.acquireWakeLockNow()przed zwróceniem zonReceive().
Przykład użycia
Usługę można uruchomić z poziomu API Java. Najpierw zdecyduj, kiedy usługa powinna zostać uruchomiona i odpowiednio zaimplementuj rozwiązanie. Oto przykład reagujący na zmianę połączenia sieciowego.
Poniższe linie pokazują fragment pliku manifestu Android rejestrujący odbiornik broadcastów.
<receiver android:name=".NetworkChangeReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
Odbiornik broadcastów obsługuje intencję w funkcji onReceive. To doskonałe miejsce, aby sprawdzić, czy aplikacja jest na pierwszym planie. Jeśli nie jest, możemy przygotować intencję do uruchomienia, bez informacji lub z dodatkowymi danymi dołączonymi za pomocą putExtra (pamiętaj, że bundle obsługuje tylko wartości parcelable). Na końcu usługa jest uruchamiana, a wakelock jest pobierany.
- 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
}
}
}