NDK أندرويد: ما هو، مجموعة التطوير الأصلية و JNI لـ C++

المؤلف: IT Sectr نُشر: 2026-02-09 وقت القراءة: 10 دق

NDK (مجموعة التطوير الأصلية) هو مجموعة أدوات لكتابة أجزاء من التطبيق بلغة C و C++ لأندرويد. على عكس SDK أندرويد العادي، يقوم NDK بتجميع الكود إلى مكتبات أصلية (.so) تعمل مباشرة مع المعالج دون طبقة آلة افتراضية. وفقاً لدليل NDK من جوجل، 2026، يُستخدم NDK للحوسبة عالية الأداء، الألعاب، معالجة الصوت وإعادة استخدام مشاريع C/C++ الحالية. JNI (واجهة جافا الأصلية) تربط الكود الأصلي مع Kotlin وجافا.

الخلاصة

  • NDK — مجموعة أدوات لتجميع كود C/C++ إلى مكتبات أصلية لأندرويد.
  • JNI — واجهة تفاعل بين آلة جافا/Kotlin الافتراضية والكود الأصلي.
  • CMake — نظام البناء الرئيسي لمشاريع NDK، يتم تكوينه عبر CMakeLists.txt.
  • ABI — بنية المعالج المستهدفة (arm64-v8a, armeabi-v7a, x86_64) التي تُبنى المكتبة من أجلها.
  • NDK غير ضروري لمعظم تطبيقات أندرويد ويُستخدم فقط في المهام ذات متطلبات الأداء العالي.

ما هو NDK

NDK (مجموعة التطوير الأصلية) هو مجموعة أدوات للترجمة المتقاطعة لكود C و C++ إلى مكتبات قابلة للتنفيذ لأندرويد. تشمل NDK المترجم Clang، المكتبة القياسية libc++، ملفات رؤوس واجهة برمجة تطبيقات أندرويد وأدوات البناء. يكتب المطور الكود الأصلي بلغة C أو C++، يجمعه إلى ملفات .so (مكتبات مشتركة) ويربطها بالتطبيق عبر JNI.

NDK لا يستبدل SDK أندرويد بل يكمله. تبقى واجهة المستخدم، دورة الحياة وخدمات النظام كلها في Kotlin أو جافا. يحل الكود الأصلي مهاماً محددة: الحسابات الرياضية، التشفير، برامج الترميز، الفيزياء في الألعاب. توصي جوجل باستخدام NDK فقط عندما لا يوفر SDK الأداء المطلوب أو الوصول إلى إمكانيات العتاد.

يبدأ تاريخ NDK من عام 2009 (NDK r1). خلال هذا الوقت، تطورت مجموعة الأدوات من مجموعة تجريبية من البرامج النصية إلى نظام ناضج يدعم CMake، تصحيح الأخطاء باستخدام LLDB والتنميط. اعتباراً من 2026، الإصدار الحالي هو NDK r27 مع Clang 19، دعم كامل لـ C++20 و libc++ كمكتبة قياسية وحيدة.

الإمكانيات الرئيسية لـ NDK

يمنح NDK المطور إمكانية الوصول إلى إمكانيات أندرويد منخفضة المستوى من خلال واجهات برمجة التطبيقات الأصلية. تشمل حالات الاستخدام الرئيسية: العمل مع OpenGL ES و Vulkan للرسومات ثلاثية الأبعاد، معالجة الصوت عبر Oboe، عمليات التشفير عبر BoringSSL وتحسينات NEON لـ ARM. جميع واجهات برمجة التطبيقات هذه متاحة من C/C++ وتتطلب NDK للترجمة.

بالإضافة إلى ذلك، يسمح NDK بإعادة استخدام مكتبات C/C++ الحالية دون إعادة كتابتها في Kotlin. هذا مهم بشكل خاص للمشاريع ذات التاريخ الطويل في C++ — محركات الألعاب (Unity, Unreal Engine)، مكتبات رؤية الكمبيوتر (OpenCV) أو حزم التشفير (OpenSSL). في هذه الحالات، يوفر NDK سنوات من التطوير.

المكونالوظيفة
Clangمترجم C/C++ مع دعم C++20
libc++المكتبة القياسية لـ C++ (الوحيدة في NDK r27)
CMakeنظام بناء المكتبات الأصلية
LLDBمصحح الكود الأصلي في Android Studio
ndk-buildنظام بناء قديم (استُبدل بـ CMake)

NDK مقابل SDK: متى نحتاج الكود الأصلي

الاختيار بين NDK و SDK النقي يعتمد على المهمة. يوفر SDK أندرويد واجهات برمجة تطبيقات جاهزة بلغة Kotlin/جافا لـ 90% من السيناريوهات — الشبكات، الملفات، الإشعارات، الكاميرا. يُستخدم NDK عندما لا توفر واجهات برمجة التطبيقات هذه الأداء أو الوظائف المطلوبة. على سبيل المثال، معالجة الصوت في الوقت الفعلي عبر SDK ممكنة ولكن بزمن استجابة 50–200 مللي ثانية، بينما المكتبة الأصلية عبر Oboe تقلل زمن الاستجابة إلى 5–15 مللي ثانية.

حجم APK هو عامل آخر. إضافة NDK تزيد من حجم التطبيق لأن كل بنية ABI تتطلب مكتبة .so منفصلة. ومع ذلك، فإن استخدام Android App Bundle (AAB) يخفف المشكلة: Google Play توصل للمستخدم فقط المكتبة المناسبة لبنية جهازه. مشروع أصلي نموذجي يضيف 1–10 ميجابايت إلى حجم التثبيت لكل جهاز.

مقارنة SDK و NDK حسب المعايير

المعيارSDK (Kotlin/Java)NDK (C/C++)
الأداءمتوسط (ترجمة JIT/AOT)عالي (كود آلي)
زمن استجابة الصوت50–200 مللي ثانية5–15 مللي ثانية عبر Oboe
الرسومات ثلاثية الأبعادعبر واجهة Canvas/OpenGL KotlinVulkan/OpenGL مباشرة
تعقيد التطويرمنخفضعالي (إدارة الذاكرة، JNI)
قابلية نقل الكودأندرويد فقطلينكس، ويندوز، ماك، iOS
حجم APKأدنى+1–10 ميجابايت لكل مكتبة

هندسة NDK: الأدوات والمكتبات

يُثبت NDK بشكل منفصل عن SDK أندرويد عبر مدير SDK. داخل دليل NDK توجد سلسلة الأدوات (المترجمات)، مكتبات المنصة، ملفات الرؤوس والأدوات. سلسلة أدوات NDK تتضمن Clang للترجمة المتقاطعة لجميع ABI المستهدفة: arm64-v8a, armeabi-v7a, x86_64 و x86. يختار المترجم تلقائياً البنية المطلوبة بناءً على علامات CMake أو ndk-build.

واجهات برمجة تطبيقات أندرويد للكود الأصلي متوفرة في ملفات الرؤوس بدليل sysroot/usr/include. هناك توجد تعريفات لجميع واجهات برمجة التطبيقات المتاحة في الكود الأصلي: native_activity.h لدورة حياة النشاط، input.h لأحداث الإدخال، sensor.h لأجهزة الاستشعار. تضمين ملفات الرؤوس هذه يتيح الوصول إلى إمكانيات العتاد دون طبقة JNI.

هيكل دليل NDK

text
ndk/
  toolchains/
    llvm/prebuilt/windows-x86_64/
      bin/        // Clang, ld, llvm-profdata
      sysroot/    // ملفات رؤوس واجهة برمجة تطبيقات أندرويد
  platforms/
    android-26/  // libc, libm, libdl لواجهة API 26
    android-34/
  sources/
    cxx-stl/     // رؤوس libc++
  build/
    cmake/       // ملف سلسلة أدوات CMake

واجهات برمجة التطبيقات الأصلية لأندرويد

عبر NDK تتوفر المجموعات التالية من واجهات برمجة التطبيقات الأصلية: Native App Glue (إدارة دورة حياة النشاط من C)، OpenGL ES 3.2 و Vulkan 1.3 (الرسومات)، Oboe (صوت منخفض زمن الاستجابة)، Neural Networks API (التعلم الآلي على الجهاز). كل واجهة لديها ملف رأس ومكتبة ثابتة/ديناميكية كجزء من NDK.

العمل مع واجهات برمجة التطبيقات الأصلية لا يتطلب JNI — يتم استدعاء الدوال مباشرة من كود C/C++. ومع ذلك، لا يزال التفاعل مع واجهة المستخدم في Kotlin يتطلب JNI. هذه الهندسة الهجينة شائعة في محركات الألعاب: الرسومات والفيزياء بلغة C++ (عبر Vulkan)، القوائم وواجهة المستخدم في Kotlin (عبر Jetpack Compose).

JNI: كيف يستدعي Kotlin C++

JNI (واجهة جافا الأصلية) هي آلية قياسية لاستدعاء الكود الأصلي من آلة جافا/Kotlin الافتراضية. يعلن المطور عن دالة خارجية في Kotlin باستخدام الكلمة المفتاحية external ويحمل مكتبة .so عبر System.loadLibrary. في جانب C++، تُعلن الدالة باستخدام اصطلاح تسمية JNI الذي يشفّر اسم الحزمة والفئة.

الإعلان عن دالة أصلية في Kotlin

kotlin
class NativeBridge {
    companion object {
        init {
            System.loadLibrary("native-lib")
        }
    }

    external fun stringFromJNI(): String
    external fun fibonacci(n: Int): Long
    external fun processBuffer(data: ByteArray): ByteArray
}

// الاستخدام في الكود
val bridge = NativeBridge()
println(bridge.stringFromJNI())  // "Hello from C++"

تنفيذ دالة JNI بلغة C++

cpp
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_NativeBridge_stringFromJNI(
    JNIEnv* env, jobject /* this */) {
    std::string hello = "Hello from C++ NDK";
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT jlong JNICALL
Java_com_example_app_NativeBridge_fibonacci(
    JNIEnv* env, jobject /* this */, jint n) {
    if (n <= 1) return n;
    jlong a = 0, b = 1;
    for (int i = 2; i <= n; i++) {
        jlong temp = a + b;
        a = b;
        b = temp;
    }
    return b;
}

إعداد CMakeLists.txt لـ NDK

CMake هو نظام البناء الرئيسي لـ NDK، الذي حل محل ndk-build القديم. يصف ملف CMakeLists.txt الملفات المصدرية والمكتبات وعلامات الترجمة. يستدعي Android Studio CMake تلقائياً عند بناء المشروع إذا تم تكوين externalNativeBuild في build.gradle. يُنشئ CMake ملفات make لكل ABI مستهدف ويجمع الكود الأصلي بالتوازي.

مثال CMakeLists.txt لمكتبة أصلية

cmake
cmake_minimum_required(VERSION 3.22.1)
project("nativelib")

# تضمين ملفات الرؤوس
include_directories(src/main/cpp/include)

# إنشاء مكتبة أصلية
add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp
    src/main/cpp/math_utils.cpp
    src/main/cpp/audio_processor.cpp
)

# ربط مكتبات نظام أندرويد
target_link_libraries(
    native-lib
    android
    log
    OpenSLES
    # libc++ تُربط تلقائياً
)

# علامات التحسين
target_compile_options(native-lib PRIVATE -O3 -Wall -Wextra)

إعداد build.gradle لـ NDK

groovy
android {
    defaultConfig {
        ndk {
            // ABI المستهدفة للبناء
            abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
            version "3.22.1"
        }
    }
    buildTypes {
        release {
            externalNativeBuild {
                cmake {
                    arguments "-DCMAKE_BUILD_TYPE=Release"
                }
            }
        }
    }
}

ABI والبناء متعدد المنصات

ABI (واجهة التطبيق الثنائية) هو تنسيق الكود الآلي الذي يحدد التوافق مع معالج الجهاز. كل بنية ARM أو x86 لها ABI خاص بها. يجمع NDK الكود الأصلي بشكل منفصل لكل ABI محدد. أشهر ABI في 2026: arm64-v8a (99% من الأجهزة الحديثة)، armeabi-v7a (الأجهزة القديمة 32 بت)، x86_64 (المحاكي و Chromebook).

تحديد abiFilters في build.gradle يحد من البناء فقط للبنى المطلوبة، مما يقلل وقت الترجمة. بالنسبة لـ Google Play، يُوصى بتضمين جميع ABI التي تدعمها المكتبة — وهذا يضمن عملها على جميع الأجهزة. تتيح Google Play Console تكوين توصيل APK خاص بكل ABI عبر App Bundle.

تحديد ABI الجهاز في الكود الأصلي

cpp
#include <jni.h>
#include <android/api-level.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_NativeBridge_getABIInfo(
    JNIEnv* env, jobject /* this */) {

    #if defined(__arm__)
        #if defined(__ARM_ARCH_7A__)
            return env->NewStringUTF("armeabi-v7a");
        #endif
    #elif defined(__aarch64__)
        return env->NewStringUTF("arm64-v8a");
    #elif defined(__x86_64__)
        return env->NewStringUTF("x86_64");
    #elif defined(__i386__)
        return env->NewStringUTF("x86");
    #endif

    return env->NewStringUTF("unknown");
}
ABIالبنيةعدد البتاتالأجهزة
arm64-v8aARMv8-A64 بتجميع الهواتف الحديثة تقريباً
armeabi-v7aARMv7-A32 بتالأجهزة القديمة (قبل 2020)
x86_64x86-6464 بتمحاكي، Chromebook
x86x86 IA-3232 بتمحاكيات قديمة

أمثلة على الكود الأصلي بلغة C++

لنأخذ مثالاً عملياً: مكتبة رياضية للعمل مع الأعداد ذات الفاصلة العائمة. الكود الأصلي بلغة C++ ينفذ العمليات الحسابية بكفاءة أعلى من Kotlin، بفضل الوصول المباشر لتعليمات NEON لـ ARM وغياب فحوصات حدود المصفوفات في وقت التشغيل. يوضح هذا المثال نمطاً نموذجياً لاستخدام NDK — نقل الحسابات الثقيلة إلى الطبقة الأصلية.

العمليات الرياضية في الكود الأصلي

cpp
#include <jni.h>
#include <cmath>
#include <vector>

extern "C" JNIEXPORT jfloatArray JNICALL
Java_com_example_app_NativeBridge_normalizeArray(
    JNIEnv* env, jobject, jfloatArray input) {

    jsize len = env->GetArrayLength(input);
    jfloat* elements = env->GetFloatArrayElements(input, nullptr);

    // حساب المتوسط والانحراف المعياري
    float sum = 0.0f, sumSq = 0.0f;
    for (jsize i = 0; i < len; i++) {
        sum += elements[i];
        sumSq += elements[i] * elements[i];
    }
    float mean = sum / len;
    float stddev = std::sqrt(sumSq / len - mean * mean);

    // التطبيع: (x - mean) / stddev
    jfloat* result = new jfloat[len];
    for (jsize i = 0; i < len; i++) {
        result[i] = (elements[i] - mean) / stddev;
    }

    env->ReleaseFloatArrayElements(input, elements, JNI_ABORT);
    jfloatArray output = env->NewFloatArray(len);
    env->SetFloatArrayRegion(output, 0, len, result);
    delete[] result;
    return output;
}

التسجيل من الكود الأصلي

لتصحيح الكود الأصلي، استخدم الماكرو __android_log_print من مكتبة android/log.h. تظهر الرسائل في Logcat بجانب سجلات Java/Kotlin. يساعد مستوى التسجيل (ANDROID_LOG_DEBUG, ANDROID_LOG_ERROR) في تصفية الرسائل.

cpp
#include <android/log.h>
#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

extern "C" JNIEXPORT void JNICALL
Java_com_example_app_NativeBridge_processData(
    JNIEnv* env, jobject, jint count) {

    LOGD("Processing %d items", count);

    for (int i = 0; i < count; i++) {
        // معالجة ثقيلة
        LOGD("Item %d processed", i);
    }

    LOGD("Processing complete");
}

الأسئلة الشائعة

هل استخدام NDK إلزامي لتطوير أندرويد؟

لا. لمعظم التطبيقات، يكفي SDK مع Kotlin. NDK ضروري للمهام عالية الأداء: معالجة الصوت/الفيديو في الوقت الفعلي، الرسومات ثلاثية الأبعاد، التشفير أو إعادة استخدام مشاريع C/C++ الحالية.

أي مترجم يستخدمه NDK؟

يستخدم NDK Clang من سلسلة أدوات LLVM. منذ NDK r23، تمت إزالة GCC بالكامل. يقوم Clang بالترجمة المتقاطعة لجميع ABI أندرويد: arm64-v8a, armeabi-v7a, x86_64, x86.

ما هو ABI في سياق NDK؟

ABI (واجهة التطبيق الثنائية) هو تنسيق الكود الآلي الذي يحدد التوافق مع المعالج. يقوم NDK ببناء مكتبات .so لكل ABI بشكل منفصل. arm64-v8a هو ABI الرئيسي للأجهزة الحديثة.

هل يمكن تصحيح كود C++ عبر NDK؟

نعم. يدعم Android Studio LLDB — مصحح الكود الأصلي. يمكنك تعيين نقاط توقف في ملفات C++، عرض المتغيرات ومكدس الاستدعاءات. يتطلب ذلك NDK وإضافة LLDB.

كيف يؤثر NDK على حجم APK؟

كل مكتبة أصلية تضيف من 100 كيلوبايت إلى عدة ميجابايت في APK. كل ABI يتطلب مكتبة .so منفصلة. يوصل Android App Bundle للمستخدم فقط البنية المناسبة، مما يقلل حجم التثبيت.

الملخص

  • NDK — مجموعة أدوات للترجمة المتقاطعة لكود C/C++ إلى مكتبات أصلية لأندرويد باستخدام مترجم Clang.
  • JNI — واجهة تربط Kotlin/Java مع الكود الأصلي عبر الكلمة المفتاحية external واصطلاحات تسمية الدوال.
  • CMake — نظام البناء الرئيسي لـ NDK، يُكوّن عبر CMakeLists.txt و externalNativeBuild في Gradle.
  • ABI يحدد بنية المعالج — arm64-v8a, armeabi-v7a, x86_64. كل ABI يتطلب بناء مكتبة منفصل.
  • استخدم NDK فقط للمهام التي تتطلب أداءً عالياً: الألعاب، الصوت، الرسومات، التشفير، التعلم الآلي على الجهاز.
  • يدعم Google Play التقسيم حسب ABI عبر App Bundle: يتلقى المستخدم فقط المكتبة المناسبة لبنية جهازه.
  • المكتبات الأصلية تزيد حجم APK بشكل كبير ولكنها توفر أقصى أداء وزمن استجابة منخفض للعمليات الحرجة.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا