استخدام واجهات برمجة تطبيقات أحدث

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

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

لهذا السبب، سيمنعك NDK من إنشاء مراجع قوية إلى واجهات برمجة التطبيقات الأحدث من minSdkVersion لتطبيقك هذا يحميك من رمز شحن عن طريق الخطأ يعمل أثناء الاختبار ولكن سيتعذّر تحميله (سيتم طرح UnsatisfiedLinkError من System.loadLibrary()) في تاريخ أقدم الأجهزة. من ناحية أخرى، من الأصعب كتابة التعليمات البرمجية التي تستخدم واجهات برمجة التطبيقات أحدث من minSdkVersion لتطبيقك، لأنه يجب عليك طلب واجهات برمجة التطبيقات باستخدام dlopen() وdlsym() بدلاً من استدعاء دالة عادي.

بدلاً من استخدام مراجع قوية، يمكن استخدام مراجع ضعيفة. إشارة ضعيفة مرجع لم يتم العثور عليه عند تحميل المكتبة النتائج في عنوان ضبط هذا الرمز على nullptr بدلاً من تعذُّر التحميل. ما زال لا يمكن استدعاء هذه المواقع بأمان، ولكن طالما أن مواقع الاتصال محمية لمنع الاتصال في حال عدم توفّرها، ويمكن تشغيل باقي التعليمات البرمجية، ويمكنك يمكنك استدعاء واجهة برمجة التطبيقات بشكل طبيعي بدون الحاجة إلى استخدام dlopen() وdlsym().

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

تفعيل مراجع واجهة برمجة التطبيقات الضعيفة في إصدارك

إنشاء فيديوهات Shorts

اجتياز -DANDROID_WEAK_API_DEFS=ON عند تشغيل CMake. إذا كنت تستخدم أداة CMake عبر externalNativeBuild، أضِف ما يلي إلى build.gradle.kts (أو هذا رائع إذا كنت لا تزال تستخدم build.gradle:

android {
    // Other config...

    defaultConfig {
        // Other config...

        externalNativeBuild {
            cmake {
                arguments.add("-DANDROID_WEAK_API_DEFS=ON")
                // Other config...
            }
        }
    }
}

لعبة ndk-build

أضِف ما يلي إلى ملف Application.mk:

APP_WEAK_API_DEFS := true

إذا لم يكن لديك ملف Application.mk، يمكنك إنشاؤه في الدليل كملف Android.mk. هناك تغييرات إضافية في الملف build.gradle.kts (أو build.gradle) غير ضروري لعمل ndk-build.

أنظمة تصميم أخرى

إذا كنت لا تستخدم CMake أو ndk-build، يمكنك الرجوع إلى مستندات التصميم. لمعرفة ما إذا كانت هناك طريقة مقترحة لتفعيل هذه الميزة أم لا. إذا كان الإصدار هذا الخيار في الأصل، يمكنك تفعيل الميزة من خلال وضع العلامات التالية عند التجميع:

-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability

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

راجِع دليل صيانة نظام التصميم للحصول على مزيد من المعلومات.

طلبات البيانات من واجهة برمجة التطبيقات المحمية

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

يمكن أن يصدر Clang تحذيرًا (unguarded-availability) عند تنفيذ الإجراء بدون رقابة. طلب بيانات من واجهة برمجة تطبيقات غير متاحة لـ minSdkVersion في تطبيقك. إذا كنت باستخدام ملف ndk-build أو ملف CMake Toolchain، سيتم تلقائيًا تم تفعيلها والترويج لها إلى خطأ عند تفعيل هذه الميزة.

في ما يلي مثال على رمز برمجي يستخدِم استخدامًا مشروطًا لواجهة برمجة تطبيقات بدون تم تفعيل هذه الميزة، باستخدام dlopen() وdlsym():

void LogImageDecoderResult(int result) {
    void* lib = dlopen("libjnigraphics.so", RTLD_LOCAL);
    CHECK_NE(lib, nullptr) << "Failed to open libjnigraphics.so: " << dlerror();
    auto func = reinterpret_cast<decltype(&AImageDecoder_resultToString)>(
        dlsym(lib, "AImageDecoder_resultToString")
    );
    if (func == nullptr) {
        LOG(INFO) << "cannot stringify result: " << result;
    } else {
        LOG(INFO) << func(result);
    }
}

من الفوضوي بعض الشيء في القراءة، وهناك بعض التكرارات لأسماء الدوال (وإذا تكتب C، والتواقيع أيضًا)، فسيتم إنشاؤها بنجاح ولكن دائمًا اتخاذ الإجراء الاحتياطي في وقت التشغيل إذا أخطأت عن طريق الخطأ في كتابة اسم الدالة إلى dlsym، وعليك استخدام هذا النمط لكل واجهة برمجة تطبيقات.

في حال وجود مراجع ضعيفة لواجهة برمجة التطبيقات، يمكن إعادة كتابة الدالة أعلاه على النحو التالي:

void LogImageDecoderResult(int result) {
    if (__builtin_available(android 31, *)) {
        LOG(INFO) << AImageDecoder_resultToString(result);
    } else {
        LOG(INFO) << "cannot stringify result: " << result;
    }
}

مزيد من المعلومات، يتّصل "__builtin_available(android 31, *)" يتم تخزين النتيجة مؤقتًا ومقارنتها بـ 31 بواسطة android_get_device_api_level() (وهو مستوى واجهة برمجة التطبيقات الذي يوفّر AImageDecoder_resultToString()).

إنّ أبسط طريقة لتحديد القيمة التي يجب استخدامها لـ __builtin_available هي محاولة البناء بدون حارس (أو حارس __builtin_available(android 1, *)) ونفِّذ ما تخبرك به رسالة الخطأ. على سبيل المثال، هناك مكالمة غير خاضعة للرقابة إلى AImageDecoder_createFromAAsset() تشتمل على سينتج عن minSdkVersion 24 ما يلي:

error: 'AImageDecoder_createFromAAsset' is only available on Android 30 or newer [-Werror,-Wunguarded-availability]

وفي هذه الحالة، يجب حماية المكالمة من قِبل "__builtin_available(android 30, *)". إذا لم يكن هناك خطأ في الإصدار، فإما أن تتوفر واجهة برمجة التطبيقات دائمًا minSdkVersion وليست هناك حاجة إلى حماية أو تم إعداد تصميمك بشكل خاطئ تم إيقاف تحذير unguarded-availability.

بدلاً من ذلك، سيقول مرجع NDK API شيئًا على غرار "تم الطرح في واجهة برمجة التطبيقات 30" لكل واجهة برمجة تطبيقات. إذا كان هذا النص غير موجود، فهذا يعني أنه تتوفر واجهة برمجة التطبيقات لجميع مستويات واجهة برمجة التطبيقات المتوافقة.

تجنُّب تكرار برامج حماية واجهة برمجة التطبيقات

إذا كنت تستخدم هذا الخيار، سيكون لديك على الأرجح أقسام من الرمز في التطبيق لا يمكن استخدامها إلا على الأجهزة الجديدة بما يكفي. بدلاً من تكرار __builtin_available() مراجعة كل دالة من الدوال، ويمكنك إضافة تعليقات توضيحية إلى التعليمات البرمجية الخاصة بها باعتبار أنها تتطلب مستوى معين لواجهة برمجة التطبيقات. على سبيل المثال، واجهات برمجة التطبيقات ImageDecoder API نفسها في واجهة برمجة التطبيقات 30، لذلك بالنسبة للدوال التي تستخدم بشكل مكثّف لتلك واجهات برمجة التطبيقات التي يمكنك تنفيذها على سبيل المثال:

#define REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
#define API_AT_LEAST(x) __builtin_available(android x, *)

void DecodeImageWithImageDecoder() REQUIRES_API(30) {
    // Call any APIs that were introduced in API 30 or newer without guards.
}

void DecodeImageFallback() {
    // Pay the overhead to call the Java APIs via JNI, or use third-party image
    // decoding libraries.
}

void DecodeImage() {
    if (API_AT_LEAST(30)) {
        DecodeImageWithImageDecoder();
    } else {
        DecodeImageFallback();
    }
}

مميزات حماية واجهات برمجة التطبيقات

تتناول لغة Clang كيفية استخدام __builtin_available بشكل خاص. حرفية فقط يعمل if (__builtin_available(...)) (مع أنّه من المحتمل استبداله بالكروم). بالتساوي فلن تنجح العمليات البسيطة مثل if (!__builtin_available(...)) سيتم إصدار تحذير unsupported-availability-guard، بالإضافة إلى unguarded-availability). وقد يتحسن هذا في إصدار مستقبلي من Clang. عرض الإصدار 33161 من LLVM للاطّلاع على مزيد من المعلومات.

لا تنطبق عمليات التحقّق من unguarded-availability إلا على نطاق الدالة حيث تنبؤي. سيصدر Clang التحذير حتى إذا كانت دالة طلب البيانات من واجهة برمجة التطبيقات لا يتم استدعاؤه أبدًا إلا من داخل نطاق محميّ. لتجنب تكرار الحراس في التعليمات البرمجية الخاصة بك، راجِع تجنُّب تكرار حماية واجهة برمجة التطبيقات.

لماذا ليس هذا هو الخيار التلقائي؟

الفرق بين المراجع القوية لواجهة برمجة التطبيقات وواجهة برمجة التطبيقات الضعيفة ما لم يتم استخدامها بشكل صحيح هي أن الطريقة السابقة ستفشل بسرعة وبشكل واضح، في حين أن لن يفشل ذلك إلى أن يتخذ المستخدم إجراءً يؤدي إلى عدم ظهور واجهة برمجة التطبيقات في الاجتماع. عند حدوث ذلك، لن تكون رسالة الخطأ واضحة وقت التجميع "AFoo_bar() غير متاح" فسيكون خطأ segError. مع المراجع القوية، ورسالة الخطأ أكثر وضوحًا، والإخفاق السريع أكثر أمانًا.

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

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

محاذير

يمكن استخدام هذه الميزة مع معظم واجهات برمجة التطبيقات، ولكن هناك بعض الحالات التي لا يتوفّر فيها عملك.

وأقل احتمالاً لحدوث مشكلة هو واجهات برمجة تطبيقات libc الجديدة. على عكس بقية واجهات برمجة تطبيقات Android، التي تتم حمايتها من خلال #if __ANDROID_API__ >= X في العناوين وليس فقط __INTRODUCED_IN(X)، وهو ما يمنع حتى التعريف الضعيف من المشاهدة. ونظرًا لأن أقدم مستوى من دعم NDKs الحديثة على مستوى واجهة برمجة التطبيقات هو r21، فإن العدد الأكبر وهي تتوفر بالفعل واجهات برمجة تطبيقات libc مطلوبة بشكل شائع. تتم إضافة واجهات برمجة تطبيقات libc جديدة إصدار (انظر status.md)، ولكن كلما كانت أحدث، زاد احتمال هي حالة هامشية لن يحتاج إليها سوى عدد قليل من المطورين. ومع ذلك، إذا كنت من هؤلاء المطوّرين، عليك في الوقت الحالي مواصلة استخدام dlsym() للاتصال بهم واجهات برمجة التطبيقات إذا كان minSdkVersion أقدم من واجهة برمجة التطبيقات هذه مشكلة يمكن حلها، ولكن إجراء ذلك ينطوي على خطر تعطيل توافق المصدر لجميع التطبيقات (أي سيفشل الرمز البرمجي الذي يحتوي على polyfills من واجهات برمجة تطبيقات libc في التجميع بسبب سمات availability غير متطابقة في libc والبيانات المحلية)، لذلك ولسنا متأكدين مما إذا سنقوم بإصلاحه أو متى سنقوم بإصلاحه.

الحالة التي يُحتمل أن يواجهها عدد أكبر من مطوّري البرامج هي عندما تتم إضافة المكتبة يحتوي على واجهة برمجة التطبيقات الجديدة أحدث من minSdkVersion. هذه الميزة فقط ويفعّل مراجع رموز ضعيفة؛ لا يوجد شيء يسمى مكتبة ضعيفة المرجع. على سبيل المثال، إذا كان عمر "minSdkVersion" 24 عامًا، يمكنك ربط حسابك. libvulkan.so وإجراء مكالمة محمية إلى vkBindBufferMemory2، لأنّ تتوفّر ميزة libvulkan.so على الأجهزة التي تبدأ بالإصدار 24 من واجهة برمجة التطبيقات. من ناحية أخرى، إذا كانت قيمة minSdkVersion تبلغ 23 عامًا، يجب العودة إلى dlopen وdlsym. لأنّ المكتبة لن تتوفّر على الأجهزة المتوافقة فقط واجهة برمجة التطبيقات 23. لا نعرف حلاً جيدًا لإصلاح هذه الحالة، ولكن على المدى الطويل سيتم حله تلقائيًا لأننا (متى أمكن) لم نعد نسمح واجهات برمجة التطبيقات لإنشاء مكتبات جديدة.

لمؤلفي المكتبة

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

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