الفرق بين المراجعتين لصفحة: «ReactNative/headless js android»
لا ملخص تعديل |
جميل-بيلوني (نقاش | مساهمات) طلا ملخص تعديل |
||
(مراجعتان متوسطتان بواسطة مستخدمين اثنين آخرين غير معروضتين) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE: تشغيل JavaScript في | <noinclude>{{DISPLAYTITLE:تشغيل JavaScript في الخلفية (Headless JS) في React Native}}</noinclude> | ||
Headless JS | تعد Headless JS طريقة لتشغيل المهام في JavaScript أثناء وجود التطبيق في الخلفية. يمكن استخدامها مثلًا لمزامنة البيانات الجديدة أو التعامل مع إشعارات الدفع (push notifications) أو تشغيل الموسيقى. | ||
==واجهة برمجة تطبيقات JavaScript== | ==واجهة برمجة تطبيقات JavaScript== | ||
المهام (tasks) هي دوال غير متزامنة (async function) بسيطة تُسجّلها في <code>AppRegistry</code>، على غرار تسجيل تطبيقات React: | المهام (tasks) هي دوال غير متزامنة (async function) بسيطة تُسجّلها في <code>AppRegistry</code>، على غرار تسجيل تطبيقات [[React]]: | ||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
AppRegistry.registerHeadlessTask('SomeTaskName', () => require('SomeTaskName')); | import { AppRegistry } from 'react-native'; | ||
AppRegistry.registerHeadlessTask('SomeTaskName', () => | |||
require('SomeTaskName') | |||
); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
ثم في <code>SomeTaskName.js</code> (الذي يمثّل اسم الملفّ الذي يحمل المهمّة): | ثم في <code>SomeTaskName.js</code> (الذي يمثّل اسم الملفّ الذي يحمل المهمّة): | ||
سطر 17: | سطر 20: | ||
==واجهة برمجة تطبيقات Java== | ==واجهة برمجة تطبيقات Java== | ||
لا يزال هذا يتطلب القليل من الشيفرة الأصيلة. تحتاج إلى توسيع <code>HeadlessJsTaskService</code> | لا يزال هذا يتطلب القليل من الشيفرة الأصيلة. تحتاج إلى توسيع <code>HeadlessJsTaskService</code> وإعادة تعريف <code>getTaskConfig</code>، على سبيل المثال: | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="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 { | public class MyTaskService extends HeadlessJsTaskService { | ||
سطر 28: | سطر 40: | ||
"SomeTaskName", | "SomeTaskName", | ||
Arguments.fromBundle(extras), | Arguments.fromBundle(extras), | ||
5000, // مُهلَة المهمّة | 5000, // timeout for the task مُهلَة المهمّة | ||
false // خيار اختياريّ يحدّد ما إذا كان من المسموح تشغيل المهمّة في المقدّمة. هذه هي القيمة الافتراضيّة | false // خيار اختياريّ يحدّد ما إذا كان من المسموح تشغيل المهمّة في المقدّمة. هذه هي القيمة الافتراضيّة | ||
); | ); | ||
سطر 41: | سطر 53: | ||
<service android:name="com.example.MyTaskService" /> | <service android:name="com.example.MyTaskService" /> | ||
</syntaxhighlight> | </syntaxhighlight> | ||
الآن، متى ما [https://developer.android.com/reference/android/content/Context.html#startService%28android.content.Intent%29 بدأت خدمتك] كمهمة دورية مثلًا أو استجابةً لبعض أحداث النظام أو بثٍّ (broadcast) ما، ستعمل JS على بدء الدوران (spin up) وتشغيل المهمة ثم ستتوقّف عن الدوران (spin down). | |||
مثال: | مثال: | ||
سطر 54: | سطر 66: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
===المحاذير== | == إعادة المحاولة == | ||
لن تنفذ headless JS أي إعادة محاولة بشكل افتراضي، للقيام بذلك لا بد من إنشاء <code>HeadlessJsRetryPolicy</code> ورمي خطأ Error محدد. يعد <code>LinearCountingRetryPolicy</code> تنفيذًا لها، ويسمح بتحديد الحد الأعلى لعدد مرات إعادة المحاولة بفوارق ثابتة بينها. إذا لم يناسبك هذا يمكنك تنفيذ <code>HeadlessJsRetryPolicy</code> خاصة بك، والتي يمكن أن تمرَّر كمعامل إضافي للباني <code>HeadlessJsTaskConfig</code>، كما في المثال التالي:<syntaxhighlight lang="javascript"> | |||
HeadlessJsRetryPolicy retryPolicy = new LinearCountingRetryPolicy( | |||
3, // الحد لأعلى لكرات إعادة المحاولة | |||
1000 // التأخير بين كل محاولة | |||
); | |||
return new HeadlessJsTaskConfig( | |||
'SomeTaskName', | |||
Arguments.fromBundle(extras), | |||
5000, | |||
false, | |||
retryPolicy | |||
); | |||
</syntaxhighlight>تنفذ إعادة المحاولة فقط عندما يُرمى خطأ <code>error</code> محدد، يمكنك استيراد الخطأ ورميه عندما تحتاج لإعادة المحاولة، كما في المثال:<syntaxhighlight lang="javascript"> | |||
import {HeadlessJsTaskError} from 'HeadlessJsTask'; | |||
module.exports = async (taskData) => { | |||
const condition = ...; | |||
if (!condition) { | |||
throw new HeadlessJsTaskError(); | |||
} | |||
}; | |||
</syntaxhighlight>إذا أردت أن تسبب كل الأخطاء إعادة محاولة فعليك التقاطها ورمي الخطأ السابق. | |||
==المحاذير== | |||
* لا تتصرف الدالة التي تمرر إلى <code>setTimeout</code> كما هو متوقع دائمًا، بدلًا من ذلك تستدعى الدالة فقط عندما يفتح التطبيق من جديد، إذا كنت بحاجة إلى الانتظار فقط ، فاستخدم وظيفة إعادة المحاولة | |||
* افتراضيًّا، سيتعطل (crash) تطبيقك إذا حاولت تشغيل مهمةٍ أثناء وجود التطبيق في المقدمة. يمنع هذا المطورين من الوقوع في خطأ القيام بالكثير من العمل في مهمة وإبطاء واجهة المستخدم. يمكنك تمرير مُعاملٍ منطقيّ رابعٍ للتحكم في هذا السلوك. | * افتراضيًّا، سيتعطل (crash) تطبيقك إذا حاولت تشغيل مهمةٍ أثناء وجود التطبيق في المقدمة. يمنع هذا المطورين من الوقوع في خطأ القيام بالكثير من العمل في مهمة وإبطاء واجهة المستخدم. يمكنك تمرير مُعاملٍ منطقيّ رابعٍ للتحكم في هذا السلوك. | ||
* إذا بدأت تشغيل خدمتك من <code>BroadcastReceiver</code>، فتأكد من استدعاء <code>HeadlessJsTaskService.acquireWakeLockNow()</code> قبل الإعادة من <code>onReceive()</code>. | * إذا بدأت تشغيل خدمتك من <code>BroadcastReceiver</code>، فتأكد من استدعاء <code>HeadlessJsTaskService.acquireWakeLockNow()</code> قبل الإعادة من <code>onReceive()</code>. | ||
==مثال للاستخدام== | ==مثال للاستخدام== | ||
يمكن بدء الخدمة من واجهة برمجة Java. تحتاج | يمكن بدء الخدمة من واجهة برمجة Java. تحتاج أولًا إلى تحديد موعد بدء الخدمة وإجراء (implement) الحل الخاص بك وفقًا لذلك. فيما يلي مثال بسيط يتفاعل مع تغيير اتصال الشبكة. | ||
الأسطر التالية تعرض جزءًا من ملف Android manifest لتسجيل مستقبل البث (broadcast receiver). | الأسطر التالية تعرض جزءًا من ملف Android manifest لتسجيل مستقبل البث (broadcast receiver). | ||
سطر 70: | سطر 108: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
يتلقى | يتلقى مُستقبِل البَث بعد ذلك النية (intent) التي تم بثها في الدالة <code>onReceive</code>. هذا مكان جيّد للتحقق مما إذا كان تطبيقك في المقدمة أم لا. إذا لم يكن التطبيق في المقدمة، فيمكننا إعداد نيتنا لبدئها، مع عدم وجود معلومات أو معلومات إضافية مجمَّعة باستخدام <code>putExtra</code> (ضع في الحسبان أن الحزمة يمكن أن تتعامل مع القيم القابلة للتحزيم parcelable فقط). في النهاية، تبدأ الخدمة ويتم الحصول على wakelock (تعد wake lock آلية للإشارة إلى أن تطبيقك بحاجة إلى امتلاك تركيز الجهاز وإبقاءه). | ||
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
public class NetworkChangeReceiver extends BroadcastReceiver { | public class NetworkChangeReceiver extends BroadcastReceiver { | ||
سطر 123: | سطر 162: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
سطر 128: | سطر 170: | ||
* [https://facebook.github.io/react-native/docs/headless-js-android صفحة Headless JS في توثيق React Native الرسمي.] | * [https://facebook.github.io/react-native/docs/headless-js-android صفحة Headless JS في توثيق React Native الرسمي.] | ||
[[تصنيف:ReactNative]] | [[تصنيف:ReactNative]] | ||
[[تصنيف:React Native Docs]] |
المراجعة الحالية بتاريخ 13:50، 9 أكتوبر 2021
تعد Headless JS طريقة لتشغيل المهام في JavaScript أثناء وجود التطبيق في الخلفية. يمكن استخدامها مثلًا لمزامنة البيانات الجديدة أو التعامل مع إشعارات الدفع (push notifications) أو تشغيل الموسيقى.
واجهة برمجة تطبيقات JavaScript
المهام (tasks) هي دوال غير متزامنة (async function) بسيطة تُسجّلها في AppRegistry
، على غرار تسجيل تطبيقات React:
import { AppRegistry } from 'react-native';
AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName')
);
ثم في SomeTaskName.js
(الذي يمثّل اسم الملفّ الذي يحمل المهمّة):
module.exports = async (taskData) => {
// افعل ما تشاء
};
يمكنك القيام بأي شيء في مهمتك، كطلبات الشبكة والمؤقّتات وما إلى ذلك، شرطَ ألّا تتعلّق بواجهة المستخدم. بمجرد اكتمال مهمتك (أي بعد حل الوعد)، سينتقل React Native إلى الوضع "متوقف مؤقتًا (paused)" (ما لم تكن هناك مهام أخرى قيد التشغيل، أو ما لم يوجد تطبيق في المقدمة [foreground]).
واجهة برمجة تطبيقات Java
لا يزال هذا يتطلب القليل من الشيفرة الأصيلة. تحتاج إلى توسيع HeadlessJsTaskService
وإعادة تعريف getTaskConfig
، على سبيل المثال:
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 for the task مُهلَة المهمّة
false // خيار اختياريّ يحدّد ما إذا كان من المسموح تشغيل المهمّة في المقدّمة. هذه هي القيمة الافتراضيّة
);
}
return null;
}
}
ثم أضف الخدمة إلى ملف AndroidManifest.xml
الخاص بك:
<service android:name="com.example.MyTaskService" />
الآن، متى ما بدأت خدمتك كمهمة دورية مثلًا أو استجابةً لبعض أحداث النظام أو بثٍّ (broadcast) ما، ستعمل JS على بدء الدوران (spin up) وتشغيل المهمة ثم ستتوقّف عن الدوران (spin down).
مثال:
Intent service = new Intent(getApplicationContext(), MyTaskService.class);
Bundle bundle = new Bundle();
bundle.putString("foo", "bar");
service.putExtras(bundle);
getApplicationContext().startService(service);
إعادة المحاولة
لن تنفذ headless JS أي إعادة محاولة بشكل افتراضي، للقيام بذلك لا بد من إنشاء HeadlessJsRetryPolicy
ورمي خطأ Error محدد. يعد LinearCountingRetryPolicy
تنفيذًا لها، ويسمح بتحديد الحد الأعلى لعدد مرات إعادة المحاولة بفوارق ثابتة بينها. إذا لم يناسبك هذا يمكنك تنفيذ HeadlessJsRetryPolicy
خاصة بك، والتي يمكن أن تمرَّر كمعامل إضافي للباني HeadlessJsTaskConfig
، كما في المثال التالي:
HeadlessJsRetryPolicy retryPolicy = new LinearCountingRetryPolicy(
3, // الحد لأعلى لكرات إعادة المحاولة
1000 // التأخير بين كل محاولة
);
return new HeadlessJsTaskConfig(
'SomeTaskName',
Arguments.fromBundle(extras),
5000,
false,
retryPolicy
);
تنفذ إعادة المحاولة فقط عندما يُرمى خطأ error
محدد، يمكنك استيراد الخطأ ورميه عندما تحتاج لإعادة المحاولة، كما في المثال:
import {HeadlessJsTaskError} from 'HeadlessJsTask';
module.exports = async (taskData) => {
const condition = ...;
if (!condition) {
throw new HeadlessJsTaskError();
}
};
إذا أردت أن تسبب كل الأخطاء إعادة محاولة فعليك التقاطها ورمي الخطأ السابق.
المحاذير
- لا تتصرف الدالة التي تمرر إلى
setTimeout
كما هو متوقع دائمًا، بدلًا من ذلك تستدعى الدالة فقط عندما يفتح التطبيق من جديد، إذا كنت بحاجة إلى الانتظار فقط ، فاستخدم وظيفة إعادة المحاولة - افتراضيًّا، سيتعطل (crash) تطبيقك إذا حاولت تشغيل مهمةٍ أثناء وجود التطبيق في المقدمة. يمنع هذا المطورين من الوقوع في خطأ القيام بالكثير من العمل في مهمة وإبطاء واجهة المستخدم. يمكنك تمرير مُعاملٍ منطقيّ رابعٍ للتحكم في هذا السلوك.
- إذا بدأت تشغيل خدمتك من
BroadcastReceiver
، فتأكد من استدعاء HeadlessJsTaskService.acquireWakeLockNow()
قبل الإعادة من onReceive()
.
مثال للاستخدام
يمكن بدء الخدمة من واجهة برمجة Java. تحتاج أولًا إلى تحديد موعد بدء الخدمة وإجراء (implement) الحل الخاص بك وفقًا لذلك. فيما يلي مثال بسيط يتفاعل مع تغيير اتصال الشبكة.
الأسطر التالية تعرض جزءًا من ملف Android manifest لتسجيل مستقبل البث (broadcast receiver).
<receiver android:name=".NetworkChangeReceiver" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
يتلقى مُستقبِل البَث بعد ذلك النية (intent) التي تم بثها في الدالة onReceive
. هذا مكان جيّد للتحقق مما إذا كان تطبيقك في المقدمة أم لا. إذا لم يكن التطبيق في المقدمة، فيمكننا إعداد نيتنا لبدئها، مع عدم وجود معلومات أو معلومات إضافية مجمَّعة باستخدام putExtra
(ضع في الحسبان أن الحزمة يمكن أن تتعامل مع القيم القابلة للتحزيم parcelable فقط). في النهاية، تبدأ الخدمة ويتم الحصول على wakelock (تعد wake lock آلية للإشارة إلى أن تطبيقك بحاجة إلى امتلاك تركيز الجهاز وإبقاءه).
public class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
/**
سيُستدعى هذا الجزء في كل مرّة يتغيّر فيها اتصال الشبكة
e.g. Connected -> Not Connected
**/
if (!isAppOnForeground((context))) {
/**
سنبدأ خدمتنا ونرسل معلومات إضافيّة حول اتصالات الشبكة
**/
boolean hasInternet = isNetworkAvailable(context);
Intent serviceIntent = new Intent(context, MyTaskService.class);
serviceIntent.putExtra("hasInternet", hasInternet);
context.startService(serviceIntent);
HeadlessJsTaskService.acquireWakeLockNow(context);
}
}
private boolean isAppOnForeground(Context context) {
/**
نحتاج إلى التحقق من وجود التطبيق في المقدّمة وإلا سيتعطل التطبيق
http://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);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return (netInfo != null && netInfo.isConnected());
}
}