الفرق بين المراجعتين لصفحة: «Cordova/plugins android»

من موسوعة حسوب
لا ملخص تعديل
لا ملخص تعديل
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:إضافات أندرويد في Cordova}}</noinclude>
<noinclude>{{DISPLAYTITLE:إضافات أندرويد في Cordova}}</noinclude>
[[تصنيف: Cordova]]
[[تصنيف: Cordova]]
يقدم هذا القسم كيفية تقديم (implement) أكواد الإضافات الأصلية (native plugin code) على منصة أندرويد.
يوضح هذا القسم كيفية تقديم (implement) شيفرات الإضافات الأصلية (native plugin code) على منصة أندرويد.


قبل قراءة هذه الصفحة، راجع صفحة [[Cordova/plugins|دليل تطوير الإضافات]] لتكوين نظرة عامة على بنية الإضافات وواجهات [[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].
قبل قراءة هذه الصفحة، راجع صفحة [[Cordova/plugins|دليل تطوير الإضافات]] لتكوين نظرة عامة على بنية الإضافات وواجهات [[JavaScript|جافاسكريبت]] الخاصة بها. يواصل هذا القسم تطوير [[Cordova/plugins#.D9.85.D8.AB.D8.A7.D9.84 JavaScript|مثال الإضافة <code>echo</code>]] الوارد في دليل تطوير الإضافات، والذي يربط الاتصال من المعرض [[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>، التي تُنشؤُ من المعرض [[Cordova/webviews|Android WebView]] إضافة إلى جسر أصلي (native bridge). يتألف الجزء الأصلي من إضافات أندرويد من صنف [[Java|جافا]] واحد على الأقل، والذي يوسع الصنف <code>CordovaPlugin</code> ويعيد تعريف أحد توابعه <code>execute</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]] الخاصة بالإضافة التابع <code>cordova.exec</code> على النحو التالي:<syntaxhighlight lang="javascript">exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);‎</syntaxhighlight>هذه الشيفرة ترسل طلبية (request) من [[Cordova/webviews|webview]] إلى الجانب الأصلي (native side) لأندرويد، وتستدعي التابع <code>action</code> فعليا على الصنف <code>service</code>، مع وسائط إضافية تُمرر في المصفوفة <code>args</code>.
تستخدم واجهة [[JavaScript|جافاسكريبت]] الخاصة بالإضافة التابع <code>cordova.exec</code> على النحو التالي:<syntaxhighlight lang="javascript">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> الخاص بتطبيق <code>Cordova-Android</code>. انظر صفحة [[Cordova/plugins|الإضافات]] لمزيد من المعلومات حول كيفية استخدام ملف <code>plugin.xml</code> لإدارج العنصر <code>feature</code>:<syntaxhighlight lang="xml"><feature name="<service_name>">
سواء أقمت بتوزيع الإضافة على هيئة ملف [[Java|جافا]]، أو كملف مضغوط <code>jar</code>، يجب تحديد الإضافة في ملف <code>res/xml/config.xml</code> الخاص بتطبيق <code>Cordova-Android</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>‎</syntaxhighlight>يتطابق <code>service_name</code> مع الاسم المستخدم عند استدعاء دالة [[Java|الجافا]] <code>exec</code> . قيمته تساوي الاسم الكامل المؤهل لصنف [[Java|جافا]] (ava class's fully qualified namespace identifier). بخلاف ذلك، يمكن أن تُصرّف (compiled) الإضافة لكنها لن تكون متاحة لكوردوفا.
</feature>‎</syntaxhighlight>يتطابق <code>service_name</code> مع الاسم المستخدم عند استدعاء دالة [[Java|الجافا]] <code>exec</code> . قيمته تساوي الاسم الكامل المؤهل لصنف [[Java|جافا]] (java class's fully qualified namespace identifier). بخلاف ذلك، يمكن أن تُصرّف (compiled) الإضافة، لكنها لن تكون متاحة لكوردوفا.
==تهيئة الإضافات ودورة الحياة (Plugin Initialization and Lifetime)==
==تهيئة الإضافات ودورة الحياة (Plugin Initialization and Lifetime)==
يتم إنشاء نسخة (instance) واحدة من كائن الإضافة خلال دورة حياة المعرض <code>[[Cordova/webviews|WebView]]</code>. ولكن بعد الإشارة إليها أولاً عبر استدعاء من [[Java|JavaScript]]، إلا في حال إضافة وسم <code><param></code> مع <code>name="onload"‎</code> و <code>value="true"‎</code>في ملف <code>config.xml</code>. مثلا:<syntaxhighlight lang="xml"><feature name="Echo">
يتم إنشاء نسخة (instance) واحدة من كائن الإضافة خلال دورة حياة المعرض <code>[[Cordova/webviews|WebView]]</code>. ولكن بعد الإشارة إليها عبر استدعاءٍ من [[Java|جافاسكريبت]]، إلا في حال إضافة الوسم <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" />
سطر 20: سطر 20:
     super.initialize(cordova, webView);
     super.initialize(cordova, webView);
     // your init code here
     // your init code here
}‎</syntaxhighlight>يمكن للإضافات الوصول إلى أحداث دورة حياة (lifecycle events) أندرويد، ويمكنها التعامل معها من خلال توسيع أحد التوابع (<code>onResume</code> و <code>onDestroy</code>، إلخ). الإضافات ذات الطلبيات الطويلة (long-running requests)، أو [[Cordova/Activity|النشاط]]ات الخلفية (background activity)، مثل قارئات الوسائط، أو المستمعات (listeners)، أو ذات الحالة الداخلية (internal state) يجب أن تقدّم (implement) التابع <code>onReset()‎</code>. والذي يُنفّذ عند انتقال المعرض(<code>WebView)</code> إلى صفحة جديدة، أو عند تحديث الصفحة، ما يؤدي إلى إعادة تحميل [[Java|JavaScript]].
}‎</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>. والذي يُنفّذ عند انتقال المعرض(<code>WebView)</code> إلى صفحة جديدة، أو عند تحديث الصفحة، ما يؤدي إلى إعادة تحميل [[Java|جافاسكريبت]].
==كتابة إضافة لأندرويد بلغة جافا==
==كتابة إضافة لأندرويد بلغة جافا==
يرسل استدعاء من [[JavaScript]] طلبية إضافة (plugin request) إلى الجانب الأصلي (native side)، ويُعيّن إضافة [[Java|جافا]] المقابلة في ملف <code>config.xml</code>، ولكن كيف سيبدو الشكل النهائي لصنف إضافة [[Java|جافا]]-أندرويد؟ أي شيء يتم إرساله إلى الإضافة عبر دالة [[JavaScript|الجافاسكريبت]] <code>exec</code> سيُمرّر إلى تابع <code>execute</code> الخاص بصنف بالإضافة. معظم تقديمات<code>execute</code> تبدو كالتالي:<syntaxhighlight lang="javascript">@Override
يرسل استدعاء من [[JavaScript|جافاسكريبت]] طلبية إضافة (plugin request) إلى الجانب الأصلي (native side)، ويُعيّن إضافة [[Java|جافا]] المقابلة في الملف <code>config.xml</code>، وأي شيء يتم إرساله إلى الإضافة عبر دالة [[JavaScript|الجافاسكريبت]] <code>exec</code> سيُمرّر إلى تابع <code>execute</code> الخاص بصنف بالإضافة. معظم تقديمات<code>execute</code> تبدو كالتالي:<syntaxhighlight lang="javascript">@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)) {
سطر 30: سطر 30:
     }
     }
     return false;  // Returning false results in a "MethodNotFound" error.
     return false;  // Returning false results in a "MethodNotFound" error.
}‎</syntaxhighlight>يتوافق الوسيط <code>action</code> الخاص بدالة [[JavaScript|جافاسكريبت]] <code>exec</code> مع تابع صنف خاص (private class method)، والذي يُرسل مع الوسائط الاختيارية.
}‎</syntaxhighlight>يتوافق الوسيط <code>action</code> الخاص بدالة [[JavaScript|جافاسكريبت]] <code>exec</code> مع تابع صنف خاص (private class method)، والذي يُرسل مع الوسائط الاختيارية.


عند إمساك الاستثناءات والأخطاء، من المهم أن تطابق الأخطاء المُعاادة إلى [[JavaScript]] أسماء الاستثناءات في لغة [[Java|جافا]] قدر الممكن.
عند إمساك الاستثناءات والأخطاء، من المهم أن تطابق الأخطاء المُعادة إلى [[JavaScript|جافاسكريبت]] أسماء الاستثناءات في لغة [[Java|جافا]] قدر الممكن.
==المهام الفرعية (Threading)==
==المهام الفرعية (Threading)==
لا تعمل إضافات [[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) runOnUiThread] الخاص بالنشاط ([[Cordova/Activity|Activity]]) كما يلي:<syntaxhighlight lang="javascript">@Override
لا تعمل إضافات [[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) runOnUiThread] الخاص بالنشاط ([[Cordova/Activity|Activity]]) كما يلي:<syntaxhighlight lang="javascript">@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: سطر 62:
}‎</syntaxhighlight>
}‎</syntaxhighlight>
==إضافة مكتبات الارتباط (Adding Dependency Libraries)==
==إضافة مكتبات الارتباط (Adding Dependency Libraries)==
إن كان لإضافة أندرويد خاصتك ارتباطات (dependencies) إضافية، فيجب إدراجها في ملف <code>plugin.xml</code>، هناك طريقتان لفعل ذلك.
إن كان لإضافة أندرويد خاصتك ارتباطات (dependencies) إضافية، فيجب إدراجها في الملف <code>plugin.xml</code>، هناك طريقتان لفعل ذلك.


الطريقة المثلى هي استخدام الوسم <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><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 مواصفات الإضافات] لمزيد من التفاصيل). لا تستخدم هذه الطريقة إلا إن كنت على يقين من أنه لا توجد إضافة أخرى تعتمد على تلك المكتبة (مثلًا إن كانت المكتبة مخصوصة بالإضافة). وإلا فإنك تخاطر بالتسبب في إطلاق أخطاء بنائية لمستخدمي الإضافة في حال قامت إضافة أخرى بإضافة المكتبة نفسها. تجدر الإشارة إلى أن مطوري تطبيقات Cordova ليسوا بالضرورة مطورين أصليين (أي لا يطورون بالضروة في اللغات الأصلية، مثل [[Java|جافا]])، لذا فإنّ أخطاء المنصة الأصلية البنائية قد تكون مصدر إحباط لهم.
الخيار الثاني هو استخدام الوسم <code><lib-file /‎></code> لتحديد موضع ملف <code>jar</code> (انظر صفحة [https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework مواصفات الإضافات] لمزيد من التفاصيل). لا تستخدم هذه الطريقة إلا إن كنت على يقين من أنه لا توجد إضافة أخرى تعتمد على تلك المكتبة (مثلًا إن كانت المكتبة مخصوصة بالإضافة). وإلا فإنك تخاطر بالتسبب في إطلاق أخطاء بنائية لمستخدمي الإضافة في حال قامت إضافة أخرى بإضافة المكتبة نفسها. تجدر الإشارة إلى أن مطوري تطبيقات كوردوفا ليسوا بالضرورة مطورين أصليين (أي لا يطورون بالضروة في اللغات الأصلية، مثل [[Java|جافا]])، لذا فإنّ أخطاء المنصة الأصلية البنائية قد تكون مصدر إحباط لهم.
==مثال لإضافة أندرويد==
==مثال لإضافة أندرويد==
لمطابقة واجهة الميزة echo المقدمة في صفحة [[Cordova/plugins|الإضافات]]، استخدم الملف <code>plugin.xml</code> لإدراج مواصفات <code>feature</code> في ملف <code>config.xml</code> الخاص بالمنصة المحلية:<syntaxhighlight lang="xml"><platform name="android">
لمطابقة واجهة [[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">
سطر 101: سطر 101:
     }
     }
}
}
}‎</syntaxhighlight>توسع عمليات الاستيراد (<code>import</code>) الموجودة في أعلى الملف الصنف <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|الجافاسكريبت]].
}‎</syntaxhighlight>تُوسّع عمليات الاستيراد (<code>import</code>) الموجودة في أعلى الملف الصنفَ <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]] كوسيط.
بعد ذلك، يسترد التابع السلسلة النصية لـ 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|جافاسكريبت]] كوسيط.
==تكامل أندرويد (Android Integration)==
==تكامل أندرويد (Android Integration)==
يوفر أندرويد نظام مقاصد ( [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 المقصد] إلى التطبيق.
يوفر أندرويد نظام مقاصد ( [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 المقصد] إلى التطبيق.


اعتبارًا من Cordova 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> إعادة الكائن المطلوب.
اعتبارًا من كوردوفا 2.0، لم يعد بإمكان الإضافات الوصول إلى السياق [http://developer.android.com/reference/android/content/Context.html Context] مباشرةً، كما تم إيقاف العضو القديم <code>ctx</code>.
== أذونات أندرويد ==


كانت أذونات أندرويد تُعالج حتى وقت قريب في وقت التثبيت (install-time)، بدلاً من وقت التشغيل (runtime). من الضروري التصريح بهذه الأذونات في أي تطبيق يستخدم الأذونات، ويجب إضافة هذه الأذونات إلى بيان أندرويد (Android Manifest). يمكن تحقيق ذلك باستخدام الملف <code>config.xml</code> لإدارج تلك الأذونات في الملف <code>AndroidManifest.xml</code>. يستخدم المثال التالي إذن جهات الاتصال (Contacts permission).  
كل توابع <code>ctx</code> صارت موجودة الآن في السياق [http://developer.android.com/reference/android/content/Context.html Context]، بحيث يمكن لكلا التابعين <code>getActivity()‎</code> و <code>getContext()‎</code> إعادة الكائن المطلوب.
<syntaxhighlight lang="xml"><config-file target="AndroidManifest.xml" parent="/*">
==أذونات أندرويد==
كانت أذونات أندرويد تُعالج حتى وقت قريب في وقت التثبيت (install-time)، بدلاً من وقت التشغيل (runtime). من الضروري التصريح بهذه الأذونات في أي تطبيق يستخدم الأذونات، ويجب إضافة هذه الأذونات إلى بيان أندرويد (Android Manifest). يمكن تحقيق ذلك باستخدام الملف <code>config.xml</code> لإدارج تلك الأذونات في الملف <code>AndroidManifest.xml</code>.
 
يستخدم المثال التالي إذن جهات الاتصال (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"‎ نموذج أذونات جديد، حيث يمكن للمستخدم تشغيل وإيقاف الأذونات حسب الضرورة. هذا يعني أن التطبيقات يجب أن تعالج هذه التغييرات في الأذونات لأجل التوافق مع المستجدات، وهو ما كان محور الإصدار Cordova-Android 5.0.0.


قدم إصدار 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="javascript">cordova.requestPermission(CordovaPlugin plugin, int requestCode, String permission);‎</syntaxhighlight>من المعتاد تعيينه في متغير محلي ثابت.<syntaxhighlight>public static final String READ = Manifest.permission.READ_CONTACTS;‎</syntaxhighlight>من المتعارف عليه أيضًا تعريف شيفرة الطلب<code>requestCode</code> على النحو التالي:<syntaxhighlight lang="javascript">public static final int SEARCH_REQ_CODE = 0;‎</syntaxhighlight>ثم ينبغي التحقق من الإذن في التابع <code>exec</code>:<syntaxhighlight lang="javascript">if(cordova.hasPermission(READ))
 
بالنسبة للإضافات، يمكن طلب الإذن عن طريق استدعاء تابع الأذونات ذي الإمضاء التالي:  
<syntaxhighlight lang="javascript">cordova.requestPermission(CordovaPlugin plugin, int requestCode, String permission);‎</syntaxhighlight>  
 
من المعتاد تعيينه في متغير محلي ثابت.  
<syntaxhighlight>public static final String READ = Manifest.permission.READ_CONTACTS;‎</syntaxhighlight>  
 
من المتعارف عليه أيضًا تعريف شيفرة الطلب<code>requestCode</code> على النحو التالي:  
<syntaxhighlight lang="javascript">public static final int SEARCH_REQ_CODE = 0;‎</syntaxhighlight>  
 
ثم ينبغي التحقق من الإذن في التابع <code>exec</code>:  
<syntaxhighlight lang="javascript">if(cordova.hasPermission(READ))
{
{
     search(executeArgs);
     search(executeArgs);
سطر 137: سطر 130:
{
{
     getReadPermission(SEARCH_REQ_CODE);
     getReadPermission(SEARCH_REQ_CODE);
}‎</syntaxhighlight>  
}‎</syntaxhighlight>وفي هذه الحالة، يكفي استدعاء التابع <code>requestPermission</code>:<syntaxhighlight lang="javascript">protected void getReadPermission(int requestCode)
 
في هذه الحالة، يكفي استدعاء <code>requestPermission</code>:  
<syntaxhighlight lang="javascript">protected void getReadPermission(int requestCode)
{
{
     cordova.requestPermission(this, requestCode, READ);
     cordova.requestPermission(this, requestCode, READ);
}‎</syntaxhighlight>  
}‎</syntaxhighlight>سيؤدي هذا إلى استدعاء [[Cordova/Activity|النشاط]] (activity) وظهور مِحَثّ (prompt) يطلب الإذن. وبمجرد حصول المستخدم على الإذن، يجب معالجة النتيجة باستخدام التابع <code>onRequestPermissionResult</code>، والذي يجب إعادة تعريفه في كل إضافة.


سيؤدي هذا إلى استدعاء [[Cordova/Activity|النشاط]] (activity) وظهور مِحَثّ (prompt) يطلب الإذن. وبمجرد حصول المستخدم على الإذن، يجب معالجة النتيجة باستخدام التابع <code>onRequestPermissionResult</code>، الذي يجب إعادة تعريفه في كل إضافة.  يمكن العثور على مثال على في الأسفل:  
إليك المثال التالي:<syntaxhighlight lang="javascript">public void onRequestPermissionResult(int requestCode, String[] permissions,
<syntaxhighlight lang="javascript">public void onRequestPermissionResult(int requestCode, String[] permissions,
                                         int[] grantResults) throws JSONException
                                         int[] grantResults) throws JSONException
{
{
سطر 169: سطر 158:
             break;
             break;
     }
     }
}‎</syntaxhighlight>  
}‎</syntaxhighlight>ستعود التعليمة<code>switch</code> أعلاه من المحث، وبناءًا على قيمة الوسيط المعطى <code>requestCode</code>، فقد تستدعي التابع المقابل. تجدر الإشارة إلى أن محثات (prompts) الأذونات قد تتعطل (stack) في حالة عدم معالجة عملية التنفيذ بشكل صحيح، لهذا من المهم تجنب ذلك.
 
ستعود التعليمة <code>switch</code> أعلاه من المحث، وبناءًا على قيمة الوسيط المعطى <code>requestCode</code>، فقد تستدعي التابع المقابل. تجدر الإشارة إلى أن محثات (prompts) الأذونات قد تتعطل (stack) في حالة عدم معالجة عملية التنفيذ بشكل صحيح، لهذا من المهم تجنب ذلك.  


بالإضافة إلى طلب الحصول على إذن واحد، من الممكن أيضًا طلب الأذونات لمجموعة بأكملها عن طريق تعريف مصفوفة الأذونات، كما هو الحال في الإضافة Geolocation:  
بالإضافة إلى طلب الحصول على إذن واحد، من الممكن أيضًا طلب الأذونات لمجموعة بأكملها عن طريق تعريف مصفوفة الأذونات، كما هو الحال في الإضافة Geolocation:<syntaxhighlight lang="javascript">String [] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };‎</syntaxhighlight>يعد ذلك، كل ما عليك القيام به لطلب الإذن، هو ما يلي:<syntaxhighlight lang="javascript">cordova.requestPermissions(this, 0, permissions);‎</syntaxhighlight>يعيد التعبير أعلاه الأذونات المحددة في المصفوفة. من المستحسن تمرير مصفوفة أذونات متاحة للعموم، نظرًا لأنه يمكن استخدامها من طرف الإضافات التي تستخدم إضافتك في ارتباطاتها، على الرغم من أن هذا ليس ضروريا.
<syntaxhighlight lang="javascript">String [] permissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION };‎</syntaxhighlight>  
==تصحيح إضافات أندرويد (Debugging Android Plugins)==
يمكن تصحيح أخطاء أندرويد باستخدام Eclipse أو Android Studio، لكن يُوصى باستخدام Android Studio.


يعد ذلك، كل ما عليك القيام به لطلب الإذن، هو ما يلي:
لمّا كانت منصة Cordova-Android تُستخدم حاليًا كمكتبة مشروع، وكانت الإضافات مدعومة كشيفرة مصدرية، فمن الممكن تصحيح شيفرة [[Java|جافا]] داخل تطبيقات كوردوفا تمامًا مثل تطبيقات أندرويد الأصلية.
<syntaxhighlight lang="javascript">cordova.requestPermissions(this, 0, permissions);‎</syntaxhighlight>  
==إطلاق أنشطة أخرى==
هناك بعض الإجراءات الخاصة التي يجب اتخاذها إن كنت تريد من الإضافة أن تطلق [[Cordova/Activity|نشاطًا]] (Activity) يدفع (pushes) [[Cordova/Activity|نشاط]] كوردوفا (Cordova Activity) إلى الخلفية. سينهي أندرويد الأنشطة الموجودة في الخلفية عندما تنحسر الذاكرة المتاحة في الجهاز. في هذه الحالة، ستُنهى أيضًا نسخة (instance‏) <code>CordovaPlugin</code>.


يعيد التعبير أعلاه الأذونات المحددة في المصفوفة. من المستحسن تمرير مصفوفة أذونات متاحة للعموم، نظرًا لأنه يمكن استخدامها من طرف الإضافات التي تستخدم إضافتك في ارتباطاتها، على الرغم من أن هذا ليس ضروريا.  
إن كانت الإضافة تنتظر نتيجة من [[Cordova/Activity|النشاط]] الذي أطلَقته، فسيتم إنشاء نسخةٍ جديدةٍ من الإضافة عند إعادة [[Cordova/Activity|نشاط]] كوردوفا إلى الواجهة، وعند الحصول على النتيجة. لكن لن تُحفظ حالة الإضافة أو تستعاد تلقائيًا، وسيُفقد السياق <code>CallbackContext</code> الخاص بالإضافة.


== تصحيح إضافات أندرويد (Debugging Android Plugins) ==
هناك تابعان يمكن أن تعرفها <code>CordovaPlugin</code> للتعامل مع هذه المسألة:<syntaxhighlight>/**
 
يمكن تصحيح أخطاء أندرويد باستخدام Eclipse أو Android Studio، لكن يُوصى باستخدام Android Studio.  لما كانت Cordova-Android تُستخدم حاليًا كمكتبة مشروع، وكانت الإضافات مدعومة كشيفرة مصدرية، فمن الممكن تصحيح شيفرة [[Java|جافا]] داخل تطبيقات Cordova تمامًا مثل تطبيقات أندرويد الأصلية.
 
== إطلاق أنشطة أخرى ==
 
هناك بعض الإجراءات الخاصة التي يجب اتخاذها إن كنت تريد من الإضافة أن تطلق [[Cordova/Activity|نشاطًا]]ًا (Activity) يدفع [[Cordova/Activity|نشاط]] كوردوفا (Cordova Activity) إلى الخلفية. سينهي نظام التشغيل أندرويد الأنشطة الموجودة في الخلفية عندما تنحسر الذاكرة المتاحة في الجهاز. في هذه الحالة، ستُنهى أيضًا نسخة (instance‏) <code>CordovaPlugin</code>. 
 
إن كانت الإضافة تنتظر نتيجة من [[Cordova/Activity|النشاط]] الذي أطلقته، فسيتم إنشاء نسخة جديدة من الإضافة عند إعادة [[Cordova/Activity|نشاط]] كوردوفا إلى الواجهة وعند الحصول على النتيجة. لكن لن تُحفظ حالة الإضافة أو تستعاد تلقائيًا، وسيُفقد السياق <code>CallbackContext</code> الخاص بالإضافة. 
 
هناك تابعان يمكن أن تعرفها <code>CordovaPlugin</code> للتعامل مع هذه المسألة:  
<syntaxhighlight>/**
  * تُستدعى عند إنهاء النشاط، مثلا إن استدعت الإضافة نشاطا خارجيا وقام نظام التشغيل  
  * تُستدعى عند إنهاء النشاط، مثلا إن استدعت الإضافة نشاطا خارجيا وقام نظام التشغيل  
  * الموجود في الخلفية CordovaActivity بإنهاء
  * الموجود في الخلفية CordovaActivity بإنهاء
سطر 207: سطر 185:


  *
  *
  * CordovaActivity تُستدعى عندما تكون الإضافة ستستلِم نتيجة نشاط ما بعد إنهاء  
  * CordovaActivity تُستدعى إذا كانت الإضافة ستستلِم نتيجة نشاط ما بعد إنهاء  
  * onSaveInstanceState() الحزمة ستكون مماثلة لتلك المعادة من الإضافة في  
  * onSaveInstanceState() الحزمة ستكون مماثلة لتلك المعادة من الإضافة في  
  *  
  *  
سطر 213: سطر 191:
  * @param callbackContext  السياق البديل الذي ستُعاد نتيجة الإضافة إليه
  * @param callbackContext  السياق البديل الذي ستُعاد نتيجة الإضافة إليه
  */
  */
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}‎</syntaxhighlight>  
public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}‎</syntaxhighlight>لاحظ أنه لا ينبغي استخدام التابعين المذكورين أعلاه إلا إن كانت الإضافة ستطلق [[Cordova/Activity|نشاطاً]] ([http://developer.android.com/reference/android/app/Activity.html Activity]) تتوخى نتيجة منه، ويجب ألأ تستعيد إلا الحالة اللازمة لمعالجة نتيجة [[Cordova/Activity|النشاط]]. لن تستعاد (restore) حالة الإضافة إلا في حال الحصول من [[Cordova/Activity|النشاط]] على نتيجة كانت الإضافة قد طلبتها (requested‎) باستخدام التابع <code>CordovaInterface</code> الخاص بالكائن <code>startActivityForResult()</code> ‎وكان نظام التشغيل قد سبق وأنهى [[Cordova/Activity|نشاط]] كوردوفا أثناء وجوده في الخلفية.
 
لاحظ أنه لا ينبغي استخدام التابعين المذكورين أعلاه إلا إن كانت الإضافة ستطلق [[Cordova/Activity|نشاطاً]] ([http://developer.android.com/reference/android/app/Activity.html Activity]) تتوخى نتيجة منه، ويجب ألأ تستعيد إلا الحالة اللازمة لمعالجة نتيجة [[Cordova/Activity|النشاط]]. لن تستعاد (restore) حالة الإضافة إلا في حال الحصول من [[Cordova/Activity|النشاط]] على نتيجة كانت الإضافة قد طلبتها (requested‎) باستخدام التابع <code>CordovaInterface</code> الخاص بالكائن <code>startActivityForResult()</code> ‎وكان نظام التشغيل قد أنهى [[Cordova/Activity|نشاط]] كوردوفا أثناء وجوده في الخلفية.  


كجزء من <code>onRestoreStateForActivityResult()‎</code>، سيُمرّر إلى الإضافة سياق بديل <code>CallbackContext</code>. ومن المهم أن تدرك أن السياق <code>CallbackContext</code> ليس هو نفسه السياق الذي تم إنهاؤه مع [[Cordova/Activity|النشاط]]. إذ يُفقد الاستدعاء الأصلي (original callback)، ولن يُنفّذ في تطبيق [[JavaScript|JavaScript]]. بدلاً من ذلك، سيُعيد السياق <code>CallbackContext</code> النتيجة كجزء من الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> الذي يُفعّل عند استئناف تشغيل التطبيق.
كجزء من التابع <code>onRestoreStateForActivityResult()‎</code>، سيتم تمرير  سياق بديل <code>CallbackContext</code> إلى الإضافة. من المهم أن تدرك أن السياق <code>CallbackContext</code> ليس هو نفسه السياق الذي تم إنهاؤه مع [[Cordova/Activity|النشاط]]. إذ يُفقد الاستدعاء الأصلي (original callback)، كما لن يُنفّذ في تطبيق [[JavaScript|جافاسكريبت]]. بدلاً من ذلك، سيُعيد السياق <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> يتبغ البنية التالية:  
الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> يتبغ الصيغة التالية:<syntaxhighlight lang="javascript">{
<syntaxhighlight lang="javascript">{
     action: "resume",
     action: "resume",
     pendingResult: {
     pendingResult: {
سطر 227: سطر 202:
         result: any
         result: any
     }
     }
}‎</syntaxhighlight>  
}‎</syntaxhighlight>
* ستطابق <code>pluginServiceName</code> [https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#name اسم العنصر] في ملف <code>plugin.xml</code>.  
*ستطابق <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>[https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/PluginResult.java PluginResult.java]</code> للتعرف على قيم السلسلة النصية الموافقة لحالات الإضافات  
*<code>pluginStatus</code> ستكون سلسلة نصية تصف حالة ناتج الإضافة (<code>PluginResult</code>) المٌمرر إلى السياق <code>CallbackContext</code>. راجع صفحة <code>[https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/PluginResult.java PluginResult.java]</code> للتعرف على قيم السلسلة النصية الموافقة لحالات الإضافات
* <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> إلى كل عمليات الاستدعاء (callbacks) التي سجلتها تطبيقات [[JavaScript|جافاسكريبت]] مع الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>. هذا يعني أن النتيجة ستذهب مباشرة إلى تطبيق Cordova؛ ولن يكون للإضافة فرصةٌ لمعالجة النتيجة بواسطة [[JavaScript|جافاسكريبت]] قبل أن يتسلمها التطبيق. لذلك حاول جعل النتيجة المعادة من الشيفرة الأصلية كاملة قدر الإمكان، وألا تعتمد على دوال [[JavaScript|جافاسكريبت]] عند إطلاق [[Cordova/Activity|الأنشطة]].
سيتم تمرير محتوى<code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> إلى كل عمليات الاستدعاء (callbacks) التي سجلتها تطبيقات [[JavaScript|JavaScript]] مع الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>. هذا يعني أن النتيجة ستذهب مباشرة إلى تطبيق Cordova؛ ولن يكون للإضافة فرصة لمعالجة النتيجة ب[[JavaScript|جافاسكريبت]] قبل أن يتسلمها التطبيق. لذلك حاول جعل النتيجة المعادة من الشيفرة الأصلية كاملة قدر الإمكان، وألا تعتمد على دوال [[JavaScript|جافاسكريبت]] عند إطلاق [[Cordova/Activity|الأنشطة]].
 
تأكد من توضيح كيفية تفسير تطبيق Cordova للنتيجة التي يتلقاها عند وقوع الحدث<code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>. يُناط بتطبيقات Cordova حفظ حالاتها، وتذكر الطلبيات التي أرسلتها، والوسائط التي مررتها إذا لزم الأمر. أيضًا يجب عليك توضيح معاني قيم <code>pluginStatus</code> ونوع البيانات التي ستعاد في الحقل <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> كجزء من الواجهة البرمجية للإضافة.  


هذه سلسلة الأحداث الكاملة لبدء [[Cordova/Activity|نشاط]] معين:
تأكد من توضيح كيفية تفسير تطبيق كوردوفا للنتيجة التي يتلقاها عند وقوع الحدث<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> كجزء من الواجهة البرمجية للإضافة.
* يستدعي تطبيق Cordova الإضافة خاصتك
* تطلق الإضافة [[Cordova/Activity|نشاطًا]] تتوخى منه الحصول على نتيجة
* ينهي نظام التشغيل أندرويد كلًا من [[Cordova/Activity|النشاط]] ونسخة (instance) الإضافة
** يُستدعى التابع<code>onSaveInstanceState()‎</code>
* يتفاعل المستخدم مع [[Cordova/Activity|نشاط]]ك، ثم ينتهي [[Cordova/Activity|النشاط]]
* يعاد إنشاء [[Cordova/Activity|نشاط]] Cordova، ثم تُتلقى نتيجة [[Cordova/Activity|النشاط]]
** يُستدعى التابع<code>onRestoreStateForActivityResult()‎</code>  
* يٌستدعى <code>onActivityResult()‎</code> ثم تمرِّر الإضافة النتيجة إلى السياق <code>CallbackContext</code> الجديد
* يُفعّل الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code> ثم يُتلقى من قبل تطبيق Cordova


يوفر أندرويد إعدادات للمطوِّرين لتصحيح أخطاء إنهاء [[Cordova/Activity|الأنشطة]] عند حدوث انحسار في مساحة الذاكرة. قم بتمكين الإعداد "<code>Don't keep activities</code>" في قائمة خيارات المطور على جهازك أو المحاكي لمحاكاة سيناريوهات انحسار الذاكرة. إن كانت الإضافة تُطلق [[Cordova/Activity|نشاطات]] خارجية، فيجب عليك إجراء بعض الاختبارات مع تمكين هذا الإعداد لضمان التعامل بشكل صحيح مع سيناريوهات انحسار الذاكرة.
هذه سلسلة الأحداث الكاملة لبدء [[Cordova/Activity|نشاط]] معين:
*يستدعي تطبيق كوردوفا الإضافة خاصتك
*تطلق الإضافة [[Cordova/Activity|نشاطًا]] تتوخى منه الحصول على نتيجة
*ينهي نظام التشغيل أندرويد كلًا من [[Cordova/Activity|النشاط]] ونسخة الإضافة
**يُستدعى التابع<code>onSaveInstanceState()‎</code>
*يتفاعل المستخدم مع [[Cordova/Activity|نشاط]]ك، ثم ينتهي [[Cordova/Activity|النشاط]]
*يعاد إنشاء [[Cordova/Activity|نشاط]] كوردوفا، ثم تُتلقى نتيجة [[Cordova/Activity|النشاط]]
**يُستدعى التابع<code>onRestoreStateForActivityResult()‎</code>
*يٌستدعى <code>onActivityResult()‎</code> ثم تمرِّر الإضافةُ النتيجةَ إلى السياق <code>CallbackContext</code> الجديد
*يُفعّل الحدث <code>[https://cordova.apache.org/docs/en/latest/cordova/events/events.html#resume resume]</code>، ثم يُتلقى من قبل تطبيق كوردوفا
يوفر أندرويد إعدادات للمطوِّرين لتصحيح أخطاء إنهاء [[Cordova/Activity|الأنشطة]] عند حدوث انحسار في مساحة الذاكرة. قم بتمكين الإعداد "<code>Don't keep activities</code>" في قائمة خيارات المطور على جهازك أو المحاكي لمحاكاة سيناريوهات انحسار الذاكرة. إن كانت الإضافة تُطلق [[Cordova/Activity|نشاطات]] خارجية، فيجب عليك تنفيذ بعض الاختبارات مع تمكين هذا الإعداد لضمان التعامل بشكل صحيح مع سيناريوهات انحسار الذاكرة.
==مصادر==
==مصادر==
*[https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html قسم إضافات أندرويد في التوثيق الرسمي]
*[https://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html صفحة Android Plugin Development Guide في توثيق كوردوفا الرسمي]

مراجعة 16:20، 22 نوفمبر 2018

يوضح هذا القسم كيفية تقديم (implement) شيفرات الإضافات الأصلية (native plugin code) على منصة أندرويد.

قبل قراءة هذه الصفحة، راجع صفحة دليل تطوير الإضافات لتكوين نظرة عامة على بنية الإضافات وواجهات جافاسكريبت الخاصة بها. يواصل هذا القسم تطوير مثال الإضافة echo الوارد في دليل تطوير الإضافات، والذي يربط الاتصال من المعرض webview إلى المنصة الأصلية (native platform)، والعكس بالعكس. للحصول على مثال آخر، راجع التعليقات في صفحة CordovaPlugin.java.

تستند إضافات أندرويد على منصة Cordova-Android، التي تُنشؤُ من المعرض Android WebView إضافة إلى جسر أصلي (native bridge). يتألف الجزء الأصلي من إضافات أندرويد من صنف جافا واحد على الأقل، والذي يوسع الصنف CordovaPlugin ويعيد تعريف تابعه execute.

إعداد صنف الإضافة (Plugin Class Mapping)

تستخدم واجهة جافاسكريبت الخاصة بالإضافة التابع cordova.exec على النحو التالي:

exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);

هذه الشيفرة ترسل طلبية (request) من webview إلى الجانب الأصلي (native side) لأندرويد، وتستدعي التابع action فعليا على الصنف service، مع وسائط إضافية تُمرر في المصفوفة args. سواء أقمت بتوزيع الإضافة على هيئة ملف جافا، أو كملف مضغوط jar، يجب تحديد الإضافة في ملف res/xml/config.xml الخاص بتطبيق Cordova-Android. انظر صفحة الإضافات لمزيد من المعلومات حول كيفية استخدام الملف plugin.xml لإدارج العنصر feature:

<feature name="<service_name>">
    <param name="android-package" value="<full_name_including_namespace>" />
</feature>

يتطابق service_name مع الاسم المستخدم عند استدعاء دالة الجافا exec . قيمته تساوي الاسم الكامل المؤهل لصنف جافا (java class's fully qualified namespace identifier). بخلاف ذلك، يمكن أن تُصرّف (compiled) الإضافة، لكنها لن تكون متاحة لكوردوفا.

تهيئة الإضافات ودورة الحياة (Plugin Initialization and Lifetime)

يتم إنشاء نسخة (instance) واحدة من كائن الإضافة خلال دورة حياة المعرض WebView. ولكن بعد الإشارة إليها عبر استدعاءٍ من جافاسكريبت، إلا في حال إضافة الوسم <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);
    // your init code here
}

يمكن للإضافات الوصول إلى أحداث دورة حياة (lifecycle events) أندرويد، ويمكنها التعامل معها من خلال توسيع أحد التوابع (onResume و onDestroy...). الإضافات ذات الطلبيات الطويلة (long-running requests)، أو النشاطات الخلفية (background activity)، مثل قارئات الوسائط، أو المستمعات (listeners)، أو النشاطات ذات الحالة الداخلية (internal state) يجب أن تقدِّم (implement) التابع onReset()‎. والذي يُنفّذ عند انتقال المعرض(WebView) إلى صفحة جديدة، أو عند تحديث الصفحة، ما يؤدي إلى إعادة تحميل جافاسكريبت.

كتابة إضافة لأندرويد بلغة جافا

يرسل استدعاء من جافاسكريبت طلبية إضافة (plugin request) إلى الجانب الأصلي (native side)، ويُعيّن إضافة جافا المقابلة في الملف config.xml، وأي شيء يتم إرساله إلى الإضافة عبر دالة الجافاسكريبت exec سيُمرّر إلى تابع 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;  // Returning false results in a "MethodNotFound" error.
}

يتوافق الوسيط action الخاص بدالة جافاسكريبت exec مع تابع صنف خاص (private class method)، والذي يُرسل مع الوسائط الاختيارية.

عند إمساك الاستثناءات والأخطاء، من المهم أن تطابق الأخطاء المُعادة إلى جافاسكريبت أسماء الاستثناءات في لغة جافا قدر الممكن.

المهام الفرعية (Threading)

لا تعمل إضافات جافاسكريبت في المهمة الرئيسية (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 المقدمة في صفحة الإضافات، استخدم الملف 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) في جافاسكريبت كوسيط.

تكامل أندرويد (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) إلى الخلفية. سينهي أندرويد الأنشطة الموجودة في الخلفية عندما تنحسر الذاكرة المتاحة في الجهاز. في هذه الحالة، ستُنهى أيضًا نسخة (instance‏) 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)، كما لن يُنفّذ في تطبيق جافاسكريبت. بدلاً من ذلك، سيُعيد السياق CallbackContext النتيجة كجزء من الحدث resume الذي يُفعّل عند استئناف تشغيل التطبيق.

الحدث resume يتبغ الصيغة التالية:

{
    action: "resume",
    pendingResult: {
        pluginServiceName: string,
        pluginStatus: string,
        result: any
    }
}
  • ستطابق pluginServiceName اسم العنصر في الملف plugin.xml.
  • pluginStatus ستكون سلسلة نصية تصف حالة ناتج الإضافة (PluginResult) المٌمرر إلى السياق CallbackContext. راجع صفحة PluginResult.java للتعرف على قيم السلسلة النصية الموافقة لحالات الإضافات
  • result ستساوي النتيجة التي مررتها الإضافة إلى CallbackContext (سلسلة نصية، عدد، كائن JSON، إلخ.)

سيتم تمرير محتوى resume إلى كل عمليات الاستدعاء (callbacks) التي سجلتها تطبيقات جافاسكريبت مع الحدث resume. هذا يعني أن النتيجة ستذهب مباشرة إلى تطبيق Cordova؛ ولن يكون للإضافة فرصةٌ لمعالجة النتيجة بواسطة جافاسكريبت قبل أن يتسلمها التطبيق. لذلك حاول جعل النتيجة المعادة من الشيفرة الأصلية كاملة قدر الإمكان، وألا تعتمد على دوال جافاسكريبت عند إطلاق الأنشطة.

تأكد من توضيح كيفية تفسير تطبيق كوردوفا للنتيجة التي يتلقاها عند وقوع الحدثresume. إذ يُناط بتطبيقات كوردوفا حفظ حالاتها، وتذكر الطلبيات التي أرسلتها، والوسائط التي مررتها إذا لزم الأمر. أيضًا يجب عليك توضيح معاني قيم pluginStatus ونوع البيانات التي ستعاد في الحقل resume كجزء من الواجهة البرمجية للإضافة.

هذه سلسلة الأحداث الكاملة لبدء نشاط معين:

  • يستدعي تطبيق كوردوفا الإضافة خاصتك
  • تطلق الإضافة نشاطًا تتوخى منه الحصول على نتيجة
  • ينهي نظام التشغيل أندرويد كلًا من النشاط ونسخة الإضافة
    • يُستدعى التابعonSaveInstanceState()‎
  • يتفاعل المستخدم مع نشاطك، ثم ينتهي النشاط
  • يعاد إنشاء نشاط كوردوفا، ثم تُتلقى نتيجة النشاط
    • يُستدعى التابعonRestoreStateForActivityResult()‎
  • يٌستدعى onActivityResult()‎ ثم تمرِّر الإضافةُ النتيجةَ إلى السياق CallbackContext الجديد
  • يُفعّل الحدث resume، ثم يُتلقى من قبل تطبيق كوردوفا

يوفر أندرويد إعدادات للمطوِّرين لتصحيح أخطاء إنهاء الأنشطة عند حدوث انحسار في مساحة الذاكرة. قم بتمكين الإعداد "Don't keep activities" في قائمة خيارات المطور على جهازك أو المحاكي لمحاكاة سيناريوهات انحسار الذاكرة. إن كانت الإضافة تُطلق نشاطات خارجية، فيجب عليك تنفيذ بعض الاختبارات مع تمكين هذا الإعداد لضمان التعامل بشكل صحيح مع سيناريوهات انحسار الذاكرة.

مصادر