JNI, Java नेटिव इंटरफ़ेस है. यह उस बाइट कोड के लिए तरीका बताता है जिसे Android इकट्ठा करता है नेटिव कोड के साथ इंटरैक्ट करने के लिए, मैनेज किया जा रहा कोड (Java या Kotlin प्रोग्रामिंग भाषाओं में लिखा जाता है) (C/C++ में लिखा गया है). JNI, वेंडर न्यूट्रल है और डाइनैमिक शेयर्ड से कोड लोड करने की सुविधा देता है और कभी-कभी यह जटिल भी काम आ सकता है.
ध्यान दें: क्योंकि Android, Kotlin को ART-फ़्रेंडली बाइटकोड में कंपाइल करता है, की तरह ही, आप इस पेज पर दिए गए निर्देशों को इन दोनों में लागू कर सकते हैं Kotlin और Java प्रोग्रामिंग भाषाओं को JNI आर्किटेक्चर और उससे जुड़ी लागतों के हिसाब से किया जा सकता है. ज़्यादा जानने के लिए, Kotlin और Android लेख पढ़ें.
अगर आप इसके बारे में नहीं जानते हैं, तो ज़्यादा जानकारी के लिए Java नेटिव इंटरफ़ेस की खास बातें ताकि यह जाना जा सके कि JNI कैसे काम करता है और इसमें कौन-कौनसी सुविधाएं उपलब्ध हैं. कुछ सूचनाएं मिल रही हैं उन चीज़ों के बारे में साफ़ तौर पर नहीं बताया गया है जो ताकि आपको अगले कुछ सेक्शन आसानी से मिलें.
वैश्विक JNI संदर्भ ब्राउज़ करने और यह देखने के लिए कि वैश्विक JNI संदर्भ कहां बनाए और हटाए जाते हैं, इसका उपयोग करें मेमोरी प्रोफ़ाइलर में JNI हीप व्यू Android Studio 3.2 और इसके बाद के वर्शन में.
सामान्य सलाह
अपनी JNI लेयर के फ़ुटप्रिंट को कम से कम करने की कोशिश करें. यहां कई डाइमेंशन का इस्तेमाल किया जा सकता है. आपके JNI समाधान को इन दिशा-निर्देशों का पालन करना चाहिए. ये दिशा-निर्देश, यहां दिए गए हैं. सबसे ज़रूरी दिशा-निर्देश सबसे ऊपर दिए गए हैं:
- जेएनआई लेयर में संसाधनों की मार्शलिंग को कम से कम करें. JNI लेयर में मार्शल करने की लागत कम नहीं होती. ऐसा इंटरफ़ेस डिज़ाइन करें जिससे आपको कम से कम डेटा मार्शल करना पड़े और जिसे मार्शल करने की फ़्रीक्वेंसी भी कम हो.
- जब भी हो सके, मैनेज की जा सकने वाली प्रोग्रामिंग भाषा और C++ में लिखे गए कोड के बीच असाइनोक्रोनस कम्यूनिकेशन से बचें. इससे आपके JNI इंटरफ़ेस का रखरखाव आसान हो जाएगा. आम तौर पर, यूज़र इंटरफ़ेस (यूआई) के अपडेट को आसानी से सिंक किया जा सकता है. इसके लिए, यूआई के अपडेट को उसी भाषा में रखें जिस भाषा में यूआई बनाया गया है. उदाहरण के लिए, JNI के ज़रिए Java कोड में UI थ्रेड से C++ फ़ंक्शन को कॉल करने के बजाय, Java प्रोग्रामिंग भाषा में दो थ्रेड के बीच कॉलबैक करना बेहतर होता है. इनमें से एक थ्रेड, ब्लॉकिंग C++ कॉल करता है और ब्लॉकिंग कॉल पूरा होने पर, UI थ्रेड को सूचना देता है.
- ऐसे थ्रेड की संख्या कम से कम करें जिन्हें JNI से छूना या छूना हो. अगर आपको Java और C++, दोनों भाषाओं में थ्रेड पूल का इस्तेमाल करना है, तो JNI अलग-अलग वर्कर के थ्रेड के बजाय, पूल के मालिकों के बीच बातचीत करने की सुविधा मिलती है.
- अपने इंटरफ़ेस कोड को आसानी से पहचाने जा सकने वाले C++ और Java सोर्स में रखें जगहों की जानकारी इकट्ठा की जा सकती है, ताकि आने वाले समय में इस प्रक्रिया में मदद मिल सके. ज़रूरत के हिसाब से, JNI ऑटो-जनरेशन वाली लाइब्रेरी का इस्तेमाल करें.
JavaVM और JNIEnv
JNI, "JavaVM" के दो मुख्य डेटा स्ट्रक्चर के बारे में बताता है और "JNIEnv" है. ये दोनों फ़ंक्शन टेबल के पॉइंटर के पॉइंटर हैं. (C++ वर्शन में, वे किसी फ़ंक्शन टेबल का पॉइंटर और हर JNI फ़ंक्शन के लिए एक सदस्य फ़ंक्शन, जो से टेबल.) JavaVM, "इंवोकेशन इंटरफ़ेस" फ़ंक्शन उपलब्ध कराता है. इनकी मदद से, JavaVM बनाया और नष्ट किया जा सकता है. सिद्धांत रूप से, हर प्रोसेस में कई JavaVM हो सकते हैं, लेकिन Android सिर्फ़ एक की अनुमति देता है.
JNIEnv में जेएनआई के ज़्यादातर फ़ंक्शन दिए जाते हैं. आपके सभी नेटिव फ़ंक्शन को JNIEnv के रूप में
पहला तर्क, @CriticalNative
तरीकों को छोड़कर,
तेज़ स्थानीय कॉल देखें.
JNIEnv का इस्तेमाल, थ्रेड-लोकल स्टोरेज के लिए किया जाता है. इस वजह से, थ्रेड के बीच JNIEnv शेयर नहीं किया जा सकता.
अगर किसी कोड के पास अपना JNIEnv पाने का कोई दूसरा तरीका नहीं है, तो आपको JavaVM शेयर करना चाहिए. साथ ही, थ्रेड का JNIEnv ढूंढने के लिए GetEnv
का इस्तेमाल करना चाहिए. (यह मानते हुए कि उसमें एक है; नीचे AttachCurrentThread
देखें.)
JNIEnv और JavaVM के C एलान, C++ एलान से अलग होते हैं. "jni.h"
शामिल फ़ाइल अलग-अलग टाइपडिफ़ उपलब्ध कराती है
होता है कि वह C में शामिल है या C++ में. इस कारण से यह एक गलत विचार है कि
दोनों भाषाओं में शामिल हेडर फ़ाइलों में JNIEnv आर्ग्युमेंट शामिल करें. (दूसरे शब्दों में कहें, तो अगर आपकी हेडर फ़ाइल में #ifdef __cplusplus
की ज़रूरत है, तो हो सकता है कि आपको कुछ और काम करना पड़े. ऐसा तब होगा, जब उस हेडर में JNIEnv का रेफ़रंस दिया गया हो.)
थ्रेड
सभी थ्रेड, Linux थ्रेड होते हैं. इन्हें कर्नेल के ज़रिए शेड्यूल किया जाता है. आम तौर पर, इन्हें मैनेज किए जा रहे कोड (Thread.start()
का इस्तेमाल करके) से शुरू किया जाता है. हालांकि, इन्हें कहीं और भी बनाया जा सकता है और फिर JavaVM
से अटैच किया जा सकता है. इसके लिए
उदाहरण के लिए, pthread_create()
या std::thread
से शुरू हुई थ्रेड
को AttachCurrentThread()
का इस्तेमाल करके अटैच किया जा सकता है या
AttachCurrentThreadAsDaemon()
फ़ंक्शन. जब तक कोई थ्रेड अटैच नहीं किया जाता, तब तक उसके पास JNIEnv नहीं होता और वह JNI कॉल नहीं कर सकता.
आम तौर पर, Thread.start()
का इस्तेमाल करके ऐसी थ्रेड बनाई जा सकती है जिसमें Java कोड को कॉल करना ज़रूरी हो. इससे यह पक्का होगा कि आपके पास स्टैक के लिए ज़रूरत के मुताबिक जगह है, आप सही ThreadGroup
में हैं, और आपने अपने Java कोड के लिए उसी ClassLoader
का इस्तेमाल किया है. Java में डीबग करने के लिए, इससे थ्रेड का नाम सेट करना भी ज़्यादा आसान है
नेटिव कोड (pthread_setname_np()
देखें, अगर आपके पास pthread_t
है या
thread_t
और std::thread::native_handle()
, अगर आपके पास
std::thread
और pthread_t
चाहिए).
नेटिव तौर पर बनाई गई थ्रेड को अटैच करने पर, java.lang.Thread
ऑब्जेक्ट बन जाता है और उसे "main" ThreadGroup
में जोड़ दिया जाता है. इससे, वह डीबगर को दिखने लगता है. AttachCurrentThread()
को कॉल किया जा रहा है
जो पहले से अटैच किए हुए थ्रेड पर नो-ऑप होता है.
Android, नेटिव कोड को एक्ज़ीक्यूट करने वाले थ्रेड को निलंबित नहीं करता. अगर आपने गै़र-ज़रूरी डेटा इकट्ठा करने की प्रोसेस जारी है या डीबगर ने अनुरोध है, तो अगली बार JNI कॉल करने पर Android, थ्रेड को रोक देगा.
JNI के ज़रिए अटैच किए गए थ्रेड को बाहर निकलने से पहले, DetachCurrentThread()
को कॉल करना होगा.
अगर सीधे कोडिंग करना अजीब है, तो Android 2.0 (Eclair) और इसके बाद के वर्शन वाले डिवाइसों पर
विध्वंसक के बारे में बताने के लिए pthread_key_create()
का इस्तेमाल कर सकता है
फ़ंक्शन है जिसे थ्रेड के बाहर निकलने से पहले कॉल किया जाएगा और
वहां से DetachCurrentThread()
को कॉल करो. (इसका इस्तेमाल करें
JNIEnv को सेव करने के लिए pthread_setspecific()
की कुंजी
थ्रेड-लोकल-स्टोरेज; इस तरह से इसे आपके विनाशक में पास कर दिया जाएगा
तर्क से तय करें.)
jclass, jmethodID, और jfieldID
अगर आपको नेटिव कोड से किसी ऑब्जेक्ट के फ़ील्ड को ऐक्सेस करना है, तो ये काम करें:
-
FindClass
वाली क्लास के लिए क्लास ऑब्जेक्ट रेफ़रंस पाएं -
GetFieldID
वाले फ़ील्ड का फ़ील्ड आईडी पाना - फ़ील्ड का कॉन्टेंट पाने के लिए, सही फ़ंक्शन का इस्तेमाल करें, जैसे कि
GetIntField
इसी तरह, किसी मेथड को कॉल करने के लिए, आपको पहले क्लास ऑब्जेक्ट का रेफ़रंस मिलेगा. इसके बाद, मेथड का आईडी मिलेगा. आईडी अक्सर इंटरनल रनटाइम डेटा स्ट्रक्चर के लिए पॉइंटर. उन्हें खोजने के लिए, कई स्ट्रिंग की तुलना की जा सकती है. हालांकि, इनके मिल जाने के बाद, फ़ील्ड पाने या तरीका शुरू करने के लिए असल कॉल बहुत तेज़ी से होता है.
अगर परफ़ॉर्मेंस अहम है, तो वैल्यू को एक बार देखना और नतीजों को अपने नेटिव कोड में कैश मेमोरी में सेव करना मददगार होता है. हर प्रोसेस में एक JavaVM हो सकता है. इसलिए, यह सही है में स्टोर करने की ज़रूरत नहीं होती.
क्लास के रेफ़रंस, फ़ील्ड आईडी, और मेथड के आईडी तब तक मान्य होते हैं, जब तक क्लास को अनलोड नहीं किया जाता. क्लास
सिर्फ़ तब अनलोड किए जाते हैं, जब ClassLoader से जुड़ी सभी क्लास का कचरा इकट्ठा किया जा सकता हो,
ऐसा करना बहुत कम होता है, लेकिन Android में इसे मुमकिन नहीं माना जा सकता. हालांकि, ध्यान रखें कि
jclass
एक क्लास रेफ़रंस है और कॉल से सुरक्षित होना चाहिए
NewGlobalRef
के लिए (अगला सेक्शन देखें).
अगर आपको क्लास लोड होने पर, आईडी को कैश मेमोरी में सेव करना है और उन्हें अपने-आप फिर से कैश मेमोरी में सेव करना है अगर कभी भी क्लास को अनलोड करके फिर से लोड किया जाता है, तो इसे शुरू करने का सही तरीका आईडी का इस्तेमाल करके, सही क्लास में कोड का एक हिस्सा जोड़ा जाता है, जो ऐसा दिखता है:
Kotlin
companion object { /* * We use a static class initializer to allow the native code to cache some * field offsets. This native function looks up and caches interesting * class/field/method IDs. Throws on failure. */ private external fun nativeInit() init { nativeInit() } }
Java
/* * We use a class initializer to allow the native code to cache some * field offsets. This native function looks up and caches interesting * class/field/method IDs. Throws on failure. */ private static native void nativeInit(); static { nativeInit(); }
अपने C/C++ कोड में ऐसा nativeClassInit
तरीका बनाएं जो आईडी लुकअप करता हो. कोड
क्लास शुरू होने पर, एक बार एक्ज़ीक्यूट किया जाएगा. अगर कक्षा को कभी अनलोड करके फिर से लोड किया जाता है, तो उसे फिर से चलाया जाएगा.
स्थानीय और ग्लोबल रेफ़रंस
किसी नेटिव मेथड में पास किया गया हर आर्ग्युमेंट और JNI फ़ंक्शन से मिलने वाला ज़्यादातर ऑब्जेक्ट, "लोकल रेफ़रंस" होता है. इसका मतलब है कि यह मौजूदा थ्रेड में मौजूदा नेटिव तरीके के दौरान मान्य है. भले ही, नेटिव तरीके के बाद ऑब्जेक्ट पर निर्भर रहता है रिटर्न, रेफ़रंस मान्य नहीं है.
यह jobject
की सभी सब-क्लास पर लागू होता है. इनमें ये सब-क्लास भी शामिल हैं
jclass
, jstring
, और jarray
.
(जब 'बड़े किए गए JNI जांच' चालू होंगे, तो रनटाइम आपको रेफ़रंस के गलत इस्तेमाल के बारे में चेतावनी देगा.)
गैर-स्थानीय संदर्भ पाने का एकमात्र तरीका फ़ंक्शन के ज़रिए है
NewGlobalRef
और NewWeakGlobalRef
.
अगर आपको किसी रेफ़रंस को लंबे समय तक सेव रखना है, तो आपको "ग्लोबल" रेफ़रंस का इस्तेमाल करना होगा. NewGlobalRef
फ़ंक्शन
स्थानीय संदर्भ को तर्क के रूप में दिखाता है और वैश्विक संदर्भ देता है.
यह गारंटी है कि आपके कॉल करने तक ग्लोबल रेफ़रंस मान्य होगा
DeleteGlobalRef
.
आम तौर पर, इस पैटर्न का इस्तेमाल तब किया जाता है, जब FindClass
से मिले jclass को कैश मेमोरी में सेव किया जाता है. उदाहरण के लिए:
jclass localClass = env->FindClass("MyClass"); jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
JNI के सभी तरीके, लोकल और ग्लोबल, दोनों रेफ़रंस को आर्ग्युमेंट के तौर पर स्वीकार करते हैं.
एक ही ऑब्जेक्ट के रेफ़रंस के लिए, अलग-अलग वैल्यू हो सकती हैं.
उदाहरण के लिए, लगातार किए जाने वाले कॉल से लेकर,
एक ही ऑब्जेक्ट के लिए, NewGlobalRef
अलग हो सकता है.
यह देखने के लिए कि क्या दो संदर्भ एक ही ऑब्जेक्ट को रेफ़र करते हैं,
तो आपको IsSameObject
फ़ंक्शन का इस्तेमाल करना होगा. नेटिव कोड में, रेफ़रंस की तुलना कभी भी ==
से न करें.
इसका एक नतीजा यह होता है कि
यह नहीं माना जाना चाहिए कि ऑब्जेक्ट के रेफ़रंस स्थिर या यूनीक हैं
नेटिव कोड में. किसी ऑब्जेक्ट को दिखाने वाली वैल्यू अलग हो सकती है
किसी विधि के इस्तेमाल से अगले चरण पर जाती है, और संभव है कि दो
लगातार होने वाले कॉल पर, अलग-अलग ऑब्जेक्ट की एक ही वैल्यू हो सकती है. jobject
वैल्यू को बटन के तौर पर इस्तेमाल न करें.
प्रोग्रामर को स्थानीय रेफ़रंस "ज़्यादा से ज़्यादा नहीं" देने चाहिए. व्यावहारिक तौर पर इसका मतलब है कि
कि अगर आप बड़ी संख्या में स्थानीय संदर्भ बना रहे हैं, तो हो सकता है कि जब आप लेखों की एक सूची
ऑब्जेक्ट है, तो आपको उन्हें मैन्युअल रूप से
JNI को ऐसा करने देने के बजाय DeleteLocalRef
. इसे सिर्फ़ 16 लोकल रेफ़रंस के लिए स्लॉट रिज़र्व करने के लिए लागू करना ज़रूरी है. इसलिए, अगर आपको इससे ज़्यादा की ज़रूरत है, तो आपको ज़रूरत के हिसाब से स्लॉट मिटाने होंगे या ज़्यादा स्लॉट रिज़र्व करने के लिए EnsureLocalCapacity
/PushLocalFrame
का इस्तेमाल करना होगा.
ध्यान दें कि jfieldID
और jmethodID
, ऑब्जेक्ट रेफ़रंस नहीं, बल्कि अपारदर्शी टाइप हैं. इन्हें NewGlobalRef
में पास नहीं किया जाना चाहिए. रॉ डेटा
GetStringUTFChars
जैसे फ़ंक्शन से मिले पॉइंटर
और GetByteArrayElements
भी ऑब्जेक्ट नहीं हैं. (उन्हें पास किया जा सकता है
और ये मेल खाने वाले रिलीज़ कॉल तक मान्य रहते हैं.)
एक असामान्य मामले के बारे में अलग से बताना ज़रूरी है. अगर आपने नेटिव विज्ञापन के लिए
AttachCurrentThread
के साथ थ्रेड, जो कोड चलाया जा रहा है
जब तक थ्रेड अलग नहीं हो जाता, तब तक अपने-आप लोकल रेफ़रंस कभी भी नहीं लगते. कोई भी स्थानीय
आपकी बनाई पहचान फ़ाइलों को मैन्युअल रूप से मिटाना होगा. आम तौर पर, किसी भी नेटिव कोड को लूप में लोकल रेफ़रंस बनाने के लिए, मैन्युअल तरीके से कुछ डेटा मिटाना पड़ता है.
ग्लोबल रेफ़रंस का इस्तेमाल करते समय सावधानी बरतें. ग्लोबल रेफ़रंस का इस्तेमाल करना ज़रूरी हो सकता है. हालांकि, इन्हें डीबग करना मुश्किल होता है. साथ ही, इनकी वजह से मेमोरी के गलत व्यवहार का पता लगाना मुश्किल हो सकता है. बाकी सब कुछ एक जैसा होने पर, कम ग्लोबल रेफ़रंस वाला समाधान बेहतर होता है.
UTF-8 और UTF-16 स्ट्रिंग
Java की प्रोग्रामिंग भाषा में, UTF-16 का इस्तेमाल किया जाता है. सुविधा के लिए, JNI ऐसे तरीके उपलब्ध कराता है जो UTF-8 में भी बदलाव किया गया है. कॉन्टेंट बनाने कोड में बदलने का बदलाव, C कोड के लिए फ़ायदेमंद है, क्योंकि यह \u0000 को 0x00 के बजाय 0xc0 0x80 के तौर पर कोड में बदलता है. इसकी सबसे अच्छी बात यह है कि C-style वाली शून्य पर खत्म होने वाली स्ट्रिंग का इस्तेमाल किया जा सकता है. ये स्ट्रिंग, libc के स्टैंडर्ड स्ट्रिंग फ़ंक्शन के साथ इस्तेमाल करने के लिए सही होती हैं. समस्या यह है कि आप इसे पास नहीं कर सकते आर्बिट्रेरी UTF-8 डेटा को JNI के लिए अपलोड कर सकता है और उम्मीद करता है कि यह ठीक से काम करेगा.
String
का UTF-16 फ़ॉर्मैट पाने के लिए, GetStringChars
का इस्तेमाल करें.
ध्यान दें कि UTF-16 स्ट्रिंग को खत्म नहीं किया जाता है और इसमें \u0000 जोड़ा जा सकता है,
आपको स्ट्रिंग की लंबाई के साथ-साथ jchar पॉइंटर पर बने रहना होगा.
अपनी Get
स्ट्रिंग को Release
करना न भूलें. स्ट्रिंग फ़ंक्शन, jchar*
या jbyte*
दिखाते हैं. ये स्थानीय रेफ़रंस के बजाय, प्राइमटिव डेटा के C-स्टाइल पॉइंटर होते हैं. जब तक Release
को कॉल नहीं किया जाता, तब तक इनके मान्य होने की गारंटी होती है. इसका मतलब है कि नेटिव तरीके के रिटर्न होने पर, इन्हें रिलीज़ नहीं किया जाता.
NewStringUTF फ़ंक्शन में पास किया गया डेटा, बदले गए UTF-8 फ़ॉर्मैट में होना चाहिए. आम तौर पर, फ़ाइल या नेटवर्क स्ट्रीम से वर्ण डेटा पढ़ने और उसे फ़िल्टर किए बिना NewStringUTF
को देने पर गड़बड़ी होती है.
जब तक आपको यह पता न हो कि डेटा मान्य MUTF-8 (या 7-बिट ASCII, जो एक संगत सबसेट है) है,
आपको अमान्य वर्णों को हटाना होगा या उन्हें सही UTF-8 फ़ॉर्म में बदलना होगा.
अगर ऐसा नहीं किया, तो हो सकता है कि UTF-16 कन्वर्ज़न से अनचाहे नतीजे मिल जाएं.
CheckJNI, एमुलेटर के लिए डिफ़ॉल्ट रूप से चालू रहता है. यह स्ट्रिंग को स्कैन करता है और अमान्य इनपुट मिलने पर वीएम को बंद कर देता है.
Android 8 से पहले, UTF-16 स्ट्रिंग के साथ काम करना आम तौर पर तेज़ होता था. इसकी वजह यह थी कि Android को GetStringChars
में कॉपी करने की ज़रूरत नहीं होती थी, जबकि GetStringUTFChars
के लिए, UTF-8 में बदलाव और एलोकेशन की ज़रूरत होती थी.
Android 8 ने String
के दिखाए जाने के तरीके में बदलाव किया है, ताकि मेमोरी बचाने के लिए ASCII स्ट्रिंग के लिए हर वर्ण के लिए 8 बिट का इस्तेमाल किया जा सके. साथ ही, मूविंग गै़रबेज कलेक्टर का इस्तेमाल शुरू किया है. ये सुविधाएं उन मामलों की संख्या को बहुत कम कर देती हैं जहां ART
String
डेटा की कॉपी बनाए बिना, उसके लिए पॉइंटर उपलब्ध करा सकता है. यहां तक कि
GetStringCritical
के लिए. हालांकि, अगर कोड से प्रोसेस की गई ज़्यादातर स्ट्रिंग छोटी हैं, तो ज़्यादातर मामलों में स्टैक से एलोकेट किए गए बफ़र और GetStringRegion
या GetStringUTFRegion
का इस्तेमाल करके, एलोकेशन और डिएलोकेशन से बचा जा सकता है. उदाहरण के लिए:
constexpr size_t kStackBufferSize = 64u; jchar stack_buffer[kStackBufferSize]; std::unique_ptr<jchar[]> heap_buffer; jchar* buffer = stack_buffer; jsize length = env->GetStringLength(str); if (length > kStackBufferSize) { heap_buffer.reset(new jchar[length]); buffer = heap_buffer.get(); } env->GetStringRegion(str, 0, length, buffer); process_data(buffer, length);
प्रिमिटिव अरे
JNI, अरे ऑब्जेक्ट के कॉन्टेंट को ऐक्सेस करने के लिए फ़ंक्शन उपलब्ध कराता है. ऑब्जेक्ट के कलेक्शन को एक बार में एक एंट्री को ऐक्सेस करना ज़रूरी है. वहीं, प्राइमिटिव के कलेक्शन को सीधे पढ़ा और लिखा जा सकता है, जैसे कि उन्हें C में एलान किया गया हो.
बिना किसी रुकावट के इंटरफ़ेस को ज़्यादा से ज़्यादा कारगर बनाना
VM को लागू करने के बाद, Get<PrimitiveType>ArrayElements
कॉल के परिवार की मदद से रनटाइम या तो असल एलिमेंट का पॉइंटर दिखा पाता है या
कुछ मेमोरी बांटकर उसकी एक कॉपी बनाओ. दोनों ही मामलों में, जब तक संबंधित Release
कॉल जारी नहीं किया जाता, तब तक यह पक्का किया जाता है कि लौटाए गए रॉ पॉइंटर मान्य हों. इसका मतलब है कि अगर डेटा कॉपी नहीं किया गया है, तो ऐरे ऑब्जेक्ट को पिन कर दिया जाएगा और हेप को कॉम्पैक्ट करने के हिस्से के तौर पर उसे दूसरी जगह नहीं भेजा जा सकता.
आपको हर कलेक्शन को Release
करना होगा और Get
करना होगा. साथ ही, अगर Get
कॉल नहीं हो पाता है, तो आपको यह पक्का करना होगा कि आपका कोड, शून्य को Release
करने की कोशिश न करे
पॉइंटर बाद में छोड़ें.
isCopy
आर्ग्युमेंट के लिए कोई ऐसा पॉइंटर पास करके यह पता लगाया जा सकता है कि डेटा कॉपी किया गया है या नहीं. यह शायद ही कभी काम का हो.
Release
कॉल में mode
तर्क दिया जाता है, जो
वैल्यू, तीनों में से कोई एक हो. रनटाइम में की जाने वाली कार्रवाइयां, इन बातों पर निर्भर करती हैं
भले ही, इससे असल डेटा का पॉइंटर या उसकी कॉपी दिखे:
0
- असल: ऐरे ऑब्जेक्ट को अनपिन किया गया है.
- कॉपी करें: डेटा को फिर से कॉपी कर लिया जाता है. कॉपी के साथ बफ़र फ़्री हो जाता है.
JNI_COMMIT
- असल में: कुछ नहीं होता.
- कॉपी करें: डेटा को फिर से कॉपी कर लिया जाता है. कॉपी वाला बफ़र फ़्री नहीं है.
JNI_ABORT
- असल: ऐरे ऑब्जेक्ट को अनपिन किया गया है. पुरानी सूचनाएं लिखा गया कॉन्टेंट रद्द नहीं किया जाता.
- कॉपी: कॉपी किए गए बफ़र को खाली कर दिया जाता है. इसमें किए गए सभी बदलाव मिट जाते हैं.
isCopy
फ़्लैग की जांच करने की एक वजह यह भी जान लें कि
आपको JNI_COMMIT
के साथ Release
को कॉल करना है
के बीच में बदलाव कर सकते हैं — अगर आप
ऐसे कोड को बदलता है और उसे एक्ज़ीक्यूट करता है जो अरे के कॉन्टेंट का इस्तेमाल करता है, तो आपको
यह कर पाएं
'नो-ऑप' सुविधा बंद न करें. फ़्लैग की जांच करने की एक और वजह यह है कि JNI_ABORT
को बेहतर तरीके से मैनेज किया जा सके. उदाहरण के लिए, हो सकता है कि
किसी अरे को देखने, उसकी जगह पर बदलाव करने, टुकड़ों को अन्य फ़ंक्शन में पास करने, और
फिर बदलावों को खारिज करें. यदि आप जानते हैं कि JNI
तो आपको दूसरा "बदलाव करने लायक" वीडियो बनाने की ज़रूरत नहीं है कॉपी करें. अगर JNI आपको ओरिजनल पास कर रहा है, तो आपको अपनी कॉपी बनानी होगी.
यह एक सामान्य गलती है (उदाहरण के तौर पर कोड में दोहराई गई) यह मानकर चलते हैं कि आप Release
कॉल को स्किप कर सकते हैं, अगर
*isCopy
गलत है. ऐसा नहीं है. अगर कोई कॉपी बफ़र नहीं होता
तो ओरिजनल मेमोरी को पिन करके रखा जाना चाहिए.
कचरा हटाने के लिए इस्तेमाल किया जाता है.
यह भी ध्यान रखें कि JNI_COMMIT
फ़्लैग, अरे को रिलीज़ नहीं करता है,
और आपको किसी दूसरे फ़्लैग के साथ Release
पर दोबारा कॉल करना होगा
आखिरकार.
क्षेत्र के हिसाब से कॉल
Get<Type>ArrayElements
जैसे कॉल का दूसरा विकल्प मौजूद है
और GetStringChars
, जो आपकी ज़रूरत के हिसाब से काफ़ी मददगार हो सकते हैं
करना है डेटा को अंदर या बाहर कॉपी करना. इसके लिए, इन्हें आज़माएं:
jbyte* data = env->GetByteArrayElements(array, NULL); if (data != NULL) { memcpy(buffer, data, len); env->ReleaseByteArrayElements(array, data, JNI_ABORT); }
यह अरे को पहचान लेता है और पहले len
बाइट को कॉपी करता है
इसमें से एलिमेंट शामिल करता है और फिर अरे को रिलीज़ कर देता है. लागू करने के तरीके के आधार पर, Get
कॉल ऐरे के कॉन्टेंट को पिन करेगा या कॉपी करेगा.
कोड, डेटा को कॉपी करता है (शायद दूसरी बार) और फिर Release
को कॉल करता है. इस मामले में, JNI_ABORT
यह पक्का करता है कि तीसरी कॉपी न हो.
एक ही काम को ज़्यादा आसानी से पूरा किया जा सकता है:
env->GetByteArrayRegion(array, 0, len, buffer);
इसके कई फ़ायदे हैं:
- इसके लिए, दो के बजाय एक JNI कॉल की ज़रूरत होती है. इससे ओवरहेड कम हो जाता है.
- पिन करने या अतिरिक्त डेटा कॉपी की ज़रूरत नहीं होती.
- प्रोग्रामर की गड़बड़ी का जोखिम कम करता है — भूलने का कोई जोखिम नहीं
किसी गड़बड़ी के बाद,
Release
को कॉल करने के लिए.
इसी तरह, डेटा को किसी ऐरे में कॉपी करने के लिए Set<Type>ArrayRegion
कॉल का इस्तेमाल किया जा सकता है. साथ ही, String
से वर्ण कॉपी करने के लिए GetStringRegion
या GetStringUTFRegion
का इस्तेमाल किया जा सकता है.
अपवाद
जब तक कोई अपवाद लागू न हो, आपको जेएनआई फ़ंक्शन को कॉल नहीं करना चाहिए.
आपके कोड को अपवाद (फ़ंक्शन की रिटर्न वैल्यू,
ExceptionCheck
या ExceptionOccurred
के ज़रिए) का पता चलना चाहिए और उसे रिटर्न करना चाहिए या फिर अपवाद को हटाकर उसे मैनेज करना चाहिए.
अपवाद के तौर पर आप सिर्फ़ एक JNI फ़ंक्शन जिसे कॉल कर सकते हैं लंबित हैं:
DeleteGlobalRef
DeleteLocalRef
DeleteWeakGlobalRef
ExceptionCheck
ExceptionClear
ExceptionDescribe
ExceptionOccurred
MonitorExit
PopLocalFrame
PushLocalFrame
Release<PrimitiveType>ArrayElements
ReleasePrimitiveArrayCritical
ReleaseStringChars
ReleaseStringCritical
ReleaseStringUTFChars
कई JNI कॉल अपवाद हो सकते हैं, लेकिन अक्सर वे एक आसान तरीका देते हैं
गड़बड़ी की जांच की जा रही है. उदाहरण के लिए, अगर NewString
, शून्य के अलावा कोई वैल्यू दिखाता है, तो आपको अपवाद की जांच करने की ज़रूरत नहीं है. हालांकि, अगर किसी CallObjectMethod
जैसे फ़ंक्शन का इस्तेमाल करके कोई तरीका कॉल किया जाता है, तो आपको हमेशा अपवाद की जांच करनी चाहिए. ऐसा इसलिए, क्योंकि अगर कोई अपवाद हुआ है, तो रिटर्न वैल्यू मान्य नहीं होगी.
ध्यान दें कि मैनेज किए जा रहे कोड से मिलने वाले अपवाद, नेटिव स्टैक फ़्रेम को अनवाइंड नहीं करते. (और C++ अपवादों का, आम तौर पर Android पर इस्तेमाल न करने की सलाह दी जाती है. इन्हें
यह C++ कोड से, मैनेज किए जा रहे कोड में JNI की ट्रांज़िशन सीमा पर पहुंच जाता है.)
JNI Throw
और ThrowNew
के निर्देश
मौजूदा थ्रेड में अपवाद पॉइंटर सेट करें. 'मैनेज किया जा रहा है' पेज पर वापस जाने पर
नेटिव कोड से, अपवाद को नोट किया जाएगा और सही तरीके से हैंडल किया जाएगा.
स्थानीय कोड "कैच" कर सकता है ExceptionCheck
पर कॉल करके या
ExceptionOccurred
और इसकी मदद से मिटाएं
ExceptionClear
. हमेशा की तरह,
अपवादों को हैंडल किए बिना छोड़ देने से समस्याएं हो सकती हैं.
Throwable
ऑब्जेक्ट में बदलाव करने के लिए, पहले से कोई फ़ंक्शन मौजूद नहीं है
है, इसलिए यदि आप अपवाद स्ट्रिंग प्राप्त करना (जैसे) प्राप्त करना चाहते हैं, तो आपको
Throwable
क्लास ढूंढने के लिए, इसका तरीका आईडी खोजें
getMessage "()Ljava/lang/String;"
, उसे शुरू करें और अगर नतीजा
क्या GetStringUTFChars
का इस्तेमाल करके कुछ हासिल किया जा सकता है
printf(3)
या इसके बराबर की कोई प्रतिक्रिया नहीं होनी चाहिए.
विस्तारित जांच
JNI, गड़बड़ी की बहुत कम जांच करता है. आम तौर पर, गड़बड़ियों की वजह से ऐप्लिकेशन क्रैश हो जाता है. Android में CheckJNI नाम का मोड भी है, जिसमें JavaVM और JNIEnv फ़ंक्शन टेबल पॉइंटर को फ़ंक्शन की टेबल पर स्विच किया जाता है. ये फ़ंक्शन, स्टैंडर्ड इंप्लिमेंटेशन का इस्तेमाल करने से पहले, लंबे समय तक जांच करने वाले फ़ंक्शन की टेबल पर स्विच करते हैं.
अन्य जांचों में ये शामिल हैं:
- सरणियां: नेगेटिव साइज़ का अरे तय करने की कोशिश की जा रही है.
- खराब पॉइंटर: JNI कॉल में खराब jarray/jclass/jobject/jstring पास करना या खाली न होने वाले आर्ग्युमेंट के साथ, JNI कॉल पर शून्य पॉइंटर भेजना.
- क्लास के नाम: JNI कॉल में, “java/lang/String” स्टाइल के अलावा कोई भी क्लास का नाम पास करना.
- ज़रूरी कॉल: किसी “गंभीर” शब्द और उससे जुड़ी रिलीज़ के बीच JNI कॉल करना.
- डायरेक्ट बाइटबफ़र:
NewDirectByteBuffer
में गलत आर्ग्युमेंट पास करना. - अपवाद: अपवाद के बाकी होने पर JNI कॉल करना.
- JNIEnv*s: गलत थ्रेड से JNIEnv* का इस्तेमाल करना.
- jfieldIDs: शून्य jfieldID का इस्तेमाल करके या किसी फ़ील्ड को गलत टाइप की वैल्यू पर सेट करने के लिए jfieldID का इस्तेमाल करना (जैसे, स्ट्रिंग फ़ील्ड को Stringबिल्डर असाइन करने की कोशिश करना) या इंस्टेंस फ़ील्ड सेट करने के लिए jfieldID का इस्तेमाल करना या इंस्टेंस फ़ील्ड के लिए jfieldID का इस्तेमाल करना या एक क्लास के jfieldID का इस्तेमाल दूसरी क्लास के इंस्टेंस वाले इंस्टेंस से करना.
- jmethodIDs:
Call*Method
JNI कॉल करते समय गलत jmethodID का इस्तेमाल करना: गलत रिटर्न टाइप, स्टैटिक/नॉन-स्टैटिक मैच, 'इस' (नॉन-स्टैटिक कॉल के लिए) के लिए गलत टाइप या स्टैटिक कॉल के लिए गलत क्लास (स्टैटिक कॉल). - पहचान फ़ाइलें: गलत पहचान फ़ाइल पर
DeleteGlobalRef
/DeleteLocalRef
का इस्तेमाल किया गया है. - रिलीज़ मोड: रिलीज़ कॉल में खराब रिलीज़ मोड पास करना (
0
,JNI_ABORT
याJNI_COMMIT
के अलावा कोई और). - सुरक्षा का टाइप: अपने नेटिव तरीके से काम न करने वाला टाइप लौटाना (स्ट्रिंग लौटाने के लिए बताए गए तरीके से StringBuilder को रिटर्न करना) के लिए टाइप करना. जैसे,
- UTF-8: बदले गए UTF-8 बाइट क्रम को JNI कॉल में पास करना.
(तब भी, तरीकों और फ़ील्ड के ऐक्सेस की जांच नहीं की जाती: ऐक्सेस से जुड़ी पाबंदियां, नेटिव कोड पर लागू नहीं होती हैं.)
CheckJNI को चालू करने के कई तरीके हैं.
एम्युलेटर का इस्तेमाल करने पर, CheckJNI डिफ़ॉल्ट रूप से चालू होता है.
अगर आपके पास रूट किया गया डिवाइस है, तो CheckJNI चालू होने पर, रनटाइम को रीस्टार्ट करने के लिए इन निर्देशों का इस्तेमाल करें:
adb shell stop adb shell setprop dalvik.vm.checkjni true adb shell start
इनमें से किसी भी मामले में, रनटाइम शुरू होने पर, आपको अपने Logcat आउटपुट में कुछ ऐसा दिखेगा:
D AndroidRuntime: CheckJNI is ON
अगर आपके पास सामान्य डिवाइस है, तो इस निर्देश का इस्तेमाल किया जा सकता है:
adb shell setprop debug.checkjni 1
इससे पहले से चल रहे ऐप्लिकेशन पर कोई असर नहीं पड़ेगा. हालांकि, इसके बाद लॉन्च किए जाने वाले हर ऐप्लिकेशन में CheckJNI चालू होगा. (प्रॉपर्टी को किसी दूसरी वैल्यू में बदलें या सिर्फ़ फिर से चालू करने से CheckJNI फिर से बंद हो जाएगा.) इस मामले में, अगली बार कोई ऐप्लिकेशन शुरू होने पर, आपको अपने logcat आउटपुट में कुछ ऐसा दिखेगा:
D Late-enabling CheckJNI
अपने ऐप्लिकेशन के मेनिफ़ेस्ट में android:debuggable
एट्रिब्यूट को सेट करके भी, सिर्फ़ अपने ऐप्लिकेशन के लिए CheckJNI को चालू किया जा सकता है. ध्यान दें कि Android बिल्ड टूल, कुछ बिल्ड टाइप के लिए यह काम अपने-आप कर देंगे.
स्थानीय लाइब्रेरी
शेयर की गई लाइब्रेरी से स्टैंडर्ड System.loadLibrary
का इस्तेमाल करके, नेटिव कोड लोड किया जा सकता है.
Android के पुराने वर्शन में PackageManager में गड़बड़ियां थीं. इनकी वजह से, पैकेज को इंस्टॉल किया गया और अपडेट करने की ज़रूरत नहीं पड़ेगी. ReLinker यह प्रोजेक्ट और अन्य नेटिव लाइब्रेरी लोड होने से जुड़ी समस्याओं को हल करने का तरीका भी बताता है.
स्टैटिक क्लास के शुरू करने वाले फ़ंक्शन से System.loadLibrary
(या ReLinker.loadLibrary
) को कॉल करें. तर्क "अस्पष्ट" है लाइब्रेरी का नाम,
इसलिए, libfubar.so
को लोड करने के लिए, आप "fubar"
में पास हो पाएंगे.
अगर आपके पास नेटिव तरीकों वाली सिर्फ़ एक क्लास है, तो
System.loadLibrary
को उस क्लास के लिए स्टैटिक शुरू करने वाले व्यक्ति में रखें. नहीं तो आप
Application
से कॉल करना चाहते हैं, ताकि आपको पता रहे कि लाइब्रेरी हमेशा लोड रहती है,
साथ ही, ये हमेशा जल्दी लोड हो जाती हैं.
रनटाइम आपके नेटिव तरीके दो तरीकों से ढूंढ सकता है. आपके पास उन्हें RegisterNatives
के साथ साफ़ तौर पर रजिस्टर करने का विकल्प है. इसके अलावा, dlsym
की मदद से रनटाइम को उन्हें डाइनैमिक तौर पर खोजने की अनुमति भी दी जा सकती है. RegisterNatives
के फ़ायदे यह हैं कि आपको पहले से ही यह पता चल जाता है कि सिंबल मौजूद हैं या नहीं. साथ ही, JNI_OnLoad
के अलावा कुछ भी एक्सपोर्ट न करके, शेयर की गई लाइब्रेरी को छोटा और तेज़ बनाया जा सकता है. रनटाइम को अपने
फ़ंक्शन है कि इसमें लिखने के लिए थोड़ा कम कोड होता है.
RegisterNatives
का इस्तेमाल करने के लिए:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
फ़ंक्शन दें.- अपने
JNI_OnLoad
में,RegisterNatives
का इस्तेमाल करके अपने सभी स्थानीय तरीकों को रजिस्टर करें. - वर्शन स्क्रिप्ट (इसका सुझाव दिया जाता है) का इस्तेमाल करके बनाएं या
-fvisibility=hidden
का इस्तेमाल करें, ताकि आपकी लाइब्रेरी से सिर्फ़JNI_OnLoad
एक्सपोर्ट हो. यह तेज़ और छोटा कोड जनरेट करता है. साथ ही, संभावित कन्वर्ज़न से बचाता है आपके ऐप्लिकेशन में लोड की गई अन्य लाइब्रेरी से टकराव होता है (लेकिन यह कम काम का स्टैक ट्रेस बनाता है अगर आपका ऐप्लिकेशन नेटिव कोड में क्रैश हो जाता है).
स्टैटिक शुरू करने वाला टूल कुछ ऐसा दिखना चाहिए:
Kotlin
companion object { init { System.loadLibrary("fubar") } }
Java
static { System.loadLibrary("fubar"); }
JNI_OnLoad
फ़ंक्शन कुछ ऐसा दिखना चाहिए, अगर
C++ में लिखा गया:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // Find your class. JNI_OnLoad is called from the correct class loader context for this to work. jclass c = env->FindClass("com/example/app/package/MyClass"); if (c == nullptr) return JNI_ERR; // Register your class' native methods. static const JNINativeMethod methods[] = { {"nativeFoo", "()V", reinterpret_cast<void*>(nativeFoo)}, {"nativeBar", "(Ljava/lang/String;I)Z", reinterpret_cast<void*>(nativeBar)}, }; int rc = env->RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod)); if (rc != JNI_OK) return rc; return JNI_VERSION_1_6; }
इसके बजाय "discovery" का इस्तेमाल करने के लिए आपको उन्हें एक विशिष्ट नाम से रखना होगा (देखें JNI की खास बातें देखें). इसका मतलब है कि अगर किसी मेथड का सिग्नेचर गलत है, तो आपको इसकी जानकारी तब तक नहीं मिलेगी, जब तक कि मेथड को पहली बार असल में इस्तेमाल नहीं किया जाता.
JNI_OnLoad
से किए गए किसी भी FindClass
कॉल से, क्लास लोडर के संदर्भ में क्लास को हल किया जाएगा. इसका इस्तेमाल, शेयर की गई लाइब्रेरी को लोड करने के लिए किया गया था. जब दूसरे व्यक्ति से कॉल किया गया हो
कॉन्टेक्स्ट के लिए, FindClass
Java स्टैक या अगर कोई भी मौजूद नहीं है (क्योंकि कॉल ऐसे नेटिव थ्रेड से है जो अभी-अभी अटैच किया गया है)
यह "सिस्टम" का इस्तेमाल करता है क्लास लोडर. सिस्टम क्लास लोडर को आपके ऐप्लिकेशन की
क्लास की सेटिंग को बदल दिया है, इसलिए आप उसमें FindClass
के साथ खुद की क्लास नहीं खोज पाएंगे
संदर्भ. इससे JNI_OnLoad
में कक्षाओं को खोजना और उन्हें कैश मेमोरी में सेव करना आसान हो जाता है: जब आपके पास मान्य jclass
ग्लोबल रेफ़रंस हो, तो अटैच की गई किसी भी थ्रेड से उसका इस्तेमाल किया जा सकता है.
@FastNative
और @CriticalNative
की मदद से फटाफट कॉल करें
नेटिव मेथड के साथ एनोटेट किया जा सकता है
@FastNative
या
@CriticalNative
(दोनों नहीं) का इस्तेमाल करें. हालांकि, ये एनोटेशन
व्यवहार में कुछ ऐसे बदलाव होते हैं, जिन्हें इस्तेमाल करने से पहले ध्यान से देखना ज़रूरी है. हालांकि, हम
इन बदलावों के बारे में नीचे बताएं. कृपया ज़्यादा जानकारी के लिए दस्तावेज़ देखें.
@CriticalNative
एनोटेशन को सिर्फ़ उन नेटिव तरीकों पर लागू किया जा सकता है जो
मैनेज किए जा रहे ऑब्जेक्ट का इस्तेमाल करें (पैरामीटर या रिटर्न वैल्यू में या इंप्लिसिट this
के तौर पर), और यह
एनोटेशन से JNI ट्रांज़िशन एबीआई को बदला जाता है. लागू किए गए नेटिव विज्ञापनों में,
इसके फ़ंक्शन सिग्नेचर से, JNIEnv
और jclass
पैरामीटर.
@FastNative
या @CriticalNative
तरीके को लागू करते समय, ज़रूरी काम के लिए, ग़ैर-ज़रूरी डेटा हटाने की प्रोसेस, थ्रेड को निलंबित नहीं कर सकती. साथ ही, यह प्रोसेस ब्लॉक भी हो सकती है. इनका उपयोग न करें
लंबे समय तक चलने वाले तरीकों के एनोटेशन. इनमें आम तौर पर तेज़, लेकिन बिना किसी सीमा वाले तरीके शामिल हैं.
खास तौर पर, कोड में ज़रूरी I/O कार्रवाइयां नहीं की जानी चाहिए या ऐसे नेटिव लॉक हासिल नहीं किए जाने चाहिए जिन्हें लंबे समय तक होल्ड किया जा सकता है.
ये एनोटेशन सिस्टम उपयोग के लिए इस तारीख से लागू किए गए थे
Android 8
और वह CTS-टेस्टेड सार्वजनिक बन गया
Android 14 में एपीआई. ये ऑप्टिमाइज़ेशन, Android 8 से 13 वाले डिवाइसों पर भी काम कर सकते हैं. हालांकि, इनके लिए सीटीएस की गारंटी नहीं दी जाती. नेटिव तरीकों का डाइनैमिक लुकअप सिर्फ़ Android 12 और उसके बाद के वर्शन पर काम करता है. Android 8 से 11 वाले वर्शन पर इसे चलाने के लिए, JNI RegisterNatives
के साथ साफ़ तौर पर रजिस्टर करना ज़रूरी है. Android 7 पर इन एनोटेशन को अनदेखा किया जाता है. @CriticalNative
के लिए एबीआई का मेल न खाने पर, गलत आर्ग्युमेंट मार्शलिंग होगी और ऐप्लिकेशन क्रैश हो सकता है.
जिन परफ़ॉर्मेंस के लिए अहम तरीकों में इन एनोटेशन की ज़रूरत होती है उनके लिए, यह सुझाव दिया जाता है कि
उस तरीके(तरीकों) को साफ़ तौर पर JNI RegisterNatives
के साथ रजिस्टर करें, न कि
नाम पर आधारित "खोज" का इस्तेमाल किया जा सकता है. ऐप्लिकेशन के शुरू होने की परफ़ॉर्मेंस को बेहतर बनाने के लिए, हमारा सुझाव है कि आप बेसलाइन प्रोफ़ाइल में @FastNative
या @CriticalNative
तरीकों के कॉलर शामिल करें. Android 12 और उसके बाद के वर्शन में,
किसी कंपाइल किए गए तरीके से @CriticalNative
नेटिव मेथड को किया जाने वाला कॉल
C/C++ में नॉन-इनलाइन कॉल के रूप में सस्ता है, जब तक कि सभी तर्क रजिस्टर में फ़िट हो जाएं (उदाहरण के लिए
8 इंटिग्रल और ज़्यादा से ज़्यादा आठ फ़्लोटिंग पॉइंट आर्ग्युमेंट, आर्म64 पर).
कभी-कभी किसी नेटिव मेथड को दो हिस्सों में बांटना बेहतर हो सकता है. यह एक बहुत तेज़ तरीका है, जो दूसरा ईमेल खाता है, जो धीमे मामलों को भी ठीक करता है. उदाहरण के लिए:
Kotlin
fun writeInt(nativeHandle: Long, value: Int) { // A fast buffered write with a `@CriticalNative` method should succeed most of the time. if (!nativeTryBufferedWriteInt(nativeHandle, value)) { // If the buffered write failed, we need to use the slow path that can perform // significant I/O and can even throw an `IOException`. nativeWriteInt(nativeHandle, value) } } @CriticalNative external fun nativeTryBufferedWriteInt(nativeHandle: Long, value: Int): Boolean external fun nativeWriteInt(nativeHandle: Long, value: Int)
Java
void writeInt(long nativeHandle, int value) { // A fast buffered write with a `@CriticalNative` method should succeed most of the time. if (!nativeTryBufferedWriteInt(nativeHandle, value)) { // If the buffered write failed, we need to use the slow path that can perform // significant I/O and can even throw an `IOException`. nativeWriteInt(nativeHandle, value); } } @CriticalNative static native boolean nativeTryBufferedWriteInt(long nativeHandle, int value); static native void nativeWriteInt(long nativeHandle, int value);
64-बिट पर ध्यान देना
64-बिट पॉइंटर का इस्तेमाल करने वाले आर्किटेक्चर के साथ काम करने के लिए, long
Java फ़ील्ड में, नेटिव स्ट्रक्चर में पॉइंटर सेव करते समय int
.
इस्तेमाल न की जा सकने वाली सुविधाएं/पुराने सिस्टम के साथ काम करने की सुविधा
JNI 1.6 की सभी सुविधाएं काम करती हैं. हालांकि, इनमें से कुछ सुविधाएं काम नहीं करतीं:
DefineClass
लागू नहीं किया गया है. Android, Java बाइटकोड या क्लास फ़ाइलों का इस्तेमाल नहीं करता. इसलिए, बाइनरी क्लास डेटा को पास करना काम नहीं करता.
Android के पुराने वर्शन के साथ काम करने के लिए, आपको इन बातों का ध्यान रखना पड़ सकता है:
- नेटिव फ़ंक्शन का डाइनैमिक लुकअप
Android 2.0 (Eclair), '$' तक वर्ण ठीक से नहीं था "_00024" में बदला गया खोज के दौरान काम हो रहा है साफ़ तौर पर रजिस्ट्रेशन करना ज़रूरी है या 'Google Analytics 4 प्रॉपर्टी' नेटिव क्लास से बाहर निकालें.
- थ्रेड को अलग करना
Android 2.0 (Eclair) तक
pthread_key_create
का इस्तेमाल नहीं किया जा सकता था डिटैचर फ़ंक्शन, ताकि "थ्रेड को पहले डिटैच करना ज़रूरी हो" बाहर निकलें" चेक करें. (रनटाइम में pThread की डिस्ट्रक्टर फ़ंक्शन का भी इस्तेमाल किया जाता है, इसलिए यह देखने की दौड़ होगी कि किसे पहले कॉल किया जाता है.) - कमज़ोर ग्लोबल रेफ़रंस
Android 2.2 (Froyo) तक, कमज़ोर ग्लोबल रेफ़रंस लागू नहीं किए गए थे. पुराने वर्शन, उनका इस्तेमाल करने की कोशिशों को पूरी तरह से अस्वीकार कर देंगे. Google Analytics 4 पर माइग्रेट करने के लिए, Android प्लैटफ़ॉर्म वर्शन कॉन्सटेंट मौजूद होती है, ताकि इसकी जांच की जा सके.
Android 4.0 (आइसक्रीम सैंडविच) तक, कमज़ोर वैश्विक संदर्भ केवल
NewLocalRef
,NewGlobalRef
, औरDeleteWeakGlobalRef
. (स्पेसिफ़िकेशन में प्रोग्रामर को, कमज़ोर ग्लोबल वैरिएबल का इस्तेमाल करने से पहले, उनके लिए हार्ड रेफ़रंस बनाने का सुझाव दिया गया है. इसलिए, इसकी वजह से कोई समस्या नहीं होनी चाहिए.)Android 4.0 (Ice Cream Sandwich) और उसके बाद के वर्शन में, कमज़ोर ग्लोबल रेफ़रंस का इस्तेमाल, किसी भी दूसरे JNI रेफ़रंस की तरह किया जा सकता है.
- लोकल रेफ़रंस
Android 4.0 (आइसक्रीम सैंडविच) तक, स्थानीय संदर्भ थे पॉइंटर इस्तेमाल करते हैं. आइसक्रीम सैंडविच ने अप्रत्यक्ष जानकारी जोड़ी कचरा इकट्ठा करने वाले बेहतर लोगों की मदद के लिए ज़रूरी है, लेकिन इसका मतलब यह है कि पुरानी रिलीज़ पर JNI बग का पता नहीं लगाया जा सकता. ज़्यादा जानकारी के लिए, ICS में JNI लोकल रेफ़रंस में हुए बदलाव देखें.
Android 8.0 से पहले के Android वर्शन में, लोकल रेफ़रंस की संख्या, वर्शन के हिसाब से तय की गई सीमा तक ही सीमित होती है. Android 8.0 से, Android अनलिमिटेड लोकल रेफ़रंस के साथ काम करता है.
GetObjectRefType
की मदद से पहचान फ़ाइल का टाइप तय करनाAndroid 4.0 (Ice Cream Sandwich) तक, डायरेक्ट पॉइंटर (ऊपर देखें) के इस्तेमाल की वजह से,
GetObjectRefType
को सही तरीके से लागू नहीं किया जा सकता था. इसके बजाय, हमने एक अनुमान का इस्तेमाल किया इस सर्वे में, कमज़ोर ग्लोबल टेबल, तर्क, स्थानीय लोगों, टेबल और इसी क्रम में ग्लोबल टेबल भी दिखेगी. जब पहली बार उसे आपका डायरेक्ट पॉइंटर मिलेगा, तो वह यह रिपोर्ट करेगा कि आपका रेफ़रंस उस तरह का है जिसकी जांच की जा रही थी. उदाहरण के लिए, इसका मतलब यह था कि अगर आपनेGetObjectRefType
को ग्लोबल jclass पर कॉल किया जो हुआ jclass जैसा ही होना चाहिए. इसे आपके स्टैटिक आर्ग्युमेंट के तौर पर इस्तेमाल किया जाना चाहिए नेटिव तरीके का इस्तेमाल करते हैं, तो आपको इसके बजायJNILocalRefType
मिलेंगेJNIGlobalRefType
.@FastNative
और@CriticalNative
Android 7 तक, इन ऑप्टिमाइज़ेशन एनोटेशन को अनदेखा किया जाता था.
@CriticalNative
के लिए एबीआई के मैच न होने पर, आर्ग्युमेंट को गलत तरीके से मार्शल किया जाएगा और ऐप्लिकेशन क्रैश हो सकता है.@FastNative
और@CriticalNative
तरीकों के लिए, नेटिव फ़ंक्शन का डाइनैमिक लुकअप, Android 8 से 10 में लागू नहीं किया गया था. साथ ही, Android 11 में इसके कुछ बग मौजूद हैं. बिना ऑप्टिमाइज़ेशन के इन ऑप्टिमाइज़ेशन का उपयोग करना जेएनआईRegisterNatives
के साथ साफ़ तौर पर रजिस्ट्रेशन करने पर, की वजह से Android 8-11 क्रैश हो जाता है.FindClass
नेClassNotFoundException
थ्रो कियापुराने सिस्टम के साथ काम करने की सुविधा के लिए, Android पर
ClassNotFoundException
का डेटा दिखता है अगर कोई क्लास नहीं मिलती है, तोNoClassDefFoundError
के बजायFindClass
. यह व्यवहार, Java रेफ़्लेक्शन एपीआई के मुताबिक हैClass.forName(name)
.
अक्सर पूछे जाने वाले सवाल: मुझे UnsatisfiedLinkError
क्यों मिलेगा?
नेटिव कोड पर काम करते समय, इस तरह की गड़बड़ी देखना आम बात है:
java.lang.UnsatisfiedLinkError: Library foo not found
कुछ मामलों में इसका मतलब है कि वहां क्या लिखा है — लाइब्रेरी नहीं मिली. कुछ मामलों में, लाइब्रेरी मौजूद होती है, लेकिन dlopen(3)
उसे खोल नहीं पाता. साथ ही, गड़बड़ी की जानकारी, अपवाद के ब्यौरे वाले मैसेज में मिल सकती है.
"लाइब्रेरी नहीं मिली" दिखने की सामान्य वजहें अपवाद:
- लाइब्रेरी मौजूद नहीं है या ऐप्लिकेशन को उसे ऐक्सेस नहीं किया जा सकता. इस्तेमाल की जाने वाली चीज़ें
इसकी मौजूदगी की जांच करने के लिए
adb shell ls -l <path>
और अनुमतियां शामिल हैं. - लाइब्रेरी को एनडीके से नहीं बनाया गया था. इससे, डिवाइस पर मौजूद फ़ंक्शन या लाइब्रेरी पर डिपेंडेंसी हो सकती है.
UnsatisfiedLinkError
गड़बड़ियों की कोई अन्य क्लास इस तरह दिखती है:
java.lang.UnsatisfiedLinkError: myfunc at Foo.myfunc(Native Method) at Foo.main(Foo.java:10)
Logcat में, आपको यह जानकारी दिखेगी:
W/dalvikvm( 880): No implementation found for native LFoo;.myfunc ()V
इसका मतलब है कि रनटाइम ने मैच होने वाला कोई तरीका ढूंढने की कोशिश की, लेकिन उसे कोई तरीका नहीं मिला. इसकी कुछ आम वजहें ये हैं:
- लाइब्रेरी लोड नहीं हो रही है. इसके लिए Logcat आउटपुट देखें लाइब्रेरी लोड होने के बारे में मैसेज.
- यह तरीका नहीं मिल रहा है, क्योंकि नाम या हस्ताक्षर मेल नहीं खाते हैं. यह
आम तौर पर इस वजह से होता है:
- लैज़ी मेथड लुकअप के लिए,
extern "C"
और सही दिखने (JNIEXPORT
) के साथ C++ फ़ंक्शन का एलान न करना. ध्यान दें कि Ice Cream Sandwich से पहले, JNIEXPORT मैक्रो गलत था. इसलिए, किसी पुरानेjni.h
के साथ नए GCC का इस्तेमाल नहीं किया जा सकता.arm-eabi-nm
का इस्तेमाल किया जा सकता है लाइब्रेरी में मौजूद चिह्नों को देखने के लिए; अगर वे ऐसे दिखते हैं चोटिल (_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass
जैसा कुछ)Java_Foo_myfunc
के बजाय) या अगर प्रतीक प्रकार अंग्रेज़ी के छोटे अक्षर 't' की बजाय, बड़े अक्षर 'T' का इस्तेमाल करें, तो आपको इसके लिए, एलान में बदलाव करना होगा. - स् पष्ट पंजीकरण के लिए, दर्ज करते समय मामूली गड़बड़ियां
तरीका सिग्नेचर. सुनिश्चित करें कि आप
रजिस्ट्रेशन कॉल, लॉग फ़ाइल में मौजूद हस्ताक्षर से मेल खाता हो.
याद रखें कि 'B' को
byte
और 'Z' कोboolean
लिखा जाता है. हस्ताक्षर में क्लास के नाम के कॉम्पोनेंट 'L' से शुरू होते हैं और ';' पर खत्म होते हैं. पैकेज/क्लास के नामों को अलग करने के लिए '/' का इस्तेमाल करें और अंदरूनी क्लास के नामों (Ljava/util/Map$Entry;
, जैसे) को अलग करने के लिए '$' का इस्तेमाल करें.
- लैज़ी मेथड लुकअप के लिए,
javah
का इस्तेमाल करके, अपने-आप जेएनआई हेडर जनरेट होने से मदद मिल सकती है
कुछ समस्याओं से बचें.
अक्सर पूछे जाने वाले सवाल: FindClass
को मेरी क्लास क्यों नहीं मिली?
(ज़्यादातर सलाह, GetMethodID
या GetStaticMethodID
वाले तरीकों या GetFieldID
या GetStaticFieldID
वाले फ़ील्ड को ढूंढने में होने वाली समस्याओं पर भी लागू होती हैं.)
पक्का करें कि क्लास के नाम वाली स्ट्रिंग का फ़ॉर्मैट सही है. JNI क्लास
नाम, पैकेज के नाम से शुरू होते हैं और स्लैश से अलग किए जाते हैं.
जैसे कि java/lang/String
. अगर कोई अरे क्लास खोज रहे हैं,
आपको स्क्वेयर ब्रैकेट की सही संख्या से शुरू करना होगा और
क्लास को 'L' से भी रैप करना होगा और ';', इसलिए,
String
होगा [Ljava/lang/String;
.
अगर आपको कोई इनर क्लास खोजनी है, तो '.' के बजाय '$' का इस्तेमाल करें. आम तौर पर, .class फ़ाइल पर javap
का इस्तेमाल करके, अपनी क्लास का इंटरनल नाम पता किया जा सकता है.
अगर कोड छोटा करने की सुविधा चालू की जा रही है, तो पक्का करें कि कॉन्फ़िगर करें कि किस कोड को रखना है. कोड को छोटा करने वाले टूल के लिए, कॉन्फ़िगर करना ज़रूरी है. ऐसा इसलिए, क्योंकि ऐसा न करने पर, कोड को छोटा करने वाला टूल उन क्लास, तरीकों या फ़ील्ड को हटा सकता है जिनका इस्तेमाल सिर्फ़ JNI से किया जाता है.
अगर क्लास का नाम सही लग रहा है, तो हो सकता है कि आपको क्लास लोड करने की कोशिश करनी पड़े
समस्या. FindClass
इसमें कक्षा की खोज शुरू करना चाहता है
क्लास लोडर की सुविधा देता है. यह कॉल स्टैक की जांच करता है,
जो कुछ ऐसा दिखेगा:
Foo.myfunc(Native Method) Foo.main(Foo.java:10)
सबसे ऊपर मौजूद तरीका Foo.myfunc
है. FindClass
अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है
Foo
से जुड़े ClassLoader
ऑब्जेक्ट को ढूंढता है
क्लास में शामिल किया जाता है और उसका इस्तेमाल किया जाता है.
आम तौर पर, यह आपके मनमुताबिक होता है. अगर आपने खुद कोई थ्रेड बनाई है, तो आपको समस्या आ सकती है. ऐसा pthread_create
को कॉल करके और फिर उसे AttachCurrentThread
से अटैच करके किया जा सकता है. अब आपके ऐप्लिकेशन में कोई स्टैक फ़्रेम नहीं है.
अगर आप इस थ्रेड में FindClass
को कॉल करते हैं, तो
JavaVM "सिस्टम" में शुरू होगा क्लास लोडर न होने की वजह से
इसलिए, खास तौर पर आपके ऐप्लिकेशन के लिए बनाई गई क्लास को ढूंढने की कोशिश नहीं की जा सकेगी.
इसे ठीक करने के कुछ तरीके यहां दिए गए हैं:
FindClass
लुकअप को एक बार,JNI_OnLoad
में करें और बाद में इस्तेमाल करने के लिए, क्लास रेफ़रंस को कैश मेमोरी में सेव करें. एक्ज़ीक्यूट करने के दौरान किया गया कोई भीFindClass
कॉलJNI_OnLoad
इससे जुड़े क्लास लोडर का इस्तेमाल करेगा फ़ंक्शन को किया है, जिसेSystem.loadLibrary
(यह एक एक खास नियम का इस्तेमाल किया जाता है). अगर आपका ऐप्लिकेशन कोड लाइब्रेरी लोड कर रहा है, तोFindClass
सही क्लास लोडर का इस्तेमाल करेगा.- जिन फ़ंक्शन की ज़रूरत है उनके लिए क्लास का इंस्टेंस पास करें
क्लास तर्क लेने के लिए अपनी स्थानीय विधि घोषित करके और
फिर
Foo.class
पास हो रहा है. ClassLoader
ऑब्जेक्ट के रेफ़रंस को किसी ऐसी जगह पर कैश मेमोरी में सेव करें जहां वह आसानी से मिल जाए और सीधेloadClass
कॉल करें. इसके लिए, आपको कुछ मेहनत करनी होगी.
अक्सर पूछे जाने वाले सवाल: मैं नेटिव कोड के साथ रॉ डेटा कैसे शेयर करूं?
आप ऐसी स्थिति में पड़ सकते हैं जहां आपको बड़ी मैनेज किए जा रहे और नेटिव कोड, दोनों से रॉ डेटा का बफ़र. आम तौर पर, बिटमैप या साउंड सैंपल में बदलाव करने पर ऐसा होता है. इसके दो मुख्य तरीके हैं.
डेटा को byte[]
में सेव किया जा सकता है. यह बहुत तेज़ी से
मैनेज किए जा रहे कोड से ऐक्सेस किया जा सकता है. हालांकि, नेटिव साइड पर, यह गारंटी नहीं है कि डेटा को कॉपी किए बिना उसे ऐक्सेस किया जा सकता है. तय सीमा में
कुछ तरीके लागू करना, GetByteArrayElements
और
GetPrimitiveArrayCritical
आपकी साइट के असल पॉइंटर दिखाएगा
मैनेज किए जा रहे हीप में रॉ डेटा, लेकिन अन्य में यह बफ़र तय करेगा
और डेटा को कॉपी करें.
दूसरा विकल्प यह है कि डेटा को डायरेक्ट बाइट बफ़र में सेव किया जाए. इन्हें java.nio.ByteBuffer.allocateDirect
या JNI NewDirectByteBuffer
फ़ंक्शन की मदद से बनाया जा सकता है. सामान्य से अलग
बाइट बफ़र में सेव होता है, मैनेज किए जा रहे हीप में स्टोरेज को नहीं बांटा जाता है, और
हमेशा सीधे नेटिव कोड से ऐक्सेस करें (पता पाएं
GetDirectBufferAddress
के साथ). यह इस बात पर निर्भर करता है कि
बाइट बफ़र ऐक्सेस लागू किया गया और मैनेज किए जा रहे कोड से डेटा ऐक्सेस किया गया
बहुत धीमा हो सकता है.
कौनसा विकल्प इस्तेमाल करना है, यह दो बातों पर निर्भर करता है:
- क्या ज़्यादातर डेटा ऐक्सेस, Java में लिखे गए कोड से होगा या C/C++ में?
- यदि डेटा को अंत में सिस्टम API को पास किया जाता है, तो किस फ़ॉर्म
होना चाहिए? (उदाहरण के लिए, अगर डेटा को आखिर में किसी ऐसे फ़ंक्शन में पास किया जाता है जो बाइट[] लेता है, तो सीधे
ByteBuffer
में प्रोसेसिंग करना गलत हो सकता है.)
अगर किसी एक ही विकल्प का साफ़ तौर पर पता चलता है, तो डायरेक्ट बाइट बफ़र का इस्तेमाल करें. इनके लिए सहायता इसे सीधे JNI में बनाया गया है. इसे आने वाले समय में रिलीज़ होने पर, परफ़ॉर्मेंस में सुधार होना चाहिए.