メインコンテンツへスキップ

ヘッドレスJS

ヘッドレスJSは、アプリがバックグラウンドにある間にJavaScriptでタスクを実行する方法です。例えば、新しいデータの同期、プッシュ通知の処理、音楽の再生などに使用できます。

JS API

タスクは、Reactアプリケーションの登録と同様に、AppRegistryに登録する非同期関数です。

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

次に、SomeTaskName.js

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

UIに触れない限り、ネットワークリクエスト、タイマーなど、タスクで何でも実行できます。タスクが完了すると(つまり、Promiseが解決されると)、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を遅くすることで自分で自分の首を絞めるのを防ぐためです。この動作を制御するために、4番目のboolean引数を渡すことができます。
  • BroadcastReceiverからサービスを開始する場合は、onReceive()から戻る前にHeadlessJsTaskService.acquireWakeLockNow()を呼び出すようにしてください。

使用例

サービスはJava APIから開始できます。まず、サービスを開始するタイミングを決定し、それに応じてソリューションを実装する必要があります。ここでは、ネットワーク接続の変更に反応する例を示します。

次の行は、ブロードキャストレシーバーを登録するためのAndroidマニフェストファイルの一部を示しています。

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

ブロードキャストレシーバーは、onReceive関数でブロードキャストされたインテントを処理します。これは、アプリがフォアグラウンドにあるかどうかを確認するのに最適な場所です。アプリがフォアグラウンドにない場合は、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());
}
}