مقدمة إلى أندرويد NDK. الأنواع البدائية JNI

06.04.2019

لتطوير التطبيقات لنظام التشغيل Android، توفر Google حزمتي تطوير: SDK وNDK. هناك العديد من المقالات والكتب وكذلك الإرشادات الجيدة من Google حول SDK. ولكن حتى جوجل نفسها لا تكتب سوى القليل عن NDK. ومن بين الكتب الجديرة بالاهتمام، أود أن أشير إلى كتاب واحد فقط، وهو Cinar O. - Pro Android C++ with the NDK – 2012.

تستهدف هذه المقالة أولئك الذين ليسوا على دراية بعد (أو على دراية قليلة) بنظام Android NDK ويرغبون في تعزيز معرفتهم. سأهتم بـ JNI، لأنه يبدو لي أننا بحاجة إلى البدء بهذه الواجهة. أيضًا، في النهاية، دعونا نلقي نظرة على مثال صغير به وظيفتين لكتابة ملف وقراءته.

ما هو أندرويد NDK؟

أندرويد إن دي كيه(مجموعة التطوير الأصلية) هي مجموعة من الأدوات التي تسمح لك بتنفيذ جزء من التطبيق الخاص بك باستخدام لغات مثل C/C++.

ما هو استخدام NDK؟

توصي Google باستخدام NDK فقط في حالات نادرة جدًا. في كثير من الأحيان هذه هي الحالات التالية:
  • تحتاج إلى زيادة الإنتاجية (على سبيل المثال، فرز كمية كبيرة من البيانات)؛
  • استخدم مكتبة طرف ثالث. على سبيل المثال، تمت كتابة الكثير من الأشياء بالفعل بلغات C/C++ وتحتاج فقط إلى استخدام المواد الموجودة. مثال على المكتبات مثل Ffmpeg، OpenCV؛
  • برمجة منخفضة المستوى (على سبيل المثال، كل ما يتجاوز Dalvik)؛

ما هو JNI؟

واجهة جافا الأصلية- آلية قياسية لتشغيل التعليمات البرمجية تحت سيطرة جهاز Java الظاهري، والتي تتم كتابتها بلغة C/C++ أو لغات المجمع، ويتم ربطها في شكل مكتبات ديناميكية، مما يلغي الحاجة إلى الارتباط الثابت. وهذا يجعل من الممكن استدعاء دالة C/C++ من برنامج Java، والعكس صحيح.

فوائد JNI

الميزة الرئيسية على نظائرها (Netscape Java Runtime Interface أو واجهة Microsoft Raw الأصلية وواجهة COM/Java) هي أن JNI تم تصميمها في الأصل لتوفير التوافق الثنائي، لتوافق التطبيقات المكتوبة بلغة JNI لأي أجهزة Java افتراضية على منصة معينة (عندما أتحدث عن JNI، فأنا لست مرتبطًا بآلة Dalvik، لأن JNI تمت كتابته بواسطة Oracle لـ JVM وهو مناسب لجميع أجهزة Java الافتراضية). ولذلك، سيتم تنفيذ التعليمات البرمجية المترجمة في C/C++ بغض النظر عن النظام الأساسي. الإصدارات السابقة لم تكن تسمح بالتوافق الثنائي.

التوافق الثنائي أو التوافق الثنائي هو نوع من توافق البرامج الذي يسمح للبرنامج بالعمل في بيئات مختلفة دون تغيير ملفاته القابلة للتنفيذ.

كيف تعمل جي إن آي

جدول JNI، منظم مثل جدول الوظائف الافتراضية في C++. يمكن أن يعمل VM مع العديد من هذه الجداول. على سبيل المثال، سيكون أحدهما لتصحيح الأخطاء، والثاني للاستخدام. المؤشر إلى واجهة JNI صالح فقط في مؤشر الترابط الحالي. هذا يعني أن المؤشر لا يمكنه الانتقال من مؤشر ترابط إلى آخر. ولكن يمكن استدعاء الأساليب الأصلية من مواضيع مختلفة. مثال:

Jdouble Java_pkg_Cls_f__ILjava_lang_String_2 (JNIEnv *env, jobject obj, jint i, jstring s) ( const char *str = (*env)->GetStringUTFChars(env, s, 0); (*env)->ReleaseStringUTFChars(env, s, str ); عودة 10 )

  • *بيئة- مؤشر للواجهة؛
  • obj- رابط للكائن الذي تم وصف الطريقة الأصلية فيه؛
  • أنا و ق- الحجج التي تم تمريرها؛
يتم نسخ الأنواع الأولية بين الجهاز الافتراضي والتعليمة البرمجية الأصلية، ويتم تمرير الكائنات حسب المرجع. مطلوب VM لتتبع كافة الروابط التي تم تمريرها إلى التعليمات البرمجية الأصلية. لا يمكن تحرير كافة المراجع التي تم تمريرها إلى التعليمات البرمجية الأصلية بواسطة GC. لكن الكود الأصلي، بدوره، يجب أن يُعلم الجهاز الظاهري بأنه لم يعد بحاجة إلى مراجع للكائنات التي تم تمريرها.

روابط محلية وعالمية

تقسم JNI المراجع إلى ثلاثة أنواع: المراجع المحلية والعالمية والضعيفة. السكان المحليون صالحون حتى اكتمال الطريقة. كافة كائنات Java التي ترجعها وظائف JNI هي كائنات محلية. يجب أن يعتمد المبرمج على الجهاز الظاهري نفسه لتنظيف كافة المراجع المحلية. الروابط المحلية متاحة فقط في الموضوع الذي تم إنشاؤها فيه. ومع ذلك، إذا كانت هناك حاجة، فيمكن تحريرها على الفور باستخدام طريقة حذف LocalRef لواجهة JNI:

جي كلاس كلاز؛ clazz = (*env)->FindClass(env, "java/lang/String"); // الكود الخاص بك (*env)->DeleteLocalRef(env, clazz);
تظل المراجع العالمية حتى يتم إصدارها بشكل صريح. لتسجيل مرجع عمومي، قم باستدعاء الأسلوب NewGlobalRef. إذا لم تعد هناك حاجة إلى المرجع العام، فيمكن حذفه باستخدام طريقة RemoveGlobalRef:

Jclass localClazz; jclass globalClazz; localClazz = (*env)->FindClass(env, "java/lang/String"); globalClazz = (*env)->NewGlobalRef(env, localClazz); // الكود الخاص بك (*env)->DeleteLocalRef(env, localClazz);

معالجة الأخطاء

لا تقوم JNI بالتحقق من وجود أخطاء مثل NullPointerException وIllegalArgumentException. الأسباب:
  • انخفاض الإنتاجية.
  • في معظم وظائف مكتبة C، يكون من الصعب جدًا الحماية من الأخطاء.
تسمح لك JNI باستخدام استثناء Java. تُرجع معظم وظائف JNI رمز خطأ وليس الاستثناء نفسه، وبالتالي يتعين عليك معالجة الكود نفسه، وفي Java تقوم بالفعل بطرح استثناء. في JNI، يجب عليك التحقق من رمز الخطأ للوظائف المستدعىة ثم استدعاء ExceptionOccurred()، والذي بدوره يُرجع كائن خطأ:

Jthrowable ExceptionOccurred(JNIEnv *env);
على سبيل المثال، بعض وظائف الوصول إلى صفيف JNI لا تُرجع أخطاء، ولكنها قد تؤدي إلى استثناءات ArrayIndexOutOfBoundsException أو ArrayStoreException.

الأنواع البدائية JNI

لدى JNI أنواع البيانات البدائية والمرجعية الخاصة بها.
نوع جافا النوع الأصلي وصف
منطقية jboolean غير موقعة 8 بت
بايت jbyte وقعت 8 بت
شار jchar غير موقعة 16 بت
قصير com.jshort وقعت 16 بت
كثافة العمليات جينت وقعت 32 بت
طويل jlong وقعت 64 بت
يطفو jfloat 32 بت
مزدوج jdouble 64 بت
فارغ فارغ لا يوجد

أنواع مراجع JNI

تعديل UTF-8

تستخدم JNI ترميز UTF-8 المعدل لتمثيل السلاسل. جافا بدورها تستخدم UTF-16. يتم استخدام UTF-8 في الغالب في لغة C لأنه يقوم بتشفير \u0000 إلى 0xc0 بدلاً من 0x00 المعتاد. يتم ترميز السلاسل المعدلة بحيث يمكن تمثيل سلسلة من الأحرف التي تحتوي على أحرف ASCII غير صفرية فقط باستخدام بايت واحد فقط.

وظائف JNI

لا تحتوي واجهة JNI على مجموعة البيانات الخاصة بها فحسب، بل تحتوي أيضًا على وظائفها الخاصة. سوف يستغرق الأمر الكثير من الوقت لمراجعتها، حيث يوجد أكثر من اثنتي عشرة منها. يمكنك التعرف عليهم في الوثائق الرسمية.

مثال على استخدام وظائف JNI

مثال صغير لمساعدتك على فهم المادة المغطاة:
#يشمل //... JavaVM *jvm; JNIEnv *env; JavaVMINitArgs vm_args; JavaVMOption* options = new JavaVMOption; options.optionString = "-Djava.class.path=/usr/lib/Java"; vm_args.version = JNI_VERSION_1_6; vm_args.nOptions = 1; vm_args.options = options; vm_args.ignoreUnrecognized = false; JNI_CreateJavaVM(&jvm, &env, &vm_args); خيارات الحذف؛ jclass cls = env->FindClass("Main"); jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V"); env->CallStaticVoidMethod(cls, mid, 100); jvm->DestroyJavaVM();
دعونا ننظر إليها سطراً سطراً:
  • JavaVM- يوفر واجهة لاستدعاء الوظائف التي تسمح لك بإنشاء JavaVM وتدميره؛
  • JNIEnv- يوفر معظم وظائف JNI؛
  • JavaVMINitArgs- وسيطات JavaVM؛
  • JavaVMOption- خيارات JavaVM؛
تقوم طريقة JNI_CreateJavaVM () بتهيئة JavaVM وإرجاع مؤشر إليها. تقوم طريقة JNI_DestroyJavaVM() بإلغاء تحميل JavaVM الذي تم إنشاؤه.

تيارات

تتم إدارة جميع سلاسل الرسائل في Linux بواسطة kernel، ولكن يمكن ربطها بـ JavaVM باستخدام وظائف AttachCurrentThread و AttachCurrentThreadAsDaemon. حتى يتم إرفاق مؤشر الترابط، لا يمكنه الوصول إلى JNIEnv. هام، لا يقوم Android بتعليق سلاسل المحادثات التي تم إنشاؤها بواسطة JNI، حتى إذا تم تشغيل GC. ولكن قبل أن ينتهي مؤشر الترابط، يجب عليه استدعاء أسلوب DetachCurrentThread للفصل عن JavaVM.

الخطوات الأولى

يجب أن يبدو هيكل مشروعك كما يلي:

كما نرى من الشكل 3، كل التعليمات البرمجية الأصلية موجودة في مجلد jni. بعد بناء المشروع، سيتم إنشاء أربعة مجلدات في مجلد libs لكل بنية معالج، حيث سيتم وضع مكتبتك الأصلية (يعتمد عدد المجلدات على عدد البنيات المحددة).

لإنشاء مشروع أصلي، تحتاج إلى إنشاء مشروع Android عادي واتباع الخطوات التالية:

  • في جذر المشروع، تحتاج إلى إنشاء مجلد jni لوضع مصادر الكود الأصلي فيه؛
  • قم بإنشاء ملف Android.mk الذي سيبني المشروع؛
  • قم بإنشاء ملف Application.mk الذي يصف تفاصيل التجميع. إنه ليس شرطًا أساسيًا، ولكنه يسمح لك بتخصيص التجميع بمرونة؛
  • قم بإنشاء ملف ndk-build الذي سيبدأ عملية الإنشاء (اختياري أيضًا).

android.mk

كما ذكرنا سابقًا، هذا ملف تعريفي لبناء مشروع أصلي. يتيح لك Android.mk تجميع التعليمات البرمجية الخاصة بك في وحدات. يمكن أن تكون الوحدات إما مكتبات ثابتة (مكتبة ثابتة، سيتم نسخها فقط إلى مشروعك، في مجلد libs)، أو مكتبات مشتركة (مكتبة مشتركة)، أو ملف مستقل قابل للتنفيذ.

مثال على الحد الأدنى من التكوين:
LOCAL_PATH:= $(اتصل بـ my-dir) يتضمن $(CLEAR_VARS) LOCAL_MODULE:= NDKBegining LOCAL_SRC_FILES:= ndkBegining.c include $(BUILD_SHARED_LIBRARY)
دعونا ننظر في الأمر بالتفصيل:

  • ل OCAL_PATH:= $(اتصل بـ my-dir)- تقوم وظيفة call my-dir بإرجاع مسار المجلد الذي تم استدعاء الملف فيه؛
  • تضمين $(CLEAR_VARS)- مسح المتغيرات التي تم استخدامها من قبل باستثناء LOCAL_PATH. يعد هذا ضروريًا لأن جميع المتغيرات عامة، لأن البناء يحدث في سياق GNU Make واحد؛
  • LOCAL_MODULE– اسم وحدة الإخراج. في مثالنا، تم تعيين اسم مكتبة الإخراج على NDKBegining، ولكن بعد الإنشاء، سيتم إنشاء مكتبات تسمى libNDKBegining في مجلد libs. يضيف Android البادئة lib إلى الاسم، ولكن في كود Java عند الاتصال، يجب عليك تحديد اسم المكتبة بدون البادئة (أي، يجب أن تتطابق الأسماء مع تلك المثبتة في ملفات التكوين)؛
  • LOCAL_SRC_FILES– سرد الملفات المصدر التي يجب إنشاء التجميع منها؛
  • تضمين $(BUILD_SHARED_LIBRARY)- يشير إلى نوع وحدة الإخراج.
يمكنك تحديد المتغيرات الخاصة بك في Android.mk، لكن لا ينبغي أن تحتوي على الصيغة التالية: LOCAL_، PRIVATE_، NDK_، APP_، my-dir. توصي Google بتسمية المتغيرات الخاصة بك مثل MY_. على سبيل المثال:
MY_SOURCE:= NDKBegining.c للوصول إلى متغير: $(MY_SOURCE) يمكن أيضًا ربط المتغيرات، على سبيل المثال: LOCAL_SRC_FILES += $(MY_SOURCE)

application.mk

يحدد هذا الملف التعريفي العديد من المتغيرات التي ستساعد في جعل البناء أكثر مرونة:
  • APP_OPTIM- متغير إضافي تم ضبطه للإصدار أو التصحيح. يستخدم للتحسين عند تجميع الوحدات. يمكنك تصحيح الأخطاء إما بالإصدار أو التصحيح، لكن التصحيح يوفر المزيد من المعلومات لتصحيح الأخطاء؛
  • APP_BUILD_SCRIPT- يشير إلى مسار بديل لـ Android.mk؛
  • APP_ABI- ربما يكون أحد أهم المتغيرات. إنه يشير إلى بنية المعالج التي يجب تجميع الوحدات النمطية لها. الإعداد الافتراضي هو Armeabi، والذي يتوافق مع بنية ARMv5TE. على سبيل المثال، لدعم ARMv7، يجب عليك استخدام Armeabi-v7a، لـ IA-32 - x86، لـ MIPS - mips، أو إذا كنت بحاجة إلى دعم جميع البنيات، فيجب أن تكون القيمة كما يلي: APP_ABI:= armeabi Armeabi-v7a x86 mips. إذا كنت تستخدم الإصدار 7 من ndk والإصدارات الأحدث، فلا يمكنك إدراج جميع البنيات، ولكن يمكنك تعيين APP_ABI:= all.
  • APP_PLATFORM- هدف المنصة؛
  • APP_STL- يستخدم Android مكتبة وقت التشغيل libstdc++.so، والتي تم تجريدها ولا تتوفر جميع وظائف C++ للمطور. ومع ذلك، يسمح المتغير APP_STL بتضمين دعم الامتداد في البناء؛
  • NDK_TOOLCHAIN_VERSION- يسمح لك بتحديد إصدار برنامج التحويل البرمجي لدول مجلس التعاون الخليجي (الافتراضي 4.6)؛

NDK-BUILDS

Ndk-build عبارة عن غلاف لـ GNU Make. بعد الإصدار 4، تم تقديم علامات ndk-build:
  • ينظف- مسح كافة الملفات الثنائية التي تم إنشاؤها؛
  • NDK_DEBUG=1- يولد رمز التصحيح.
  • NDK_LOG=1- يعرض سجل الرسائل (يستخدم لتصحيح الأخطاء)؛
  • NDK_HOST_32BIT=1- يحتوي Android على أدوات لدعم إصدارات 64 بت من الأدوات المساعدة (على سبيل المثال NDK_PATH\toolchains\mipsel-linux-android-4.8\prebuilt\windows-x86_64، وما إلى ذلك)؛
  • NDK_APPLICATION_MK- يشار إلى المسار إلى Application.mk.
في الإصدار 5 من NDK، تم تقديم علامة: NDK_DEBUG. إذا تم تعيينه على 1، فسيتم إنشاء إصدار تصحيح. إذا لم يتم تعيين العلامة، فسيقوم ndk-build افتراضيًا بالتحقق مما إذا كانت السمة android:debuggable = "true" قد تم تعيينها في AndroidManifest.xml. إذا كنت تستخدم ndk أعلى من الإصدار 8، فلا توصي Google باستخدام السمة android:debuggable في AndroidManifest.xml (لأنه إذا كنت تستخدم "ant debug" أو قمت بإنشاء إصدار تصحيح باستخدام المكون الإضافي ADT، فسيقومون تلقائيًا بإضافة NDK_DEBUG = علم واحد).

افتراضيًا، يتم تثبيت الدعم لإصدار 64 بت من الأدوات المساعدة، ولكن يمكنك إجباره على إنشاء 32 بت فقط عن طريق تعيين العلامة NDK_HOST_32BIT=1. لا تزال Google توصي باستخدام أدوات مساعدة 64 بت لتحسين أداء البرامج الكبيرة.

كيفية تجميع المشروع؟

لقد كان الألم. كان من الضروري تثبيت البرنامج المساعد CDT، وتنزيل مترجم cygwin أو mingw. تحميل أندرويد NDK. قم بتوصيل كل هذا في إعدادات Eclipse. وكم كان من المؤسف أن الأمر لم ينجح. في المرة الأولى التي صادفت فيها Android NDK، استغرق الأمر 3 أيام لإعداده (وتبين أن المشكلة هي أنه في cygwin كان عليّ منح الإذن 777 لمجلد المشروع).

الآن أصبح كل شيء أسهل بكثير مع هذا. اتبع هذا الرابط. قم بتنزيل حزمة Eclipse ADT، التي تحتوي بالفعل على كل ما تحتاجه للتجميع.

استدعاء الأساليب الأصلية من كود Java

من أجل استخدام التعليمات البرمجية الأصلية من Java، تحتاج أولاً إلى تعريف الأساليب الأصلية في فئة Java. على سبيل المثال:
سلسلة أصلية NativeGetStringFromFile(مسار السلسلة) تطرح IOException؛ الأصلي الفراغ الأصليWriteByteArrayToFile (مسار السلسلة، البايت ب) يلقي IOException؛
يجب أن تسبق الطريقة الكلمة المحجوزة "أصلي". بهذه الطريقة يعرف المترجم أن هذه هي نقطة الدخول إلى JNI. نحتاج إلى تنفيذ هذه الطرق في ملف C/C++. توصي Google أيضًا بالبدء في تسمية الطرق بالكلمة NativeX، حيث X هو الاسم الحقيقي للطريقة. ولكن قبل تنفيذ هذه الطرق يدويًا، يجب عليك إنشاء ملف رأس. يمكن القيام بذلك يدويًا، ولكن يمكنك استخدام الأداة المساعدة javah الموجودة في ملف jdk. ولكن دعنا نذهب أبعد من ذلك ولن نستخدمه من خلال وحدة التحكم، ولكننا سنفعل ذلك باستخدام أدوات Eclipse القياسية.

الآن يمكنك إطلاق. سيحتوي دليل bin/classes على ملفات الرأس الخاصة بك.

بعد ذلك، نقوم بنسخ هذه الملفات إلى دليل jni لمشروعنا الأصلي. اتصل بقائمة سياق المشروع وحدد أدوات Android - إضافة المكتبة الأصلية. سيسمح لنا هذا باستخدام وظائف jni.h. بعد ذلك، يمكنك إنشاء ملف cpp (أحيانًا يقوم Eclipse بإنشائه افتراضيًا) وكتابة نصوص الطرق الموضحة بالفعل في ملف الرأس.

لم أقم بإضافة مثال رمزي إلى المقالة حتى لا أطيله. يمكنك عرض/تنزيل مثال من جيثب.

العلامات:

  • برمجة الروبوت
  • android.ndk
  • jni
اضف اشارة

Android NDK (Native Development Kit) هي مجموعة أدوات شائعة جدًا تُستخدم لتطوير تطبيقات الأجهزة المحمولة. تستخدم العديد من التطبيقات في متجر تطبيقات Android Market مكونات تم تطويرها باستخدام لغات برمجة أخرى غير Java لتحقيق أقصى قدر من الأداء. وبناءً على ذلك، فإن NDK عبارة عن مجموعة أدوات تساعد المطورين على إنشاء مكونات لتطبيقاتهم باستخدام لغات البرمجة المجمعة لأغراض مختلفة، بدءًا من تحقيق الأداء الأمثل وحتى تبسيط التعليمات البرمجية المستخدمة.

لماذا وكيف يتم استخدام الكود الثنائي؟

نعلم جميعًا أن عملية تطوير تطبيقات Android ترتبط ارتباطًا وثيقًا باستخدام لغة البرمجة Java، وأيضًا أن استخدام لغة البرمجة هذه يبسط حياة المطورين إلى حد كبير لأنه يمكنهم استخدام نموذج Java الأنيق الموجه للكائنات. يتم تحويل التطبيقات أو الخوارزميات المطبقة في Java إلى كود ثانوي خاص يعمل بنفس الطريقة على جميع الأنظمة الأساسية المدعومة. في الوقت نفسه، يتوفر جهاز Java الظاهري أو JVM (Java Virtual Machine)، المسؤول عن تجميع JIT وتنفيذ كود Java الثانوي، لجميع الأنظمة الأساسية الموجودة تقريبًا، بدءًا من الحواسيب المركزية وحتى الهواتف المحمولة.

ومع ذلك، في حالة نظام Android، الذي يُستخدم بشكل أساسي على الهواتف الذكية والأجهزة اللوحية، يعد تحقيق أقصى أداء للتطبيقات على الأجهزة المستخدمة أمرًا أساسيًا. يتم أولاً تحويل كود مصدر Java، كما هو مذكور أعلاه، إلى كود بايت. هذا هو بالضبط الرمز الثانوي الذي يتم تنفيذه مع اختلافات طفيفة على الأنظمة الأساسية التي يتوفر لها جهاز Java الظاهري. في نهاية المطاف، يتم تشغيل التطبيق بأكمله داخل جهاز افتراضي على جهاز يعمل بنظام Android.

عندما يتعلق الأمر بتطوير تطبيقات Android، فإن العامل المذكور أعلاه يعد عيبًا بسيطًا. لكن البرمجة في Java يمكن أن تكون صعبة للغاية بسبب التعقيد المستمر للتعليمات البرمجية وصعوبة فهمها. علاوة على ذلك، يتطلب استخدام الرمز الثانوي متعدد المنصات والجهاز الظاهري موارد حوسبة كبيرة على الجهاز.

هناك عامل مهم آخر يتطلب الاهتمام وهو التعليمات البرمجية متعددة المنصات. إذا كنا بحاجة إلى كتابة برنامج لمنصات أجهزة متعددة، فقد ينتهي بنا الأمر إلى إعادة كتابة جزء كبير من وحدة التحكم وكود العرض لكل منصة، وهو ليس حلاً ذكيًا. ولكن يجب نقل جميع التعليمات البرمجية المتعلقة بوحدة التحكم إلى C وC++، حيث تدعمها جميع منصات الأجهزة المحمولة تقريبًا؛ وبالتالي، إذا تمكنا من تنفيذ المنطق داخل المكتبات في C وC++ واستخدامه لاحقًا على منصات متعددة، فسنكون قادرين على تقليل عقوبة أداء التطبيق. في مثل هذه الحالات، سوف نستخدم كود C وC++ مع كود Java العادي أو "رمز النظام الأساسي المتعدد".

عند استخدام لغة برمجة مجمعة، يتم تجميع التعليمات البرمجية المصدر مباشرة في كود الجهاز لوحدة المعالجة المركزية، وليس في تمثيل وسيط كما هو الحال في لغة جافا. بهذه الطريقة، يمكن لمطوري التطبيقات إنشاء تطبيقات ذات أداء مثالي لأجهزة Android المختلفة. يمكن تنظيم أجزاء من التعليمات البرمجية المترجمة ضمن مكتبة مشتركة واحدة، ويمكن استدعاء الوظائف منها من كود Java. يجب إنشاء مكتبة مشتركة منفصلة لكل من بنيات وحدة المعالجة المركزية المدعومة. قد تظل معظم كود المصدر الخاص به دون تغيير. يجب إضافة المكتبات المشتركة المترجمة إلى ملف .apk الخاص بالتطبيق الخاص بك. مع كل ما قيل، لن يتغير النموذج الأساسي لتطبيقات Android.

باستخدام أندرويد NDK

Android NDK عبارة عن مجموعة أدوات تسمح لك بتنفيذ أجزاء من تطبيق Android بلغات برمجة مجمعة مثل C وC++ وتحتوي على مكتبات لإدارة الأنشطة والوصول إلى المكونات المادية للجهاز، مثل أجهزة الاستشعار المختلفة والشاشة.

تم دمج Android NDK مع أدوات من Software Development Kit (Android SDK)، بالإضافة إلى بيئة التطوير المتكاملة لـ Android Studio أو بيئة تطوير Eclipse ADT القديمة. ومع ذلك، لا يمكن استخدام NDK بمفرده.

كيف يعمل أندرويد NDK

قلب Android NDK هو البرنامج النصي ndk-build، وهو المسؤول عن اجتياز ملفات مشروع Android تلقائيًا (يبدأ تطوير كل تطبيق Android جديد باستخدام بيئة تطوير متكاملة مثل Android Studio أو Eclipse بإنشاء ملفات مشروع جديدة). وجمع المعلومات حول المكونات التي يجب تجميعها. هذا البرنامج النصي مسؤول أيضًا عن إنشاء الثنائيات ونسخ هذه الثنائيات إلى دليل ملفات مشروع التطبيق.

يمكننا استخدام الكلمة الأساسية الأصلية للسماح للمترجم بمعرفة أن جزءًا معينًا قد تم تنفيذه داخل الكود المترجم. على سبيل المثال:

أرقام int العامة الأصلية(int x, int y);

أيضًا، أثناء عملية بناء المشروع، يتم إنشاء مكتبات مشتركة (المكتبات المشتركة الأصلية، بامتداد .so) والمكتبات الثابتة (المكتبات المشتركة الأصلية، بامتداد .a)، والتي يمكن ربطها بمكتبات أخرى. تستخدم الواجهة الثنائية للتطبيقات (ABI) المكتبات المشتركة ذات الامتداد .so لتنفيذ تعليمات برمجية للجهاز على النظام أثناء تشغيل التطبيق.

يتم تنفيذ جميع التعليمات البرمجية المترجمة من خلال واجهة تسمى Java Native Interface (JNI)، والتي تسمح بربط مكونات Java وC/C++ معًا.

لبناء المشروع باستخدام البرنامج النصي ndk-build، سيتعين علينا إنشاء ملفين: Android.mk وApplication.mk. يجب أن يكون كلا الملفين موجودين في دليل JNI. يصف ملف Android.mk الوحدة واسمها، وإشارات البناء، والمكتبات المستخدمة، وملفات التعليمات البرمجية المصدر التي يجب تجميعها، ويصف ملف Application.mk الوحدات الثنائية اللازمة لعمل التطبيق.

تثبيت واستخدام Android NDK على Ubuntu

يأتي Android NDK بتنسيق أرشيف ذاتي الاستخراج. لهذا السبب، سيتعين علينا فقط ضبط بت التنفيذ وفك ضغطه:

$ chmod +x android-ndk-r10c-linux-x86_64.bin $ ./android-ndk-r10c-linux-x86_64.bin

ونتيجة لذلك، سيتم حفظ مكونات NDK في دليل العمل الحالي.

التفريغ اليدوي

نظرًا لأن ملف .bin ليس أكثر من أرشيف 7-Zip ذاتي الاستخراج، فيمكننا استخراج محتوياته يدويًا باستخدام الأمر التالي:

$ 7za x -o/path/to/target/directories/android-ndk-r10c-linux-x86_64.bin

تتوفر الحزمة التي تحتوي على مكونات أرشيفية 7-Zip من مستودع Ubuntu الرسمي ويمكن تثبيتها، على سبيل المثال، باستخدام الأمر apt-get:

$ sudo apt-get install p7zip-full

التثبيت باستخدام Android Studio

يمكننا تثبيت Android NDK باستخدام مكون SDK Manager مباشرة من Android Studio.

للقيام بذلك، بعد فتح المشروع، يجب أن تذهب إلى القائمة الرئيسية للنافذة الأدوات\u003e Android\u003e SDK Manager. بعد ذلك، تحتاج إلى تحديد المربعات المجاورة لأسماء المكونات LLDB، CMake وNDK. بعد ذلك، تحتاج فقط إلى تطبيق التغييرات باستخدام الزر المناسب.

إنشاء أو استيراد مشروع بمكونات ثنائية

بعد إعداد Android Studio، يمكننا إنشاء مشروع جديد يدعم لغات البرمجة C/C++. ومع ذلك، إذا كنا بحاجة إلى إضافة أو استيراد التعليمات البرمجية الموجودة بهذه اللغات إلى مشروع Android Studio، فسنضطر إلى اتباع الخطوات أدناه.

الخطوة الأولى هي إنشاء ملفات كود مصدر جديدة باستخدام لغات البرمجة المذكورة وإضافتها إلى مشروع مفتوح في Android Studio. يمكننا تخطي هذه الخطوة إذا كان المشروع يحتوي بالفعل على ملفات مشابهة أو كنا بحاجة إلى استيراد مكتبة مجمعة مسبقًا إليه.

يتيح لك البرنامج النصي لبناء CMake إخبار نظام البناء الذي يحمل نفس الاسم بكيفية تجميع ملفات التعليمات البرمجية المصدر وإنشاء المكتبة الثنائية الناتجة. هذا الملف مطلوب أيضًا لاستيراد مكتبات NDK الموجودة أو المجمعة وربطها بمكتبتنا. يمكننا أيضًا تخطي هذه الخطوة دون أي عواقب إذا كانت مكتبتنا الثنائية الحالية تأتي بالفعل مع ملف البرنامج النصي للبناء CMakeLists.txt أو إذا كانت تستخدم مكون ndk-build وتأتي مع ملف البرنامج النصي للبناء Android.mk .

بعد ذلك، نحتاج إلى إخبار Gradle بوجود مكتبتنا الثنائية عن طريق تحديد المسار إلى ملف البرنامج النصي الخاص ببناء CMake أو ndk-build. يستخدم Gradle البرنامج النصي للبناء المحدد لاستيراد الكود المصدري إلى مشروع Android Studio وحزم المكتبة الثنائية الناتجة (ملف بامتداد .so) في ملف حزمة بتنسيق APK.

ملاحظة مهمة:إذا كان المشروع يستخدم أداة ndkCompile القديمة، فسيتعين علينا فتح ملف build.poperties وإزالة السطر التالي من التعليمات البرمجية منه قبل تكوين Gradle لاستخدام CMake أو ndk-build:

Android.useDeprecatedNdk = true

يمكننا الآن إنشاء تطبيقنا وتشغيله من خلال النقر على زر التشغيل. سيعتبر Gradle عملية CMake أو ndk-build بمثابة تبعية سيتم إنشاؤها، وبناء المكتبة الثنائية، وتجميعها في ملف APK.

بمجرد تشغيل التطبيق على الجهاز أو في المحاكي، يمكننا استخدام ميزات بيئات التطوير المتكاملة المتنوعة مثل Android Studio لتصحيح أخطائه.

كل هذا يوضح أهمية Android NDK لمطوري التطبيقات لمنصة Android. على سبيل المثال، تسمح هذه المجموعة من مكونات البرامج لمنشئي محركات الألعاب بتحسين إصدارات منتجاتهم لنظام Android بشكل أفضل، ونتيجة لذلك سينتجون تأثيرات رسومية أكثر إثارة للإعجاب، وإنفاق موارد نظام أقل عليها.

لا ترتبط عملية إنشاء تطبيق بسيط يعتمد على Android NDK بأية صعوبات. ومع ذلك، يجب على كل مطور أن يفهم نقطة مهمة واحدة: تم تصميم مكونات برنامج Android NDK للاستخدام في حالات محددة ولا ينبغي استخدامها في تطوير أي تطبيق.

يمكن أن يساعد Android NDK في عملية تطوير التطبيقات ويجعلها صعبة قدر الإمكان. كما أنه ليس سراً أن استخدام الكود الثنائي على منصة أندرويد في بعض الحالات لا يؤدي إلى زيادة ملحوظة في أداء التطبيق (على الرغم من أن أدائه يزيد في معظم الحالات)، ولكنه على أي حال يؤدي إلى تعقيد الكود الخاص به. عادةً، يتم تحقيق تحسينات في أداء التطبيق عن طريق تشغيل التعليمات البرمجية مع تعليمات خاصة بوحدة المعالجة المركزية (CPU). ولكن بشكل عام، يوصى باستخدام NDK فقط عندما يكون أداء التطبيق معلمة حرجة، وليس عندما يكون المطور أكثر راحة في كتابة التعليمات البرمجية في C/C++.

في الختام، ينبغي القول أنه لا توجد قواعد ثابتة تحكم الحالات المحتملة لاستخدام NDK، لذلك يجب عليك دائمًا اللجوء إلى معرفتك وخبرتك وحدسك.

أندرويد إن دي كيههي مجموعة أدوات موثوقة وفعالة مصممة خصيصًا لنظام Android والمطورين الذين يحتاجون إلى تنفيذ أجزاء من تطبيقاتهم باستخدام لغات البرمجة مثل C++ أو C#.

ومع ذلك، قبل استخدام Android NDK، يجب أن تكون خبيرًا كبيرًا في لغات التعليمات البرمجية الأصلية هذه وأن تتأكد من أن جهاز الكمبيوتر الخاص بك يلبي جميع متطلبات النظام، وإلا فلن تتمكن من الاستفادة من جميع الميزات التي تأتي بها مجموعة الأدوات.

بشكل عام، يمكنك الحصول على الكثير من برامج C أو Java النصية للتطبيق الحالي، ولكن عند استخدام Android NDK، يمكنك تسريع عملية تطوير مشروعك، بالإضافة إلى الحفاظ على مزامنة التغييرات بين المشاريع التي تعمل بنظام Android وغير Android.

باعتبارك مطورًا متقدمًا، عند استخدام Android NDK، فإنك تحتاج إلى الموازنة بين فوائده وعيوبه. ومن ثم، يجب عليك استخدامه فقط إذا كان ضروريًا عند تطوير تطبيق جديد وكنت بحاجة إلى هذا المكون.

ومع ذلك، ليس عليك أن تفترض أنه يمكنك زيادة أداء التطبيق الخاص بك فقط لأنك تستخدم التعليمات البرمجية الأصلية. ما عليك سوى التحقق من المتطلبات ومعرفة ما إذا كانت واجهات برمجة تطبيقات إطار عمل Android توفر لك الوظيفة الرئيسية التي تحتاجها.

ومع ذلك، عندما تكون متأكدًا من أن Android NDK هو مكون تحتاجه حقًا لتشغيل تطبيقاتك وتطويرها، يمكنك فك ضغطه ووضعه في الدليل المناسب. بعد ذلك، ستكون المتغيرات مثل "android_log_print" و"sample_ndk" متاحة داخل مشروعك.

بالإضافة إلى ذلك، توفر لك حزمة NDK الأدوات المناسبة حتى تتمكن من العمل بكفاءة مع البرامج النصية الخاصة بك، دون الحاجة إلى التعامل مع جميع تفاصيل وحدة المعالجة المركزية وABI.

مع الأخذ في الاعتبار أن Android NDK مخصص خصيصًا لمطوري Java، فهو يوفر لهم فئات مفيدة تُعلم الكود الأصلي الخاص بهم بأي عمليات رد اتصال في دورة حياة النشاط. ومع ذلك، فإن الجزء الأكثر إثارة للاهتمام في مجموعة الأدوات هذه هو أنها تمكنهم من تضمين المكتبات الأصلية في ملف حزمة التطبيق، والذي يمكن نشره على أجهزة Android.

ما الجديد في Android NDK Revision 19c:

  • يجب على المطورين البدء في اختبار تطبيقاتهم باستخدام LLD. لقد تحول AOSP إلى استخدام LLD افتراضيًا وسيستخدمه NDK افتراضيًا في الإصدار التالي. ستتم إزالة BFD والذهب بمجرد مرور LLD بدورة الإصدار مع عدم وجود مشكلات كبيرة لم يتم حلها (يقدر بـ R21). اختبر LLD في تطبيقك عن طريق تمرير -fuse-ld=lld عند الارتباط. ملاحظة: لا يدعم lld حاليًا الرموز المضغوطة على نظام التشغيل Windows. المشكلة 888. لا يمكن لـ Clang أيضًا إنشاء رموز مضغوطة على Windows، ولكن قد يكون هذا مشكلة عند استخدام القطع الأثرية المبنية من Darwin أو Linux.
  • سيتطلب متجر Play دعم 64 بت عند تحميل APK بدءًا من أغسطس 2019. ابدأ النقل الآن لتجنب المفاجآت عندما يحين الوقت. لمزيد من المعلومات، راجع منشور المدونة هذا.
  • المشكلة 780: سلاسل الأدوات المستقلة أصبحت الآن غير ضرورية. تم الآن تثبيت Clang وbinutils وsysroot وأجزاء سلسلة الأدوات الأخرى على $NDK/toolchains/llvm/prebuilt/ وسيقوم Clang بالعثور عليها تلقائيًا.