JNI, जावा नेटिव इंटरफ़ेस है. यह उस बाइट कोड के लिए तरीका बताता है जिसे Android इकट्ठा करता है नेटिव कोड के साथ इंटरैक्ट करने के लिए, मैनेज किया जा रहा कोड (Java या Kotlin प्रोग्रामिंग भाषाओं में लिखा जाता है) (C/C++ में लिखा गया है). JNI, वेंडर न्यूट्रल है और डाइनैमिक शेयर्ड से कोड लोड करने की सुविधा देता है और कभी-कभी यह जटिल भी काम आ सकता है.
ध्यान दें: क्योंकि Android, Kotlin को ART-फ़्रेंडली बाइटकोड में कंपाइल करता है, की तरह ही, आप इस पेज पर दिए गए निर्देशों को इन दोनों में लागू कर सकते हैं Kotlin और Java प्रोग्रामिंग भाषाओं को JNI आर्किटेक्चर और उससे जुड़ी लागतों के हिसाब से किया जा सकता है. इस बारे में ज़्यादा जानने के लिए, यह देखें Kotlin और Android.
अगर आप इसके बारे में नहीं जानते हैं, तो ज़्यादा जानकारी के लिए Java नेटिव इंटरफ़ेस की खास बातें ताकि यह जाना जा सके कि JNI कैसे काम करता है और इसमें कौन-कौनसी सुविधाएं उपलब्ध हैं. कुछ सूचनाएं मिल रही हैं उन चीज़ों के बारे में साफ़ तौर पर नहीं बताया गया है जो ताकि आपको अगले कुछ सेक्शन आसानी से मिलें.
वैश्विक JNI संदर्भ ब्राउज़ करने और यह देखने के लिए कि वैश्विक JNI संदर्भ कहां बनाए और हटाए जाते हैं, इसका उपयोग करें मेमोरी प्रोफ़ाइलर में JNI हीप व्यू Android Studio 3.2 और इसके बाद के वर्शन में.
सामान्य सलाह
अपनी जेएनआई लेयर के फ़ुटप्रिंट को कम करने की कोशिश करें. यहां कई डाइमेंशन का इस्तेमाल किया जा सकता है. आपके जेएनआई सलूशन को इन दिशा-निर्देशों का पालन करना चाहिए. इन दिशा-निर्देशों की सूची, अहमियत के क्रम के हिसाब से नीचे दी गई है, जो सबसे ज़रूरी बातों से शुरू होती है):
- जेएनआई लेयर में संसाधनों की मार्शलिंग को कम से कम करें. पूरे इलाके में मार्शलिंग JNI लेयर में एक गैर-मामूली लागत होती है. एक ऐसा इंटरफ़ेस डिज़ाइन करने का प्रयास करें, जो वह डेटा जिसे आपको मार्शल की ज़रूरत है और जिस फ़्रीक्वेंसी से आपको डेटा को मार्शल करना होगा.
- मैनेज की जा रही प्रोग्रामिंग में लिखे गए कोड के बीच एसिंक्रोनस कम्यूनिकेशन से बचें भाषा और कोड को C++ में लिखें. इससे आपके JNI इंटरफ़ेस का रखरखाव आसान हो जाएगा. आम तौर पर, एसिंक्रोनस प्रोसेस को आसान बनाया जा सकता है एक साथ काम नहीं करने वाले अपडेट को यूज़र इंटरफ़ेस (यूआई) की भाषा में रखकर, यूज़र इंटरफ़ेस (यूआई) अपडेट किया जाता है. उदाहरण के लिए, JNI के माध्यम से Java कोड में यूज़र इंटरफ़ेस (यूआई) थ्रेड से C++ फ़ंक्शन शुरू करना, यह बेहतर है Java प्रोग्रामिंग भाषा में दो थ्रेड के बीच कॉलबैक करने के लिए: C++ कॉल को ब्लॉक करके, यूज़र इंटरफ़ेस (यूआई) थ्रेड को ब्लॉक किया जा रहा है पूरा हुआ.
- ऐसे थ्रेड की संख्या कम से कम करें जिन्हें जेएनआई से छूना है या टच करना है. अगर आपको Java और C++, दोनों भाषाओं में थ्रेड पूल का इस्तेमाल करना है, तो JNI अलग-अलग वर्कर के थ्रेड के बजाय, पूल के मालिकों के बीच बातचीत करने की सुविधा मिलती है.
- अपने इंटरफ़ेस कोड को आसानी से पहचाने जा सकने वाले C++ और Java सोर्स में रखें जगहों की जानकारी इकट्ठा की जा सकती है, ताकि आने वाले समय में इस प्रक्रिया में मदद मिल सके. JNI ऑटो-जनरेशन का उपयोग करें लाइब्रेरी में कुछ बदलाव कर सकते हैं.
JavaVM और JNIEnv
JNI, "JavaVM" के दो मुख्य डेटा स्ट्रक्चर के बारे में बताता है और "JNIEnv" है. ये दोनों ही बातें ज़रूरी हैं फ़ंक्शन टेबल के लिए पॉइंटर. (C++ वर्शन में, वे हर JNI फ़ंक्शन के लिए, किसी फ़ंक्शन टेबल और मेंबर फ़ंक्शन के लिए पॉइंटर टेबल.) JavaVM "न्योता का इंटरफ़ेस" देता है फ़ंक्शन, इसकी मदद से, JavaVM बनाया जा सकता है और उसे बंद किया जा सकता है. सिद्धांत के तौर पर, हर प्रोसेस में एक से ज़्यादा JavaVM हो सकते हैं, लेकिन Android सिर्फ़ इसकी अनुमति देता है.
JNIEnv में जेएनआई के ज़्यादातर फ़ंक्शन दिए जाते हैं. आपके सभी नेटिव फ़ंक्शन को JNIEnv के रूप में
पहला तर्क, @CriticalNative
तरीकों को छोड़कर,
तेज़ स्थानीय कॉल देखें.
JNIEnv का इस्तेमाल थ्रेड-लोकल स्टोरेज के लिए किया जाता है. इसी वजह से, थ्रेड के बीच JNIEnv शेयर नहीं किया जा सकता.
अगर किसी कोड के पास उसका JNIEnv पाने का कोई दूसरा तरीका नहीं है, तो आपको उसे शेयर करना चाहिए
जावाVM को फ़िल्टर कर सकता है और थ्रेड के 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
में है और यह कि आप उसी ClassLoader
का इस्तेमाल कर रहे हैं
को डिफ़ॉल्ट रूप से सेट करें. Java में डीबग करने के लिए, इससे थ्रेड का नाम सेट करना भी ज़्यादा आसान है
नेटिव कोड (pthread_setname_np()
देखें, अगर आपके पास pthread_t
है या
thread_t
और std::thread::native_handle()
, अगर आपके पास
std::thread
और pthread_t
चाहिए).
नेटिव तौर पर बनाए गए थ्रेड को अटैच करने पर, java.lang.Thread
जनरेट होता है
ऑब्जेक्ट बनाना और उसे "मुख्य" में जोड़ा जाना चाहिए 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
तरीका बनाएं जो आईडी लुकअप कर सके. कोड
क्लास शुरू होने पर, एक बार एक्ज़ीक्यूट किया जाएगा. अगर क्लास को कभी अनलोड किया जाता है और
फिर से लोड करने के बाद, इसे फिर से चलाया जाएगा.
स्थानीय और ग्लोबल रेफ़रंस
हर तर्क को किसी नेटिव तरीके पर पास किया जाता है और करीब-करीब हर ऑब्जेक्ट मिलता है एक "लोकल रेफ़रंस" होता है. इसका मतलब है कि यह मान्य है: मौजूदा थ्रेड में मौजूदा नेटिव मेथड की अवधि. भले ही, नेटिव तरीके के बाद ऑब्जेक्ट पर निर्भर रहता है रिटर्न, रेफ़रंस मान्य नहीं है.
यह jobject
की सभी सब-क्लास पर लागू होता है. इनमें ये सब-क्लास भी शामिल हैं
jclass
, jstring
, और jarray
.
(जेएनआई की अवधि बढ़ाने पर, रनटाइम आपको ज़्यादातर रेफ़रंस के गलत इस्तेमाल के बारे में चेतावनी देगा
जांच करने की सुविधा चालू है.)
गैर-लोकल रेफ़रंस पाने का सिर्फ़ एक तरीका, फ़ंक्शन का इस्तेमाल करना है
NewGlobalRef
और NewWeakGlobalRef
.
अगर आपको किसी पहचान फ़ाइल को लंबे समय तक होल्ड करके रखना है, तो आपको
"दुनिया भर में" संदर्भ. NewGlobalRef
फ़ंक्शन
स्थानीय संदर्भ को तर्क के रूप में दिखाता है और वैश्विक संदर्भ देता है.
यह गारंटी है कि आपके कॉल करने तक ग्लोबल रेफ़रंस मान्य होगा
DeleteGlobalRef
.
आम तौर पर, इस पैटर्न का इस्तेमाल तब किया जाता है, जब किसी क्लास को कैश मेमोरी में सेव किया जाता है
FindClass
से, उदाहरण के लिए:
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-स्टाइल वाली, ज़ीरो-टर्मिनेट की गई स्ट्रिंग का इस्तेमाल किया जा सकता है, स्टैंडर्ड 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 से पहले, आम तौर पर Android के तौर पर UTF-16 स्ट्रिंग का इस्तेमाल करना ज़्यादा तेज़ था
को GetStringChars
में कॉपी करने की ज़रूरत नहीं थी, जबकि
GetStringUTFChars
के लिए बजट असाइन करना ज़रूरी है. साथ ही, इसे UTF-8 में बदलना ज़रूरी है.
Android 8 ने हर वर्ण के लिए 8 बिट का इस्तेमाल करने के लिए, String
में बदलाव किया है
और ASCII स्ट्रिंग (मेमोरी बचाने के लिए) के लिए
चल रहा है
गार्बेज कलेक्टर. ये सुविधाएं उन मामलों की संख्या को बहुत कम कर देती हैं जहां 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
फ़्लैग की जांच करने की एक वजह यह भी जान लें कि
आपको Release
को JNI_COMMIT
के साथ कॉल करना है
के बीच में बदलाव कर सकते हैं — अगर आप
ऐसे कोड को बदलता है और उसे एक्ज़ीक्यूट करता है जो अरे के कॉन्टेंट का इस्तेमाल करता है, तो आपको
यह कर पाएं
'नो-ऑप' सुविधा बंद न करें. फ़्लैग की जाँच करने की दूसरी संभावित वजह यह है
JNI_ABORT
का बेहतर तरीके से इस्तेमाल करना. उदाहरण के लिए, हो सकता है कि
किसी अरे को देखने, उसकी जगह पर बदलाव करने, टुकड़ों को अन्य फ़ंक्शन में पास करने, और
फिर बदलावों को खारिज करें. यदि आप जानते हैं कि 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
कॉल का इस्तेमाल कर सकते हैं
डेटा को किसी कलेक्शन में कॉपी करने के लिए और GetStringRegion
या
इसमें से वर्ण कॉपी करने के लिए GetStringUTFRegion
String
.
अपवाद
जब तक कोई अपवाद लागू न हो, आपको जेएनआई फ़ंक्शन को कॉल नहीं करना चाहिए.
आपके कोड को अपवाद (फ़ंक्शन के रिटर्न वैल्यू,
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 कॉल पर शून्य पॉइंटर भेजना.
- क्लास के नाम: किसी जेएनआई कॉल में क्लास के नाम की “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 या इसके बाद के वर्शन वाले डिवाइस पर, JNI RegisterNatives
के साथ साफ़ तौर पर रजिस्ट्रेशन करना ज़रूरी है
Android 8-11 वर्शन पर काम करता है. इन एनोटेशन को 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 sandbox) से चालू होने पर, कमज़ोर वैश्विक संदर्भ ये हो सकते हैं का उपयोग किसी अन्य JNI संदर्भ की तरह किया जाता है.
- लोकल रेफ़रंस
Android 4.0 (आइसक्रीम सैंडविच) तक, स्थानीय संदर्भ थे पॉइंटर इस्तेमाल करते हैं. आइसक्रीम सैंडविच ने अप्रत्यक्ष जानकारी जोड़ी कचरा इकट्ठा करने वाले बेहतर लोगों की मदद के लिए ज़रूरी है, लेकिन इसका मतलब यह है कि पुरानी रिलीज़ पर JNI गड़बड़ियों का पता नहीं लगाया जा सकता. यहां जाएं: ज़्यादा जानकारी के लिए, आईसीएस में जेएनआई लोकल रेफ़रंस के बदलाव देखें.
Android 8.0 से पहले के Android वर्शन में, स्थानीय संदर्भों की संख्या एक वर्शन-विशिष्ट सीमा पर सीमित है. Android 8.0 से शुरू करके, Android पर अनलिमिटेड लोकल रेफ़रंस की सुविधा मिलती है.
GetObjectRefType
की मदद से पहचान फ़ाइल का टाइप तय करनाAndroid 4.0 (आइसक्रीम सैंडविच) तक, के परिणाम डायरेक्ट पॉइंटर (ऊपर देखें), तो इसे लागू करना नामुमकिन था
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 आउटपुट देखें लाइब्रेरी लोड होने के बारे में मैसेज.
- यह तरीका नहीं मिल रहा है, क्योंकि नाम या हस्ताक्षर मेल नहीं खाते हैं. यह
आम तौर पर इस वजह से होता है:
- लेज़ी मेथड लुकअप के लिए, C++ फ़ंक्शन का एलान करने में समस्या
extern "C"
के साथ और उचित विज़िबिलिटी (JNIEXPORT
). ध्यान दें कि आइसक्रीम से पहले सैंडविच, JNIEXPORT मैक्रो गलत था, इसलिए नए GCC का उपयोग कोई पुरानाjni.h
काम नहीं करेगा.arm-eabi-nm
का इस्तेमाल किया जा सकता है लाइब्रेरी में मौजूद चिह्नों को देखने के लिए; अगर वे ऐसे दिखते हैं चोटिल (_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass
जैसा कुछ)Java_Foo_myfunc
के बजाय) या अगर प्रतीक प्रकार अंग्रेज़ी के छोटे अक्षर 't' की बजाय, बड़े अक्षर 'T' का इस्तेमाल करें, तो आपको इसके लिए, एलान में बदलाव करना होगा. - स् पष्ट पंजीकरण के लिए, दर्ज करते समय मामूली गड़बड़ियां
तरीका सिग्नेचर. सुनिश्चित करें कि आप
रजिस्ट्रेशन कॉल, लॉग फ़ाइल में मौजूद हस्ताक्षर से मेल खाता हो.
याद रखें कि 'B'
byte
और 'Z' हैboolean
है. हस्ताक्षर में क्लास के नाम के कॉम्पोनेंट 'L' से शुरू होते हैं और ';' पर खत्म होते हैं, '/' का उपयोग करें पैकेज/क्लास के नाम अलग करने के लिए और '$' का इस्तेमाल करें अलग करने के लिए इनर-क्लास के नाम (Ljava/util/Map$Entry;
, जैसे कि).
- लेज़ी मेथड लुकअप के लिए, C++ फ़ंक्शन का एलान करने में समस्या
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 में बनाया गया है. इसे आने वाले समय में रिलीज़ होने पर, परफ़ॉर्मेंस में सुधार होना चाहिए.