نصائح لمورّدي البرمجيات الوسيطة

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

اختيار مستويات واجهة برمجة التطبيقات وإصدارات NDK

لا يمكن للمستخدمين استخدام minSdkVersion أقل من قيمة هاتفك. إذا كانت تطبيقات المستخدمين تحتاج إلى التشغيل على واجهة برمجة التطبيقات 21، فلا يمكنك إنشاء واجهة برمجة التطبيقات 24. ولا بأس في إنشاء مكتبتك على مستوى واجهة برمجة تطبيقات أقل من المستخدمين. يمكنك إنشاء واجهة برمجة تطبيقات من الإصدار 16 والحفاظ على التوافق مع مستخدمي واجهة برمجة التطبيقات 21.

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

استخدام STL

إذا كنت تكتب لغة C++ وتستخدم STL، سيؤثّر اختيارك بين libc++_shared وlibc++_static على المستخدمين في حال توزيع مكتبة مشتركة. وفي حال توزيع مكتبة مشتركة، يجب استخدام السمة libc++_shared أو التأكد من عدم عرض رموز libc++ في مكتبتك. وأفضل طريقة لإجراء ذلك هي من خلال الإعلان صراحةً عن سطح واجهة التطبيق الثنائية (ABI) باستخدام نص برمجي للإصدار (يساعد ذلك أيضًا في الحفاظ على خصوصية تفاصيل التنفيذ). على سبيل المثال، قد تحتوي إحدى المكتبات الحسابية البسيطة على البرنامج النصي للإصدار التالي:

LIBMYMATH {
global:
    add;
    sub;
    mul;
    div;
    # C++ symbols in an extern block will be mangled automatically. See
    # https://stackoverflow.com/a/21845178/632035 for more examples.
    extern "C++" {
        "pow(int, int)";
    }
local:
    *;
};

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

هناك خيار آخر أقل قوة وهو استخدام -Wl,--exclude-libs,libc++_static.a -Wl,--exclude-libs,libc++abi.a عند الربط. وتُعدّ هذه الطريقة أقل فعالية لأنه لن تُخفي سوى الرموز في المكتبات ذات الاسم الواضح ولا يتم الإبلاغ عن بيانات التشخيص في المكتبات غير المستخدَمة (لا يُعتبر الخطأ الإملائي في اسم المكتبة خطأ، وتقع على المستخدم عبء تعديل قائمة المكتبة بشكل مستمر). ولا يخفي هذا النهج أيضًا تفاصيل التنفيذ الخاصة بك.

توزيع المكتبات الأصلية في الاقتراحات المطبّقة تلقائيًا

يمكن للمكوّن الإضافي لنظام Gradle المتوافق مع Android استيراد التبعيات الأصلية التي يتم توزيعها في الاقتراحات المطبّقة تلقائيًا. إذا كان المستخدمون يستخدمون مكوّن Gradle الإضافي لنظام التشغيل Android، ستكون هذه أسهل طريقة لمشاهدة المكتبة.

يمكن تجميع المكتبات الأصلية في حزمة AAR من خلال AGP. سيكون هذا هو الخيار الأسهل إذا سبق إنشاء مكتبتك من خلال externalNativeBuild.

يمكن للإصدارات التي لا تستخدم معيار AGP استخدام منافذ ndkports أو إنشاء حزمة محتوى يدويًا عن طريق اتّباع مستندات Prefab لإنشاء الدليل الفرعي prefab/ في الاقتراحات المطبّقة تلقائيًا.

البرمجيات الوسيطة لبرمجة Java مع مكتبات JNI

تحتاج مكتبات Java التي تتضمّن مكتبات JNI (أي مصادر AAR التي تحتوي على jniLibs) إلى توخي الحذر كي لا تتعارض مكتبات JNI التي تتضمنها مع مكتبات أخرى في تطبيق المستخدم. على سبيل المثال، إذا كان AAR يتضمن libc++_shared.so، ولكن مع إصدار libc++_shared.so مختلف عن الإصدار الذي يستخدمه التطبيق، سيتم تثبيت إصدار واحد فقط لحزمة APK، وقد يؤدي ذلك إلى سلوك غير موثوق به.

الحل الأكثر موثوقية هو ألا تتضمن مكتبات Java أكثر من مكتبة واحدة JNI (هذه نصيحة جيدة للتطبيقات أيضًا). يجب ربط جميع التبعيات بما في ذلك STL بشكل ثابت بمكتبة التنفيذ، ويجب استخدام نص برمجي للإصدار لفرض واجهة ABI. على سبيل المثال، في مكتبة Java com.example.foo التي تتضمن مكتبة JNI libfooimpl.so، يجب استخدام النص البرمجي للإصدار التالي:

LIBFOOIMPL {
global:
    JNI_OnLoad;
local:
    *;
};

يستخدم هذا المثال registerNatives من خلال JNI_OnLoad كما هو موضّح في نصائح JNI لضمان إظهار الحد الأدنى من واجهة التطبيق الثنائية (ABI) والحدّ من وقت تحميل المكتبة.