इस पेज पर बताया गया है कि आपका ऐप्लिकेशन, ओएस के नए वर्शन पर काम करते समय, ओएस की नई सुविधाओं का इस्तेमाल कैसे कर सकता है. साथ ही, यह भी बताया गया है कि ऐप्लिकेशन को पुराने डिवाइसों के साथ काम करने की सुविधा कैसे मिलती है.
डिफ़ॉल्ट रूप से, आपके ऐप्लिकेशन में NDK API के रेफ़रंस, स्ट्रॉन्ग रेफ़रंस होते हैं. आपकी लाइब्रेरी लोड होने के बाद, Android का डाइनैमिक लोडर इन समस्याओं को हल करने की कोशिश करेगा. अगर सिंबल नहीं मिलते हैं, तो ऐप्लिकेशन बंद हो जाएगा. यह Java के काम करने के तरीके से अलग है. Java में, जब तक एपीआई को कॉल नहीं किया जाता, तब तक कोई अपवाद नहीं दिखता.
इस वजह से, NDK टूल आपको अपने ऐप्लिकेशन के minSdkVersion
से नए एपीआई के लिए, सटीक रेफ़रंस बनाने से रोकेगा. इससे, आपको गलती से ऐसा कोड शिप करने से बचाया जा सकता है जो टेस्टिंग के दौरान काम करता है, लेकिन पुराने डिवाइसों पर लोड नहीं होता (UnsatisfiedLinkError
को System.loadLibrary()
से थ्रो किया जाएगा). दूसरी ओर, आपके ऐप्लिकेशन के minSdkVersion
से नए एपीआई का इस्तेमाल करने वाला कोड लिखना ज़्यादा मुश्किल होता है. इसकी वजह यह है कि आपको सामान्य फ़ंक्शन कॉल के बजाय, dlopen()
और dlsym()
का इस्तेमाल करके एपीआई को कॉल करना होगा.
मज़बूत रेफ़रंस के बजाय, कमज़ोर रेफ़रंस का इस्तेमाल किया जा सकता है. अगर लाइब्रेरी लोड होने के दौरान कोई कमज़ोर रेफ़रंस नहीं मिलता है, तो लाइब्रेरी लोड न होने के बजाय, उस सिंबल का पता nullptr
पर सेट हो जाएगा. हालांकि, अब भी इन कॉल को सुरक्षित तरीके से कॉल नहीं किया जा सकता. हालांकि, जब तक कॉल साइटों को एपीआई के उपलब्ध न होने पर उसे कॉल करने से रोकने के लिए सुरक्षित रखा जाता है, तब तक आपका बाकी कोड चलाया जा सकता है. साथ ही, dlopen()
और dlsym()
का इस्तेमाल किए बिना, एपीआई को सामान्य तरीके से कॉल किया जा सकता है.
कमज़ोर एपीआई रेफ़रंस के लिए, डाइनैमिक लिंकर की ज़्यादा मदद की ज़रूरत नहीं होती. इसलिए, इनका इस्तेमाल Android के किसी भी वर्शन के साथ किया जा सकता है.
अपने बिल्ड में कमज़ोर एपीआई रेफ़रंस चालू करना
CMake
CMake चलाते समय -DANDROID_WEAK_API_DEFS=ON
पास करें. अगर externalNativeBuild
के ज़रिए CMake का इस्तेमाल किया जा रहा है, तो अपने 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
फ़ाइल वाली उसी डायरेक्ट्री में बनाएं. ndk-build के लिए, आपकी build.gradle.kts
(या build.gradle
) फ़ाइल में अन्य बदलाव करने की ज़रूरत नहीं है.
अन्य बिल्ड सिस्टम
अगर CMake या ndk-build का इस्तेमाल नहीं किया जा रहा है, तो अपने बिल्ड सिस्टम के दस्तावेज़ देखें. इससे आपको पता चलेगा कि इस सुविधा को चालू करने का सुझाया गया तरीका मौजूद है या नहीं. अगर आपका बिल्ड सिस्टम, इस विकल्प के साथ काम नहीं करता है, तो इसे चालू करने के लिए, कोड कंपाइल करते समय ये फ़्लैग पास करें:
-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability
पहला, कमज़ोर रेफ़रंस की अनुमति देने के लिए NDK हेडर को कॉन्फ़िगर करता है. दूसरा विकल्प, असुरक्षित एपीआई कॉल के लिए चेतावनी को गड़बड़ी में बदल देता है.
ज़्यादा जानकारी के लिए, बिल्ड सिस्टम के रखरखाव करने वालों के लिए गाइड देखें.
सुरक्षित एपीआई कॉल
इस सुविधा से, नए एपीआई को कॉल करना अपने-आप सुरक्षित नहीं हो जाता. यह सिर्फ़ लोड होने में लगने वाले समय की गड़बड़ी को कॉल के समय की गड़बड़ी में बदलता है. इसका फ़ायदा यह है कि रनटाइम के दौरान उस कॉल को सुरक्षित किया जा सकता है और उसे आसानी से बदला जा सकता है. इसके लिए, किसी अन्य तरीके का इस्तेमाल किया जा सकता है या उपयोगकर्ता को सूचना दी जा सकती है कि ऐप्लिकेशन की वह सुविधा उनके डिवाइस पर उपलब्ध नहीं है. इसके अलावा, उस कोड पाथ को पूरी तरह से हटाया जा सकता है.
अगर आपने बिना सुरक्षा वाले किसी ऐसे एपीआई को कॉल किया है जो आपके ऐप्लिकेशन के minSdkVersion
के लिए उपलब्ध नहीं है, तो Clang चेतावनी (unguarded-availability
) दिखा सकता है. अगर ndk-build या हमारी CMake टूलचेन फ़ाइल का इस्तेमाल किया जा रहा है, तो यह चेतावनी अपने-आप चालू हो जाएगी. साथ ही, इस सुविधा को चालू करने पर, इसे गड़बड़ी के तौर पर दिखाया जाएगा.
यहां कुछ कोड का उदाहरण दिया गया है, जो 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, *)
, android_get_device_api_level()
को कॉल करता है, नतीजे को कैश मेमोरी में सेव करता है, और उसकी तुलना 31
से करता है. 31
वह एपीआई लेवल है जिसने AImageDecoder_resultToString()
को लॉन्च किया था.
__builtin_available
के लिए किस वैल्यू का इस्तेमाल करना है, यह तय करने का सबसे आसान तरीका यह है कि आप गार्ड (या __builtin_available(android 1, *)
के गार्ड) के बिना बने हुए फ़ंक्शन को इस्तेमाल करने की कोशिश करें. इसके बाद, गड़बड़ी के मैसेज में बताए गए निर्देशों का पालन करें.
उदाहरण के लिए, minSdkVersion 24
के साथ AImageDecoder_createFromAAsset()
को बिना सुरक्षा वाला कॉल करने पर, यह दिखेगा:
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(...))
जैसे सामान्य ऑपरेशन भी काम नहीं करेंगे. Clang, unsupported-availability-guard
और unguarded-availability
, दोनों चेतावनियां दिखाएगा. Clang के आने वाले वर्शन में इस समस्या को ठीक किया जा सकता है. ज़्यादा जानकारी के लिए,
LLVM Issue 33161 देखें.
unguarded-availability
की जांच सिर्फ़ उस फ़ंक्शन के दायरे पर लागू होती है जहां उसका इस्तेमाल किया जाता है. Clang चेतावनी तब भी दिखाएगा, जब एपीआई कॉल वाले फ़ंक्शन को सिर्फ़ सुरक्षित दायरे में से कॉल किया गया हो. अपने कोड में गार्ड के दोहराव से बचने के लिए, एपीआई गार्ड के दोहराव से बचना लेख पढ़ें.
यह डिफ़ॉल्ट तौर पर क्यों नहीं सेट होता?
सही तरीके से इस्तेमाल न करने पर, एपीआई के सटीक रेफ़रंस और अस्पष्ट रेफ़रंस के बीच का अंतर यह है कि पहले वाले रेफ़रंस तुरंत और साफ़ तौर पर काम नहीं करेंगे. वहीं, दूसरे रेफ़रंस तब तक काम करेंगे, जब तक उपयोगकर्ता कोई ऐसी कार्रवाई नहीं करता जिससे एपीआई को कॉल किया जा सके. ऐसा होने पर, गड़बड़ी का मैसेज, संकलन के समय "AFoo_bar() उपलब्ध नहीं है" गड़बड़ी के बारे में साफ़ तौर पर नहीं बताएगा. यह एक सेगफ़ॉल्ट होगा. बेहतर रेफ़रंस की मदद से, गड़बड़ी का मैसेज ज़्यादा साफ़ तौर पर दिखता है. साथ ही, फ़ॉलिंग-फ़ास्ट एक सुरक्षित डिफ़ॉल्ट विकल्प है.
यह एक नई सुविधा है. इसलिए, इस व्यवहार को सुरक्षित तरीके से मैनेज करने के लिए, बहुत कम मौजूदा कोड लिखा गया है. तीसरे पक्ष के ऐसे कोड में हमेशा यह समस्या होगी जिसे Android के लिए नहीं लिखा गया है. इसलिए, फ़िलहाल डिफ़ॉल्ट व्यवहार में कोई बदलाव नहीं किया जाएगा.
हमारा सुझाव है कि आप इसका इस्तेमाल करें. हालांकि, इससे समस्याओं का पता लगाना और उन्हें डीबग करना मुश्किल हो जाएगा. इसलिए, आपको इन जोखिमों को जान-बूझकर स्वीकार करना चाहिए, ताकि आपकी जानकारी के बिना व्यवहार में बदलाव न हो.
सीमाएं
यह सुविधा ज़्यादातर एपीआई के लिए काम करती है. हालांकि, कुछ मामलों में यह काम नहीं करती.
libc के नए एपीआई में समस्या होने की संभावना कम होती है. बाकी Android एपीआई के मुकाबले, इन एपीआई को हेडर में #if __ANDROID_API__ >= X
से सुरक्षित किया जाता है, न कि सिर्फ़ __INTRODUCED_IN(X)
से. इससे, कमजोर एलान को भी नहीं देखा जा सकता. NDKs के साथ काम करने वाले सबसे पुराने एपीआई लेवल का वर्शन r21 है. इसलिए, आम तौर पर ज़रूरी libc एपीआई पहले से ही उपलब्ध हैं. हर रिलीज़ में नए libc API जोड़े जाते हैं (status.md देखें). हालांकि, ये जितने नए होंगे, उतनी ही संभावना होगी कि ये एज केस हों और इनकी ज़रूरत कुछ ही डेवलपर को होगी. हालांकि, अगर आप उन डेवलपर में से एक हैं, तो फ़िलहाल आपको उन एपीआई को कॉल करने के लिए dlsym()
का इस्तेमाल करना जारी रखना होगा. ऐसा तब करना होगा, जब आपका minSdkVersion
एपीआई से पुराना हो. इस समस्या को हल किया जा सकता है. हालांकि, ऐसा करने से सभी ऐप्लिकेशन के लिए सोर्स के साथ काम करने की सुविधा बंद हो सकती है. libc और स्थानीय एलान में availability
एट्रिब्यूट के मेल न खाने की वजह से, libc API के polyfills वाले किसी भी कोड को कंपाइल नहीं किया जा सकेगा. इसलिए, हमें नहीं पता कि हम इसे कब ठीक करेंगे.
ज़्यादातर डेवलपर को यह समस्या तब आती है, जब नए एपीआई वाली लाइब्रेरी, आपकी minSdkVersion
से नई हो. यह सुविधा सिर्फ़ कमज़ोर सिंबल रेफ़रंस को चालू करती है. कमज़ोर लाइब्रेरी रेफ़रंस जैसी कोई चीज़ नहीं होती. उदाहरण के लिए, अगर आपका minSdkVersion
24 है, तो libvulkan.so
को लिंक किया जा सकता है और vkBindBufferMemory2
को गार्ड किया गया कॉल किया जा सकता है, क्योंकि libvulkan.so
, एपीआई 24 से शुरू होने वाले डिवाइसों पर उपलब्ध है. दूसरी ओर, अगर आपका minSdkVersion
23 था, तो आपको dlopen
और dlsym
पर वापस जाना होगा, क्योंकि सिर्फ़ API 23 के साथ काम करने वाले डिवाइसों पर लाइब्रेरी मौजूद नहीं होगी. हम इस समस्या को ठीक करने का कोई अच्छा तरीका नहीं जानते. हालांकि, लंबे समय में यह समस्या अपने-आप ठीक हो जाएगी, क्योंकि हम अब (जब भी संभव हो) नए एपीआई को नई लाइब्रेरी बनाने की अनुमति नहीं देते.
लाइब्रेरी के लेखकों के लिए
अगर Android ऐप्लिकेशन में इस्तेमाल करने के लिए कोई लाइब्रेरी बनाई जा रही है, तो आपको अपने सार्वजनिक हेडर में इस सुविधा का इस्तेमाल नहीं करना चाहिए. इसका इस्तेमाल, आउट-ऑफ़-लाइन कोड में सुरक्षित तरीके से किया जा सकता है. हालांकि, अगर आपने अपने हेडर में किसी भी कोड में __builtin_available
का इस्तेमाल किया है, जैसे कि इनलाइन फ़ंक्शन या टेंप्लेट डेफ़िनिशन, तो आपको अपने सभी उपयोगकर्ताओं को यह सुविधा चालू करने के लिए मजबूर करना होगा. हम NDK में इस सुविधा को डिफ़ॉल्ट रूप से चालू नहीं करते. इसलिए, आपको अपने उपभोक्ताओं के लिए यह विकल्प नहीं चुनना चाहिए.
अगर आपको अपने सार्वजनिक हेडर में इस तरह के व्यवहार की ज़रूरत है, तो इसकी जानकारी ज़रूर दें. इससे आपके उपयोगकर्ताओं को यह पता चलेगा कि उन्हें इस सुविधा को चालू करना होगा और उन्हें इसके जोखिमों के बारे में भी पता होगा.