الفرق بين المراجعتين لصفحة: «Cordova/plugins android»
لا ملخص تعديل |
تحديث |
||
(6 مراجعات متوسطة بواسطة مستخدمين اثنين آخرين غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE: | <noinclude>{{DISPLAYTITLE:تطوير الإضافات على منصة أندرويد في كوردوفا}}</noinclude> | ||
[[تصنيف: Cordova]] | [[تصنيف: Cordova]] | ||
[[تصنيف: | [[تصنيف: Cordova Plugin]] | ||
يشرح هذا القسم كيفية تنفيذ شيفرات الإضافات الأصلية (native plugin code) على منصة أندرويد. | |||
قبل قراءة هذه الصفحة، راجع صفحة [[Cordova/plugins|دليل تطوير الإضافات]] لتكوين نظرة عامة على بنية الإضافات وواجهات [[JavaScript| | قبل قراءة هذه الصفحة، راجع صفحة [[Cordova/plugins|دليل تطوير الإضافات]] لتكوين نظرة عامة على بنية الإضافات وواجهات [[JavaScript|JavaScript]] الخاصة بها. يواصل هذا القسم تطوير مثال [[Cordova/plugins#.D9.85.D8.AB.D8.A7.D9.84 JavaScript|الإضافة echo]] الوارد في [[Cordova/plugins|دليل تطوير الإضافات]]، والذي يربط الاتصال من المعرض [[Cordova/webviews|webview]] إلى المنصة الأصلية (native platform)، والعكس بالعكس. للحصول على مثال آخر، اطلع على التعليقات في الصفحة [https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/CordovaPlugin.java CordovaPlugin.java]. | ||
تستند إضافات أندرويد على منصة <code>Cordova-Android</code>، التي | تستند إضافات أندرويد على منصة <code>Cordova-Android</code>، التي تُنشَأ من المعرض [[Cordova/webviews|Android WebView]] إضافةً إلى جسر أصلي (native bridge). يتألف الجزء الأصلي من إضافات أندرويد من صنف [[Java|جافا]] واحد على الأقل، والذي يوسع الصنف <code>CordovaPlugin</code> ويعيد تعريف تابعه <code>execute</code>. | ||
==إعداد صنف الإضافة (Plugin Class Mapping)== | ==إعداد صنف الإضافة (Plugin Class Mapping)== | ||
تستخدم واجهة [[JavaScript| | تستخدم واجهة [[JavaScript|JavaScript]] الخاصة بالإضافة التابع <code>cordova.exec</code> على النحو التالي:<syntaxhighlight lang="java">exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);</syntaxhighlight>ترسل هذه الشيفرة طلبية (request) من [[Cordova/webviews|webview]] إلى الجانب الأصلي (native side) لأندرويد، وتستدعي التابع <code>action</code> فعليًّا على الصنف <code>service</code>، مع وسائط إضافية تُمرر في المصفوفة <code>args</code>. | ||
سواءً أقمت بتوزيع الإضافة على هيئة ملف [[Java|جافا]]، أو كملف مضغوط <code>jar</code>، يجب تحديد الإضافة في الملف <code>res/xml/config.xml</code> الخاص بالتطبيق. انظر صفحة [[Cordova/plugins|الإضافات]] لمزيد من المعلومات حول كيفية استخدام الملف <code>plugin.xml</code> لإدارج العنصر <code>feature</code>:<syntaxhighlight lang="xml"><feature name="<service_name>"> | |||
<param name="android-package" value="<full_name_including_namespace>" /> | <param name="android-package" value="<full_name_including_namespace>" /> | ||
</feature> | </feature></syntaxhighlight>يتطابق <code>service_name</code> مع الاسم المستخدم عند الاستدعاء <code>exec</code> في [[JavaScript]]. قيمته تساوي الاسم الكامل المؤهل (fully qualified namespace identifier) لصنف [[Java|جافا]]. بخلاف ذلك، يمكن أن تُصرّف (compiled) الإضافة، لكنها لن تكون متاحة لكوردوفا. | ||
==تهيئة الإضافات | ==تهيئة ودورة حياة الإضافات (Plugin Initialization and Lifetime)== | ||
يتم إنشاء نسخة (instance) واحدة من كائن الإضافة خلال دورة حياة المعرض <code>[[Cordova/webviews|WebView]]</code>. ولكن بعد الإشارة إليها عبر استدعاءٍ من [[ | يتم إنشاء نسخة (instance) واحدة من كائن الإضافة خلال دورة حياة المعرض <code>[[Cordova/webviews|WebView]]</code>. ولكن بعد الإشارة إليها عبر استدعاءٍ من [[JavaScript]]، إلا في حال إضافة الوسم <code><param></code>، مع الخاصيتين <code>name="onload"</code> و <code>value="true"</code> في الملف <code>config.xml</code> على النحو التالي:<syntaxhighlight lang="xml"><feature name="Echo"> | ||
<param name="android-package" value="<full_name_including_namespace>" /> | <param name="android-package" value="<full_name_including_namespace>" /> | ||
<param name="onload" value="true" /> | <param name="onload" value="true" /> | ||
سطر 21: | سطر 20: | ||
public void initialize(CordovaInterface cordova, CordovaWebView webView) { | public void initialize(CordovaInterface cordova, CordovaWebView webView) { | ||
super.initialize(cordova, webView); | super.initialize(cordova, webView); | ||
// | // شيفرة التهيئة هنا | ||
}</syntaxhighlight>يمكن للإضافات الوصول إلى أحداث دورة حياة (lifecycle events) أندرويد، ويمكنها التعامل معها من خلال توسيع أحد التوابع (<code>onResume</code> و <code>onDestroy</code>...). الإضافات ذات الطلبيات الطويلة (long-running requests)، أو [[Cordova/Activity|النشاط]]ات الخلفية (background activity)، مثل قارئات الوسائط، أو المستمعات (listeners)، أو [[Cordova/Activity|النشاط]]ات ذات الحالة الداخلية (internal state) يجب أن تقدِّم (implement) التابع <code>onReset()</code>. والذي يُنفّذ عند انتقال | }</syntaxhighlight>يمكن للإضافات الوصول إلى أحداث دورة حياة (lifecycle events) أندرويد، ويمكنها التعامل معها من خلال توسيع أحد التوابع (<code>onResume</code> و <code>onDestroy</code>...إلخ). الإضافات ذات الطلبيات الطويلة (long-running requests)، أو [[Cordova/Activity|النشاط]]ات الخلفية (background activity)، مثل قارئات الوسائط، أو المستمعات (listeners)، أو [[Cordova/Activity|النشاط]]ات ذات الحالة الداخلية (internal state) يجب أن تقدِّم (implement) التابع <code>onReset()</code>. والذي يُنفّذ عند انتقال [[Cordova/webviews|العارض]] <code>WebView</code> إلى صفحة جديدة، أو عند تحديث الصفحة، ما يؤدي إلى إعادة تحميل [[JavaScript]]. | ||
==كتابة إضافة لأندرويد بلغة جافا== | ==كتابة إضافة لأندرويد بلغة جافا== | ||
يرسل استدعاء من [[JavaScript|جافاسكريبت]] طلبية إضافة (plugin request) إلى الجانب الأصلي (native side)، ويُعيّن إضافة [[Java|جافا]] المقابلة في الملف <code>config.xml</code>، | يرسل استدعاء من [[JavaScript|جافاسكريبت]] طلبية إضافة (plugin request) إلى الجانب الأصلي (native side)، ويُعيّن إضافة [[Java|جافا]] المقابلة في الملف <code>config.xml</code>، وسيُمرّر أي شيء يتم إرساله إلى الإضافة عبر الدالة <code>exec</code> التي تخص [[JavaScript]] إلى تابع <code>execute</code> الخاص بصنف بالإضافة. معظم التنفيذات <code>execute</code> تبدو كالتالي:<syntaxhighlight lang="java">@Override | ||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { | ||
if ("beep".equals(action)) { | if ("beep".equals(action)) { | ||
سطر 31: | سطر 30: | ||
return true; | return true; | ||
} | } | ||
return false; // | return false; // "MethodNotFound" في حال وقوع خطأ false إعادة القيمة | ||
} | }</syntaxhighlight>يتوافق المعامل <code>action</code> الخاص بالدالة <code>exec</code> تخص [[JavaScript]] مع تابع صنف خاص (private class method) والذي يُرسَل مع المعاملات الاختيارية. | ||
عند إمساك الاستثناءات والأخطاء، من المهم أن تطابق الأخطاء المُعادة إلى [[JavaScript| | عند إمساك الاستثناءات والأخطاء، من المهم أن تطابق الأخطاء المُعادة إلى [[JavaScript|JavaScript]] أسماء الاستثناءات في لغة [[Java|جافا]] قدر الممكن. | ||
==المهام الفرعية (Threading)== | ==المهام الفرعية (Threading)== | ||
لا تعمل إضافات [[JavaScript| | لا تعمل إضافات [[JavaScript|JavaScript]] في المهمة الرئيسية (main thread) للواجهة <code>WebView</code>؛ ولكنها تعمل في المهمة الفرعية <code>WebCore</code>، كما هو الحال مع التابع <code>execute</code>. إن كنت بحاجة إلى التفاعل مع واجهة المستخدم، فعليك استخدام التابع [http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable) <code>runOnUiThread</code>] الخاص بالنشاط ([[Cordova/Activity|Activity]]) كما يلي:<syntaxhighlight lang="java">@Override | ||
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { | public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { | ||
if ("beep".equals(action)) { | if ("beep".equals(action)) { | ||
سطر 43: | سطر 42: | ||
public void run() { | public void run() { | ||
... | ... | ||
callbackContext.success(); // Thread-safe | callbackContext.success(); // Thread-safe | ||
} | } | ||
}); | }); | ||
سطر 49: | سطر 48: | ||
} | } | ||
return false; | return false; | ||
} | }</syntaxhighlight>إذا لم تكن بحاجة إلى تشغيل الإضافة في المهمة الفرعية لواجهة المستخدم، ولكنك لا ترغب في إعاقة (block) المهمة الفرعية <code>WebCore</code>، فعليك تنفيذ الشيفرة البرمجية باستخدام [http://developer.android.com/reference/java/util/concurrent/ExecutorService.html <code>ExecutorService</code>] الناتجة عن <code>cordova.getThreadPool()</code> على النحو التالي:<syntaxhighlight lang="java">@Override | ||
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { | public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException { | ||
if ("beep".equals(action)) { | if ("beep".equals(action)) { | ||
سطر 62: | سطر 61: | ||
} | } | ||
return false; | return false; | ||
} | }</syntaxhighlight> | ||
==إضافة مكتبات | ==إضافة مكتبات الاعتماديات (Adding Dependency Libraries)== | ||
إن كان لإضافة أندرويد خاصتك | إن كان لإضافة أندرويد خاصتك اعتماديات (dependencies) إضافية، فيجب إدراجها في الملف <code>plugin.xml</code>؛ هناك طريقتان لفعل ذلك. | ||
الطريقة المثلى هي استخدام الوسم <code><framework /></code> (انظر صفحة [https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework مواصفات الإضافات] لمزيد من التفاصيل). يسمح تحديد المكتبات بهذه الطريقة | الطريقة المثلى هي استخدام الوسم <code><framework /></code> (انظر صفحة [https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework مواصفات الإضافات] لمزيد من التفاصيل). يسمح تحديد المكتبات بهذه الطريقة باستبيانها (تحديدها) عبر [https://docs.gradle.org/current/userguide/dependency_management.html منطق إدارة الاعتماديات] الخاص بالأداة Gradle. يسمح ذلك باستخدام المكتبات الشائعة مثل: <code>gson</code> و <code>android-support-v4</code> و <code>google-play-services</code> من قبل عدة إضافات دون حدوث تداخل. | ||
الخيار الثاني هو استخدام الوسم <code><lib-file /></code> لتحديد موضع ملف <code>jar</code> (انظر صفحة [https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework مواصفات الإضافات] لمزيد من التفاصيل). لا تستخدم هذه الطريقة إلا إن كنت على يقين من أنه لا توجد إضافة أخرى تعتمد على تلك المكتبة (مثلًا إن كانت المكتبة | الخيار الثاني هو استخدام الوسم <code><lib-file /></code> لتحديد موضع ملف <code>jar</code> (انظر صفحة [https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework مواصفات الإضافات] لمزيد من التفاصيل). لا تستخدم هذه الطريقة إلا إن كنت على يقين من أنه لا توجد إضافة أخرى تعتمد على تلك المكتبة (مثلًا إن كانت المكتبة خاصة فقط بإضافتك). وإلا فإنك تخاطر بالتسبب في إطلاق أخطاء بنائية لمستخدمي الإضافة في حال قامت إضافة أخرى بإضافة المكتبة نفسها. تجدر الإشارة إلى أنَّ مطوري تطبيقات كوردوفا ليسوا بالضرورة مطورين أصليين (أي لا يطورون بالضروة في اللغات الأصلية، مثل [[Java|جافا]])، لذا فإنّ أخطاء المنصة الأصلية البنائية قد تكون مصدر إحباط لهم. | ||
== | ==الإضافة echo مثالًا عن إضافة أندرويد== | ||
لمطابقة واجهة [[Cordova/plugins#.D9.85.D8.AB.D8.A7.D9.84 JavaScript|الميزة echo]] | لمطابقة واجهة [[Cordova/plugins#.D9.85.D8.AB.D8.A7.D9.84 JavaScript|الميزة echo]] التي شُرحَت في صفحة [[Cordova/plugins|الإضافات]]، استخدم الملف <code>plugin.xml</code> لإدراج مواصفات <code>feature</code> في الملف <code>config.xml</code> الخاص بالمنصة المحلية:<syntaxhighlight lang="xml"><platform name="android"> | ||
<config-file target="config.xml" parent="/*"> | <config-file target="config.xml" parent="/*"> | ||
<feature name="Echo"> | <feature name="Echo"> | ||
سطر 76: | سطر 75: | ||
</feature> | </feature> | ||
</config-file> | </config-file> | ||
<source-file src="src/android/Echo.java" target-dir="src/org/apache/cordova/plugin" /> | <source-file src="src/android/Echo.java" target-dir="src/org/apache/cordova/plugin" /> | ||
</platform> | </platform></syntaxhighlight>ثم أضف ما يلي إلى الملف <code>src/android/Echo.java</code>:<syntaxhighlight lang="java">package org.apache.cordova.plugin; | ||
import org.apache.cordova.CordovaPlugin; | import org.apache.cordova.CordovaPlugin; | ||
import org.apache.cordova.CallbackContext; | import org.apache.cordova.CallbackContext; | ||
import org.json.JSONArray; | import org.json.JSONArray; | ||
import org.json.JSONException; | import org.json.JSONException; | ||
import org.json.JSONObject; | import org.json.JSONObject; | ||
/** | /** | ||
* هذا الصنف يعرض سلسلة نصية مُستدعاة من جافاسكريبت | * هذا الصنف يعرض سلسلة نصية مُستدعاة من جافاسكريبت | ||
*/ | */ | ||
public class Echo extends CordovaPlugin { | public class Echo extends CordovaPlugin { | ||
@Override | @Override | ||
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { | public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { | ||
سطر 96: | سطر 100: | ||
return false; | return false; | ||
} | } | ||
private void echo(String message, CallbackContext callbackContext) { | private void echo(String message, CallbackContext callbackContext) { | ||
if (message != null && message.length() > 0) { | if (message != null && message.length() > 0) { | ||
سطر 103: | سطر 108: | ||
} | } | ||
} | } | ||
} | }</syntaxhighlight>تُوسّع عمليات الاستيراد (import) الموجودة في أعلى الملف الصنفَ <code>CordovaPlugin</code>، والذي يُعاد تعريف تابعه <code>execute()</code> لأجل تلقي الرسائل من <code>exec()</code>. يختبر التابع <code>execute()</code> في البداية القيمة <code>action</code>؛ في هذه الحالة، ليس لها سوى قيمة واحدة صالحة وهي "<code>echo"</code>. وأي قيمة أخرى ستعيد <code>false</code> وتطلق الخطأ <code>INVALID_ACTION</code>، والذي سيُحوّل إلى دالة الخطأ المستدعاة (error callback) من جانب شيفرة [[JavaScript|جافاسكريبت]]. | ||
بعد ذلك، يسترد التابع السلسلة النصية | بعد ذلك، يسترد التابع السلسلة النصية echo (أو صدى السلسلة النصية) باستخدام التابع <code>getString</code> الخاص بالكائن المعطى <code>args</code>، لتحديد المعامل الأول المُمرَّر إلى التابع. | ||
بعد تمرير القيمة إلى التابع | بعد تمرير القيمة إلى التابع <code>echo</code> الخاص، يٌفحص المعامل للتأكد من أنَّه لا يساوي <code>null</code> أو سلسلة نصية فارغة، وإلا سيستدعي التابعُ <code>callbackContext.error()</code> دالة الخطأ (error callback) في [[JavaScript|جافاسكريبت]]. إذا مرت التحقيقات بنجاح، فسيُمرر <code>callbackContext.success()</code> السلسلةَ النصية <code>message</code> الأصلية مرةً أخرى إلى دالة رد نداء النجاح (success callback) في [[JavaScript|JavaScript]] كمعامل. | ||
== | ==التكامل مع أندرويد (Android Integration)== | ||
يوفر أندرويد نظام مقاصد ( [http://developer.android.com/reference/android/content/Intent.html Intent] | يوفر أندرويد نظام مقاصد ([http://developer.android.com/reference/android/content/Intent.html Intent system]) يسمح للعمليات (processes) بالتواصل مع بعضها بعضًا. يمكن للإضافات الوصول إلى الكائنات <code>CordovaInterface</code> التي لديها القدرة على الوصول إلى [[Cordova/Activity|نشاط]] (Activity) أندرويد الذي يُشغّل التطبيق. هذا هو السياق ([http://developer.android.com/reference/android/content/Context.html Context]) المطلوب لإطلاق مقصد أندرويد جديد. تتيح <code>CordovaInterface</code> للإضافات بدء تشغيل [[Cordova/Activity|النشاط]] للحصول على نتيجة، وتعيين استدعاء الإضافة (callback plugin) لاستخدامه عند عودة [http://developer.android.com/reference/android/content/Intent.html المقصد] إلى التطبيق. | ||
بدءًا من كوردوفا 2.0، لم يعد بإمكان الإضافات الوصول إلى السياق [http://developer.android.com/reference/android/content/Context.html Context] مباشرةً، كما تم إيقاف العضو القديم <code>ctx</code>. | |||
كل توابع <code>ctx</code> صارت موجودة الآن في السياق [http://developer.android.com/reference/android/content/Context.html Context]، بحيث يمكن لكلا التابعين <code>getActivity()</code> و <code>getContext()</code> إعادة الكائن المطلوب. | كل توابع <code>ctx</code> صارت موجودة الآن في السياق [http://developer.android.com/reference/android/content/Context.html Context]، بحيث يمكن لكلا التابعين <code>getActivity()</code> و <code>getContext()</code> إعادة الكائن المطلوب. | ||
==أذونات أندرويد== | ==أذونات أندرويد== | ||
كانت أذونات أندرويد تُعالج حتى وقت قريب في وقت التثبيت (install-time)، | كانت أذونات أندرويد تُعالج حتى وقت قريب في وقت التثبيت (install-time)، بدلًا من وقت التشغيل (runtime). من الضروري التصريح بهذه الأذونات في أي تطبيق يستخدم الأذونات، ويجب إضافة هذه الأذونات إلى بيان أندرويد (Android Manifest). يمكن تحقيق ذلك باستخدام الملف <code>config.xml</code> لإدارج تلك الأذونات في الملف <code>AndroidManifest.xml</code>. | ||
يستخدم المثال التالي إذن جهات الاتصال (Contacts permission) | يستخدم المثال التالي إذن جهات الاتصال (Contacts permission):<syntaxhighlight lang="xml"><config-file target="AndroidManifest.xml" parent="/*"> | ||
<uses-permission android:name="android.permission.READ_CONTACTS" /> | <uses-permission android:name="android.permission.READ_CONTACTS" /> | ||
</config-file></syntaxhighlight> | </config-file></syntaxhighlight> | ||
===أذونات وقت التشغيل (Cordova-Android 5.0.0 فما فوق)=== | ===أذونات وقت التشغيل (Cordova-Android 5.0.0 فما فوق)=== | ||
قدم إصدار Android 6.0 "Marshmallow" نموذج أذونات جديد، حيث يمكن للمستخدم تشغيل وإيقاف الأذونات حسب الضرورة. هذا يعني | قدم إصدار Android 6.0 "Marshmallow" نموذج أذونات جديد، حيث يمكن للمستخدم تشغيل وإيقاف الأذونات حسب الضرورة. هذا يعني أنَّ التطبيقات يجب أن تعالج هذه التغييرات في الأذونات لأجل التوافق مع المستجدات، وهو ما كان محور الإصدار Cordova-Android 5.0.0. | ||
يمكن العثور على الأذونات التي تحتاج إلى المعالجة وقت التشغيل في [http://developer.android.com/guide/topics/security/permissions.html#perm-groups هذا الرابط]. | يمكن العثور على الأذونات التي تحتاج إلى المعالجة وقت التشغيل في [http://developer.android.com/guide/topics/security/permissions.html#perm-groups هذا الرابط]. | ||
بالنسبة للإضافات، يمكن طلب الإذن عن طريق استدعاء تابع الأذونات ذي | بالنسبة للإضافات، يمكن طلب الإذن عن طريق استدعاء تابع الأذونات ذي التوقيع التالي:<syntaxhighlight lang="java">cordova.requestPermission(CordovaPlugin plugin, int requestCode, String permission);</syntaxhighlight>من المعتاد تعيينه في متغير محلي ثابت.<syntaxhighlight lang="java">public static final String READ = Manifest.permission.READ_CONTACTS;</syntaxhighlight>من المتعارف عليه أيضًا تعريف شيفرة الطلب <code>requestCode</code> على النحو التالي:<syntaxhighlight lang="java">public static final int SEARCH_REQ_CODE = 0;</syntaxhighlight>ثم ينبغي التحقق من الإذن في التابع <code>exec</code>:<syntaxhighlight lang="java">if(cordova.hasPermission(READ)) | ||
{ | { | ||
search(executeArgs); | search(executeArgs); | ||
سطر 132: | سطر 137: | ||
{ | { | ||
getReadPermission(SEARCH_REQ_CODE); | getReadPermission(SEARCH_REQ_CODE); | ||
}</syntaxhighlight>وفي هذه الحالة، يكفي استدعاء التابع <code>requestPermission</code>:<syntaxhighlight lang=" | }</syntaxhighlight>وفي هذه الحالة، يكفي استدعاء التابع <code>requestPermission</code>:<syntaxhighlight lang="java">protected void getReadPermission(int requestCode) | ||
{ | { | ||
cordova.requestPermission(this, requestCode, READ); | cordova.requestPermission(this, requestCode, READ); | ||
}</syntaxhighlight>سيؤدي هذا إلى استدعاء | }</syntaxhighlight>سيؤدي هذا إلى استدعاء النشاط (activity) وظهور مِحَثّ (prompt) يطلب الإذن. وبمجرد حصول المستخدم على الإذن، يجب معالجة النتيجة باستخدام التابع <code>onRequestPermissionResult</code>، والذي يجب إعادة تعريفه في كل إضافة. | ||
إليك المثال التالي:<syntaxhighlight lang=" | إليك المثال التالي:<syntaxhighlight lang="java">public void onRequestPermissionResult(int requestCode, String[] permissions, | ||
int[] grantResults) throws JSONException | int[] grantResults) throws JSONException | ||
{ | { | ||
سطر 160: | سطر 165: | ||
break; | break; | ||
} | } | ||
} | }</syntaxhighlight>ستعود التعليمة <code>switch</code> أعلاه من المحث، وبناءًا على قيمة الوسيط <code>requestCode</code> المعطى، فقد تستدعي التابع المقابل. تجدر الإشارة إلى أنَّ محثات (prompts) الأذونات قد تتعطل (stack) في حالة عدم معالجة عملية التنفيذ بشكل صحيح، لهذا من المهم تجنب ذلك. | ||
بالإضافة إلى طلب الحصول على إذن واحد، من الممكن أيضًا طلب الأذونات لمجموعة بأكملها عن طريق تعريف مصفوفة الأذونات، كما هو الحال في الإضافة Geolocation:<syntaxhighlight lang=" | بالإضافة إلى طلب الحصول على إذن واحد، من الممكن أيضًا طلب الأذونات لمجموعة بأكملها عن طريق تعريف مصفوفة الأذونات، كما هو الحال في الإضافة Geolocation:<syntaxhighlight lang="java">String [] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION }; | ||
== | </syntaxhighlight>يعد ذلك، كل ما عليك القيام به لطلب الإذن، هو ما يلي:<syntaxhighlight lang="java">cordova.requestPermissions(this, 0, permissions);</syntaxhighlight>يعيد التعبير أعلاه الأذونات المحددة في المصفوفة. من المستحسن تمرير مصفوفة أذونات متاحة للعموم، نظرًا لأنه يمكن استخدامها من طرف الإضافات التي تستخدم إضافتك في ارتباطاتها، على الرغم من أنَّ هذا ليس ضروريًّا. | ||
يمكن | ==تنقيح إضافات أندرويد== | ||
يمكن تنقيح أخطاء أندرويد (Debugging Android Plugins) باستخدام Eclipse أو Android Studio، لكن يُوصَى باستخدام Android Studio. | |||
لمّا كانت منصة Cordova-Android | لمّا كانت منصة Cordova-Android تُستخدَم حاليًا كمكتبة مشروع، وكانت الإضافات مدعومة كشيفرة مصدرية، فمن الممكن تصحيح شيفرة [[Java|جافا]] داخل تطبيقات كوردوفا تمامًا مثل تطبيقات أندرويد الأصلية. | ||
==إطلاق أنشطة أخرى== | ==إطلاق أنشطة أخرى== | ||
هناك بعض الإجراءات الخاصة التي يجب اتخاذها إن كنت تريد من الإضافة أن تطلق | هناك بعض الإجراءات الخاصة التي يجب اتخاذها إن كنت تريد من الإضافة أن تطلق نشاطًا (Activity) يدفع (pushes) نشاط كوردوفا (Cordova Activity) إلى الخلفية. سيُنهِي أندرويد الأنشطة الموجودة في الخلفية عندما تنحسر الذاكرة المتاحة في الجهاز. في هذه الحالة، ستُنهَى أيضًا النسخة <code>CordovaPlugin</code>. | ||
هناك تابعان يمكن أن | إن كانت الإضافة تنتظر نتيجة من النشاط الذي أطلَقته، فسيتم إنشاء نسخةٍ جديدةٍ من الإضافة عند إعادة نشاط كوردوفا إلى الواجهة، وعند الحصول على النتيجة. على أي حال، لن تُحفَظ حالة الإضافة أو تستعاد تلقائيًا، وسيُفقد السياق <code>CallbackContext</code> الخاص بالإضافة. هناك تابعان يمكن أن تنفذهما <code>CordovaPlugin</code> للتعامل مع هذه المسألة:<syntaxhighlight lang="java">/** | ||
* تُستدعى عند إنهاء النشاط، مثلا إن استدعت الإضافة نشاطا خارجيا وقام نظام التشغيل | * تُستدعى عند إنهاء النشاط، مثلا إن استدعت الإضافة نشاطا خارجيا وقام نظام التشغيل | ||
* الموجود في الخلفية CordovaActivity بإنهاء | * الموجود في الخلفية CordovaActivity بإنهاء | ||
سطر 193: | سطر 197: | ||
* @param callbackContext السياق البديل الذي ستُعاد نتيجة الإضافة إليه | * @param callbackContext السياق البديل الذي ستُعاد نتيجة الإضافة إليه | ||
*/ | */ | ||
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}</syntaxhighlight>لاحظ أنه | public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}</syntaxhighlight>لاحظ أنه ينبغي ألا يُستخدم التابعان المذكورين أعلاه إلا إن كانت الإضافة ستطلق نشاطًا ([http://developer.android.com/reference/android/app/Activity.html Activity]) تتوخى نتيجةً منه، ويجب ألأ تستعيد إلا الحالة اللازمة لمعالجة نتيجة [[Cordova/Activity|النشاط]]. لن تُستعَاد (restore) حالة الإضافة إلا في حال الحصول من النشاط على نتيجة كانت الإضافة قد طلبتها (requested) باستخدام التابع <code>CordovaInterface</code> الخاص بالكائن <code>startActivityForResult()</code> وكان نظام التشغيل قد سبق وأنهى نشاط كوردوفا أثناء وجوده في الخلفية. | ||
الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> يتبغ الصيغة التالية:<syntaxhighlight lang=" | كجزء من التابع <code>onRestoreStateForActivityResult()</code>، سيتم تمرير سياق بديل <code>CallbackContext</code> إلى الإضافة. من المهم أن تدرك أنَّ السياق <code>CallbackContext</code> ليس هو نفسه السياق الذي تم إنهاؤه مع النشاط. إذ يُفقَد الاستدعاء الأصلي (original callback)، كما لن يُنفّذ في تطبيق [[JavaScript]]. بدلًا من ذلك، سيُعيد السياق <code>CallbackContext</code> النتيجة كجزء من الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> الذي يُفعّل عند استئناف تشغيل التطبيق. حمل (payload) الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> يتبغ الصيغة التالية:<syntaxhighlight lang="java">{ | ||
action: "resume", | action: "resume", | ||
pendingResult: { | pendingResult: { | ||
سطر 205: | سطر 207: | ||
} | } | ||
}</syntaxhighlight> | }</syntaxhighlight> | ||
* | *<code>pluginServiceName</code>: ستطابق [https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#name اسم العنصر] في الملف <code>plugin.xml</code>. | ||
*<code>pluginStatus</code> ستكون سلسلة نصية تصف حالة ناتج الإضافة (<code>PluginResult</code>) المٌمرر إلى السياق <code>CallbackContext</code>. راجع | *<code>pluginStatus</code>: ستكون سلسلة نصية تصف حالة ناتج الإضافة (<code>PluginResult</code>) المٌمرر إلى السياق <code>CallbackContext</code>. راجع الصفحة [https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/PluginResult.java PluginResult.java] للتعرف على قيم السلسلة النصية الموافقة لحالات الإضافات. | ||
*<code>result</code> ستساوي النتيجة التي مررتها الإضافة إلى <code>CallbackContext</code> (سلسلة نصية، عدد، كائن <code>JSON</code>، إلخ.) | *<code>result</code>: ستساوي النتيجة التي مررتها الإضافة إلى <code>CallbackContext</code> (سلسلة نصية، أو عدد، أو كائن <code>JSON</code>، ...إلخ.) | ||
سيتم تمرير محتوى <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> إلى كل | سيتم تمرير محتوى <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> إلى كل ردود النداء التي سجلتها تطبيقات [[JavaScript|JavaScript]] مع الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>. هذا يعني أنَّ النتيجة ستذهب مباشرةً إلى تطبيق كوردوفا؛ ولن يكون للإضافة فرصةٌ لمعالجة النتيجة بواسطة [[JavaScript|JavaScript]] قبل أن يتسلمها التطبيق. لذلك حاول جعل النتيجة المعادة من الشيفرة الأصلية كاملةً قدر الإمكان، وألا تعتمد على دوال [[JavaScript|جافاسكريبت]] عند إطلاق الأنشطة. | ||
تأكد من توضيح كيفية تفسير تطبيق كوردوفا للنتيجة التي يتلقاها عند وقوع الحدث<code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> | تأكد من توضيح كيفية تفسير تطبيق كوردوفا للنتيجة التي يتلقاها عند وقوع الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>، إذ يُناط بتطبيقات كوردوفا حفظ حالاتها، وتذكر الطلبيات التي أرسلتها، والوسائط التي مررتها إذا لزم الأمر. يجب عليك أيضًا توضيح معاني قيم <code>pluginStatus</code> ونوع البيانات التي ستعاد في الحقل <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> كجزء من الواجهة البرمجية للإضافة. | ||
هذه سلسلة الأحداث الكاملة لبدء | هذه سلسلة الأحداث الكاملة لبدء نشاط معين: | ||
*يستدعي تطبيق كوردوفا الإضافة خاصتك | *يستدعي تطبيق كوردوفا الإضافة خاصتك | ||
*تطلق الإضافة | *تطلق الإضافة نشاطًا تتوخى منه الحصول على نتيجة | ||
*ينهي نظام التشغيل أندرويد كلًا من | *ينهي نظام التشغيل أندرويد كلًا من النشاط ونسخة الإضافة | ||
** | **يُستدعَى التابع <code>onSaveInstanceState()</code> | ||
*يتفاعل المستخدم مع | *يتفاعل المستخدم مع نشاطك، ثم ينتهي النشاط | ||
*يعاد إنشاء | *يعاد إنشاء نشاط كوردوفا، ثم يُستَلم نتيجة النشاط | ||
** | **يُستدعَى التابع <code>onRestoreStateForActivityResult()</code> | ||
* | *يٌستدعَى <code>onActivityResult()</code> ثمَّ تمرِّر الإضافةُ النتيجةَ إلى السياق <code>CallbackContext</code> الجديد | ||
*يُفعّل الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>، ثم | *يُفعّل الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>، ثم يُستلَم من قبل تطبيق كوردوفا | ||
يوفر أندرويد إعدادات للمطوِّرين | يوفر أندرويد إعدادات للمطوِّرين لتنقيح أخطاء إنهاء الأنشطة عند حدوث انحسار في مساحة الذاكرة. قم بتمكين الإعداد "Don't keep activities" في قائمة خيارات المطور على جهازك أو المحاكي لمحاكاة سيناريوهات انحسار الذاكرة. إن كانت الإضافة تُطلِق نشاطات خارجية، فيجب عليك تنفيذ بعض الاختبارات مع تمكين هذا الإعداد لضمان التعامل بشكل صحيح مع سيناريوهات انخفاض الذاكرة. | ||
== انظر أيضًا == | |||
*[[Cordova/plugins|دليل تطوير الإضافات]] | |||
*[[Cordova/plugins ios|تطوير الإضافات على منصة iOS]] | |||
*[[Cordova/plugins windows|تطوير الإضافات على منصة ويندوز]] | |||
* [[Cordova/android upgrade|ترقية أندرويد]] | |||
==مصادر== | ==مصادر== | ||
*[https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html صفحة Android Plugin Development Guide في توثيق كوردوفا الرسمي] | *[https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html صفحة Android Plugin Development Guide في توثيق كوردوفا الرسمي] |
المراجعة الحالية بتاريخ 10:41، 1 ديسمبر 2020
يشرح هذا القسم كيفية تنفيذ شيفرات الإضافات الأصلية (native plugin code) على منصة أندرويد.
قبل قراءة هذه الصفحة، راجع صفحة دليل تطوير الإضافات لتكوين نظرة عامة على بنية الإضافات وواجهات JavaScript الخاصة بها. يواصل هذا القسم تطوير مثال الإضافة echo الوارد في دليل تطوير الإضافات، والذي يربط الاتصال من المعرض webview إلى المنصة الأصلية (native platform)، والعكس بالعكس. للحصول على مثال آخر، اطلع على التعليقات في الصفحة CordovaPlugin.java.
تستند إضافات أندرويد على منصة Cordova-Android
، التي تُنشَأ من المعرض Android WebView إضافةً إلى جسر أصلي (native bridge). يتألف الجزء الأصلي من إضافات أندرويد من صنف جافا واحد على الأقل، والذي يوسع الصنف CordovaPlugin
ويعيد تعريف تابعه execute
.
إعداد صنف الإضافة (Plugin Class Mapping)
تستخدم واجهة JavaScript الخاصة بالإضافة التابع cordova.exec
على النحو التالي:
exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);
ترسل هذه الشيفرة طلبية (request) من webview إلى الجانب الأصلي (native side) لأندرويد، وتستدعي التابع action
فعليًّا على الصنف service
، مع وسائط إضافية تُمرر في المصفوفة args
.
سواءً أقمت بتوزيع الإضافة على هيئة ملف جافا، أو كملف مضغوط jar
، يجب تحديد الإضافة في الملف res/xml/config.xml
الخاص بالتطبيق. انظر صفحة الإضافات لمزيد من المعلومات حول كيفية استخدام الملف plugin.xml
لإدارج العنصر feature
:
<feature name="<service_name>">
<param name="android-package" value="<full_name_including_namespace>" />
</feature>
يتطابق service_name
مع الاسم المستخدم عند الاستدعاء exec
في JavaScript. قيمته تساوي الاسم الكامل المؤهل (fully qualified namespace identifier) لصنف جافا. بخلاف ذلك، يمكن أن تُصرّف (compiled) الإضافة، لكنها لن تكون متاحة لكوردوفا.
تهيئة ودورة حياة الإضافات (Plugin Initialization and Lifetime)
يتم إنشاء نسخة (instance) واحدة من كائن الإضافة خلال دورة حياة المعرض WebView
. ولكن بعد الإشارة إليها عبر استدعاءٍ من JavaScript، إلا في حال إضافة الوسم <param>
، مع الخاصيتين name="onload"
و value="true"
في الملف config.xml
على النحو التالي:
<feature name="Echo">
<param name="android-package" value="<full_name_including_namespace>" />
<param name="onload" value="true" />
</feature>
يجب أن تستخدم الإضافات التابع initialize
في مرحلة الانطلاق.
@Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
// شيفرة التهيئة هنا
}
يمكن للإضافات الوصول إلى أحداث دورة حياة (lifecycle events) أندرويد، ويمكنها التعامل معها من خلال توسيع أحد التوابع (onResume
و onDestroy
...إلخ). الإضافات ذات الطلبيات الطويلة (long-running requests)، أو النشاطات الخلفية (background activity)، مثل قارئات الوسائط، أو المستمعات (listeners)، أو النشاطات ذات الحالة الداخلية (internal state) يجب أن تقدِّم (implement) التابع onReset()
. والذي يُنفّذ عند انتقال العارض WebView
إلى صفحة جديدة، أو عند تحديث الصفحة، ما يؤدي إلى إعادة تحميل JavaScript.
كتابة إضافة لأندرويد بلغة جافا
يرسل استدعاء من جافاسكريبت طلبية إضافة (plugin request) إلى الجانب الأصلي (native side)، ويُعيّن إضافة جافا المقابلة في الملف config.xml
، وسيُمرّر أي شيء يتم إرساله إلى الإضافة عبر الدالة exec
التي تخص JavaScript إلى تابع execute
الخاص بصنف بالإضافة. معظم التنفيذات execute
تبدو كالتالي:
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if ("beep".equals(action)) {
this.beep(args.getLong(0));
callbackContext.success();
return true;
}
return false; // "MethodNotFound" في حال وقوع خطأ false إعادة القيمة
}
يتوافق المعامل action
الخاص بالدالة exec
تخص JavaScript مع تابع صنف خاص (private class method) والذي يُرسَل مع المعاملات الاختيارية.
عند إمساك الاستثناءات والأخطاء، من المهم أن تطابق الأخطاء المُعادة إلى JavaScript أسماء الاستثناءات في لغة جافا قدر الممكن.
المهام الفرعية (Threading)
لا تعمل إضافات JavaScript في المهمة الرئيسية (main thread) للواجهة WebView
؛ ولكنها تعمل في المهمة الفرعية WebCore
، كما هو الحال مع التابع execute
. إن كنت بحاجة إلى التفاعل مع واجهة المستخدم، فعليك استخدام التابع runOnUiThread
الخاص بالنشاط (Activity) كما يلي:
@Override
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
if ("beep".equals(action)) {
final long duration = args.getLong(0);
cordova.getActivity().runOnUiThread(new Runnable() {
public void run() {
...
callbackContext.success(); // Thread-safe
}
});
return true;
}
return false;
}
إذا لم تكن بحاجة إلى تشغيل الإضافة في المهمة الفرعية لواجهة المستخدم، ولكنك لا ترغب في إعاقة (block) المهمة الفرعية WebCore
، فعليك تنفيذ الشيفرة البرمجية باستخدام ExecutorService
الناتجة عن cordova.getThreadPool()
على النحو التالي:
@Override
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
if ("beep".equals(action)) {
final long duration = args.getLong(0);
cordova.getThreadPool().execute(new Runnable() {
public void run() {
...
callbackContext.success(); // Thread-safe.
}
});
return true;
}
return false;
}
إضافة مكتبات الاعتماديات (Adding Dependency Libraries)
إن كان لإضافة أندرويد خاصتك اعتماديات (dependencies) إضافية، فيجب إدراجها في الملف plugin.xml
؛ هناك طريقتان لفعل ذلك.
الطريقة المثلى هي استخدام الوسم <framework />
(انظر صفحة مواصفات الإضافات لمزيد من التفاصيل). يسمح تحديد المكتبات بهذه الطريقة باستبيانها (تحديدها) عبر منطق إدارة الاعتماديات الخاص بالأداة Gradle. يسمح ذلك باستخدام المكتبات الشائعة مثل: gson
و android-support-v4
و google-play-services
من قبل عدة إضافات دون حدوث تداخل.
الخيار الثاني هو استخدام الوسم <lib-file />
لتحديد موضع ملف jar
(انظر صفحة مواصفات الإضافات لمزيد من التفاصيل). لا تستخدم هذه الطريقة إلا إن كنت على يقين من أنه لا توجد إضافة أخرى تعتمد على تلك المكتبة (مثلًا إن كانت المكتبة خاصة فقط بإضافتك). وإلا فإنك تخاطر بالتسبب في إطلاق أخطاء بنائية لمستخدمي الإضافة في حال قامت إضافة أخرى بإضافة المكتبة نفسها. تجدر الإشارة إلى أنَّ مطوري تطبيقات كوردوفا ليسوا بالضرورة مطورين أصليين (أي لا يطورون بالضروة في اللغات الأصلية، مثل جافا)، لذا فإنّ أخطاء المنصة الأصلية البنائية قد تكون مصدر إحباط لهم.
الإضافة echo مثالًا عن إضافة أندرويد
لمطابقة واجهة الميزة echo التي شُرحَت في صفحة الإضافات، استخدم الملف plugin.xml
لإدراج مواصفات feature
في الملف config.xml
الخاص بالمنصة المحلية:
<platform name="android">
<config-file target="config.xml" parent="/*">
<feature name="Echo">
<param name="android-package" value="org.apache.cordova.plugin.Echo"/>
</feature>
</config-file>
<source-file src="src/android/Echo.java" target-dir="src/org/apache/cordova/plugin" />
</platform>
ثم أضف ما يلي إلى الملف src/android/Echo.java
:
package org.apache.cordova.plugin;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* هذا الصنف يعرض سلسلة نصية مُستدعاة من جافاسكريبت
*/
public class Echo extends CordovaPlugin {
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("echo")) {
String message = args.getString(0);
this.echo(message, callbackContext);
return true;
}
return false;
}
private void echo(String message, CallbackContext callbackContext) {
if (message != null && message.length() > 0) {
callbackContext.success(message);
} else {
callbackContext.error("Expected one non-empty string argument.");
}
}
}
تُوسّع عمليات الاستيراد (import) الموجودة في أعلى الملف الصنفَ CordovaPlugin
، والذي يُعاد تعريف تابعه execute()
لأجل تلقي الرسائل من exec()
. يختبر التابع execute()
في البداية القيمة action
؛ في هذه الحالة، ليس لها سوى قيمة واحدة صالحة وهي "echo"
. وأي قيمة أخرى ستعيد false
وتطلق الخطأ INVALID_ACTION
، والذي سيُحوّل إلى دالة الخطأ المستدعاة (error callback) من جانب شيفرة جافاسكريبت.
بعد ذلك، يسترد التابع السلسلة النصية echo (أو صدى السلسلة النصية) باستخدام التابع getString
الخاص بالكائن المعطى args
، لتحديد المعامل الأول المُمرَّر إلى التابع.
بعد تمرير القيمة إلى التابع echo
الخاص، يٌفحص المعامل للتأكد من أنَّه لا يساوي null
أو سلسلة نصية فارغة، وإلا سيستدعي التابعُ callbackContext.error()
دالة الخطأ (error callback) في جافاسكريبت. إذا مرت التحقيقات بنجاح، فسيُمرر callbackContext.success()
السلسلةَ النصية message
الأصلية مرةً أخرى إلى دالة رد نداء النجاح (success callback) في JavaScript كمعامل.
التكامل مع أندرويد (Android Integration)
يوفر أندرويد نظام مقاصد (Intent system) يسمح للعمليات (processes) بالتواصل مع بعضها بعضًا. يمكن للإضافات الوصول إلى الكائنات CordovaInterface
التي لديها القدرة على الوصول إلى نشاط (Activity) أندرويد الذي يُشغّل التطبيق. هذا هو السياق (Context) المطلوب لإطلاق مقصد أندرويد جديد. تتيح CordovaInterface
للإضافات بدء تشغيل النشاط للحصول على نتيجة، وتعيين استدعاء الإضافة (callback plugin) لاستخدامه عند عودة المقصد إلى التطبيق.
بدءًا من كوردوفا 2.0، لم يعد بإمكان الإضافات الوصول إلى السياق Context مباشرةً، كما تم إيقاف العضو القديم ctx
.
كل توابع ctx
صارت موجودة الآن في السياق Context، بحيث يمكن لكلا التابعين getActivity()
و getContext()
إعادة الكائن المطلوب.
أذونات أندرويد
كانت أذونات أندرويد تُعالج حتى وقت قريب في وقت التثبيت (install-time)، بدلًا من وقت التشغيل (runtime). من الضروري التصريح بهذه الأذونات في أي تطبيق يستخدم الأذونات، ويجب إضافة هذه الأذونات إلى بيان أندرويد (Android Manifest). يمكن تحقيق ذلك باستخدام الملف config.xml
لإدارج تلك الأذونات في الملف AndroidManifest.xml
.
يستخدم المثال التالي إذن جهات الاتصال (Contacts permission):
<config-file target="AndroidManifest.xml" parent="/*">
<uses-permission android:name="android.permission.READ_CONTACTS" />
</config-file>
أذونات وقت التشغيل (Cordova-Android 5.0.0 فما فوق)
قدم إصدار Android 6.0 "Marshmallow" نموذج أذونات جديد، حيث يمكن للمستخدم تشغيل وإيقاف الأذونات حسب الضرورة. هذا يعني أنَّ التطبيقات يجب أن تعالج هذه التغييرات في الأذونات لأجل التوافق مع المستجدات، وهو ما كان محور الإصدار Cordova-Android 5.0.0.
يمكن العثور على الأذونات التي تحتاج إلى المعالجة وقت التشغيل في هذا الرابط.
بالنسبة للإضافات، يمكن طلب الإذن عن طريق استدعاء تابع الأذونات ذي التوقيع التالي:
cordova.requestPermission(CordovaPlugin plugin, int requestCode, String permission);
من المعتاد تعيينه في متغير محلي ثابت.
public static final String READ = Manifest.permission.READ_CONTACTS;
من المتعارف عليه أيضًا تعريف شيفرة الطلب requestCode
على النحو التالي:
public static final int SEARCH_REQ_CODE = 0;
ثم ينبغي التحقق من الإذن في التابع exec
:
if(cordova.hasPermission(READ))
{
search(executeArgs);
}
else
{
getReadPermission(SEARCH_REQ_CODE);
}
وفي هذه الحالة، يكفي استدعاء التابع requestPermission
:
protected void getReadPermission(int requestCode)
{
cordova.requestPermission(this, requestCode, READ);
}
سيؤدي هذا إلى استدعاء النشاط (activity) وظهور مِحَثّ (prompt) يطلب الإذن. وبمجرد حصول المستخدم على الإذن، يجب معالجة النتيجة باستخدام التابع onRequestPermissionResult
، والذي يجب إعادة تعريفه في كل إضافة.
إليك المثال التالي:
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException
{
for(int r:grantResults)
{
if(r == PackageManager.PERMISSION_DENIED)
{
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
return;
}
}
switch(requestCode)
{
case SEARCH_REQ_CODE:
search(executeArgs);
break;
case SAVE_REQ_CODE:
save(executeArgs);
break;
case REMOVE_REQ_CODE:
remove(executeArgs);
break;
}
}
ستعود التعليمة switch
أعلاه من المحث، وبناءًا على قيمة الوسيط requestCode
المعطى، فقد تستدعي التابع المقابل. تجدر الإشارة إلى أنَّ محثات (prompts) الأذونات قد تتعطل (stack) في حالة عدم معالجة عملية التنفيذ بشكل صحيح، لهذا من المهم تجنب ذلك.
بالإضافة إلى طلب الحصول على إذن واحد، من الممكن أيضًا طلب الأذونات لمجموعة بأكملها عن طريق تعريف مصفوفة الأذونات، كما هو الحال في الإضافة Geolocation:
String [] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };
يعد ذلك، كل ما عليك القيام به لطلب الإذن، هو ما يلي:
cordova.requestPermissions(this, 0, permissions);
يعيد التعبير أعلاه الأذونات المحددة في المصفوفة. من المستحسن تمرير مصفوفة أذونات متاحة للعموم، نظرًا لأنه يمكن استخدامها من طرف الإضافات التي تستخدم إضافتك في ارتباطاتها، على الرغم من أنَّ هذا ليس ضروريًّا.
تنقيح إضافات أندرويد
يمكن تنقيح أخطاء أندرويد (Debugging Android Plugins) باستخدام Eclipse أو Android Studio، لكن يُوصَى باستخدام Android Studio.
لمّا كانت منصة Cordova-Android تُستخدَم حاليًا كمكتبة مشروع، وكانت الإضافات مدعومة كشيفرة مصدرية، فمن الممكن تصحيح شيفرة جافا داخل تطبيقات كوردوفا تمامًا مثل تطبيقات أندرويد الأصلية.
إطلاق أنشطة أخرى
هناك بعض الإجراءات الخاصة التي يجب اتخاذها إن كنت تريد من الإضافة أن تطلق نشاطًا (Activity) يدفع (pushes) نشاط كوردوفا (Cordova Activity) إلى الخلفية. سيُنهِي أندرويد الأنشطة الموجودة في الخلفية عندما تنحسر الذاكرة المتاحة في الجهاز. في هذه الحالة، ستُنهَى أيضًا النسخة CordovaPlugin
.
إن كانت الإضافة تنتظر نتيجة من النشاط الذي أطلَقته، فسيتم إنشاء نسخةٍ جديدةٍ من الإضافة عند إعادة نشاط كوردوفا إلى الواجهة، وعند الحصول على النتيجة. على أي حال، لن تُحفَظ حالة الإضافة أو تستعاد تلقائيًا، وسيُفقد السياق CallbackContext
الخاص بالإضافة. هناك تابعان يمكن أن تنفذهما CordovaPlugin
للتعامل مع هذه المسألة:
/**
* تُستدعى عند إنهاء النشاط، مثلا إن استدعت الإضافة نشاطا خارجيا وقام نظام التشغيل
* الموجود في الخلفية CordovaActivity بإنهاء
* ينبغي أن تحفظ الإضافة حالتها في هذا التابع فقط إن كانت تنتظر نتيجة من النشاط
* الخارجي، وكانت بحاجة إلى حفظ بعض االمعلومات لأجل معالجة النتيجة
* إلا في حال كانت الإضافة ستستلِم نتيجة النشاط onRestoreStateForActivityResult() لن يُستدعي
*
*
* @return حزمة تحتوي حالة الإضافة أو القيمة المعدومة إن لم تكن هناك حاجة لحفظ
* الحالة
*/
public Bundle onSaveInstanceState() {}
/**
*
* CordovaActivity تُستدعى إذا كانت الإضافة ستستلِم نتيجة نشاط ما بعد إنهاء
* onSaveInstanceState() الحزمة ستكون مماثلة لتلك المعادة من الإضافة في
*
* @param state حزمة تحتوي حالة الإضافة
* @param callbackContext السياق البديل الذي ستُعاد نتيجة الإضافة إليه
*/
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}
لاحظ أنه ينبغي ألا يُستخدم التابعان المذكورين أعلاه إلا إن كانت الإضافة ستطلق نشاطًا (Activity) تتوخى نتيجةً منه، ويجب ألأ تستعيد إلا الحالة اللازمة لمعالجة نتيجة النشاط. لن تُستعَاد (restore) حالة الإضافة إلا في حال الحصول من النشاط على نتيجة كانت الإضافة قد طلبتها (requested) باستخدام التابع CordovaInterface
الخاص بالكائن startActivityForResult()
وكان نظام التشغيل قد سبق وأنهى نشاط كوردوفا أثناء وجوده في الخلفية.
كجزء من التابع onRestoreStateForActivityResult()
، سيتم تمرير سياق بديل CallbackContext
إلى الإضافة. من المهم أن تدرك أنَّ السياق CallbackContext
ليس هو نفسه السياق الذي تم إنهاؤه مع النشاط. إذ يُفقَد الاستدعاء الأصلي (original callback)، كما لن يُنفّذ في تطبيق JavaScript. بدلًا من ذلك، سيُعيد السياق CallbackContext
النتيجة كجزء من الحدث resume
الذي يُفعّل عند استئناف تشغيل التطبيق. حمل (payload) الحدث resume
يتبغ الصيغة التالية:
{
action: "resume",
pendingResult: {
pluginServiceName: string,
pluginStatus: string,
result: any
}
}
pluginServiceName
: ستطابق اسم العنصر في الملفplugin.xml
.pluginStatus
: ستكون سلسلة نصية تصف حالة ناتج الإضافة (PluginResult
) المٌمرر إلى السياقCallbackContext
. راجع الصفحة PluginResult.java للتعرف على قيم السلسلة النصية الموافقة لحالات الإضافات.result
: ستساوي النتيجة التي مررتها الإضافة إلىCallbackContext
(سلسلة نصية، أو عدد، أو كائنJSON
، ...إلخ.)
سيتم تمرير محتوى resume
إلى كل ردود النداء التي سجلتها تطبيقات JavaScript مع الحدث resume
. هذا يعني أنَّ النتيجة ستذهب مباشرةً إلى تطبيق كوردوفا؛ ولن يكون للإضافة فرصةٌ لمعالجة النتيجة بواسطة JavaScript قبل أن يتسلمها التطبيق. لذلك حاول جعل النتيجة المعادة من الشيفرة الأصلية كاملةً قدر الإمكان، وألا تعتمد على دوال جافاسكريبت عند إطلاق الأنشطة.
تأكد من توضيح كيفية تفسير تطبيق كوردوفا للنتيجة التي يتلقاها عند وقوع الحدث resume
، إذ يُناط بتطبيقات كوردوفا حفظ حالاتها، وتذكر الطلبيات التي أرسلتها، والوسائط التي مررتها إذا لزم الأمر. يجب عليك أيضًا توضيح معاني قيم pluginStatus
ونوع البيانات التي ستعاد في الحقل resume
كجزء من الواجهة البرمجية للإضافة.
هذه سلسلة الأحداث الكاملة لبدء نشاط معين:
- يستدعي تطبيق كوردوفا الإضافة خاصتك
- تطلق الإضافة نشاطًا تتوخى منه الحصول على نتيجة
- ينهي نظام التشغيل أندرويد كلًا من النشاط ونسخة الإضافة
- يُستدعَى التابع
onSaveInstanceState()
- يُستدعَى التابع
- يتفاعل المستخدم مع نشاطك، ثم ينتهي النشاط
- يعاد إنشاء نشاط كوردوفا، ثم يُستَلم نتيجة النشاط
- يُستدعَى التابع
onRestoreStateForActivityResult()
- يُستدعَى التابع
- يٌستدعَى
onActivityResult()
ثمَّ تمرِّر الإضافةُ النتيجةَ إلى السياقCallbackContext
الجديد - يُفعّل الحدث
resume
، ثم يُستلَم من قبل تطبيق كوردوفا
يوفر أندرويد إعدادات للمطوِّرين لتنقيح أخطاء إنهاء الأنشطة عند حدوث انحسار في مساحة الذاكرة. قم بتمكين الإعداد "Don't keep activities" في قائمة خيارات المطور على جهازك أو المحاكي لمحاكاة سيناريوهات انحسار الذاكرة. إن كانت الإضافة تُطلِق نشاطات خارجية، فيجب عليك تنفيذ بعض الاختبارات مع تمكين هذا الإعداد لضمان التعامل بشكل صحيح مع سيناريوهات انخفاض الذاكرة.