JNI সংক্রান্ত পরামর্শ

JNI হল Java Native Interface. এটি বাইটকোডের জন্য একটি উপায় সংজ্ঞায়িত করে যা Android ম্যানেজ করা কোড থেকে কম্পাইল করে (Java বা Kotlin প্রোগ্রামিং ল্যাঙ্গুয়েজে লেখা) নেটিভ কোডের সাথে ইন্টার‍্যাক্ট করার জন্য (C/C++ এ লেখা)। JNI হল ভেন্ডর-নিউট্রাল, ডাইনামিক শেয়ার করা লাইব্রেরি থেকে কোড লোড করার জন্য সমর্থন রয়েছে, এবং মাঝে মাঝে কষ্টকর হলেও এটি যথেষ্ট দক্ষ।

মনে রাখবেন: Android, Java প্রোগ্রামিং ল্যাঙ্গুয়েজের মতো একই পদ্ধতিতে Kotlin-কে ART-ফ্রেন্ডলি বাইটকোডে কম্পাইল করে, তাই আপনি JNI আর্কিটেকচার এবং এর সাথে সম্পর্কিত খরচের ক্ষেত্রে Kotlin এবং Java প্রোগ্রামিং ল্যাঙ্গুয়েজ, উভয়ের জন্যই এই পৃষ্ঠার নির্দেশিকা প্রয়োগ করতে পারেন। আরও জানতে, Kotlin ও Android দেখুন।

আপনি যদি আগে থেকে এটি সম্পর্কে না জানেন, তাহলে JNI কীভাবে কাজ করে এবং কী কী ফিচার উপলভ্য আছে তা বুঝতে Java Native Interface Specification পড়ে দেখুন। ইন্টারফেসের কিছু দিক প্রথমবার পড়ার সময় অবিলম্বে স্পষ্ট হয় না, তাই পরের কয়েকটি বিভাগ আপনার কাজে লাগতে পারে।

গ্লোবাল JNI রেফারেন্স ব্রাউজ করতে এবং গ্লোবাল JNI রেফারেন্স কোথায় তৈরি ও মুছে ফেলা হয়েছে তা দেখতে, Android Studio 3.2 এবং তার পরবর্তী ভার্সনে মেমরি প্রোফাইলার-এ JNI হিপ ভিউ ব্যবহার করুন।

সাধারণ টিপ্স

আপনার JNI লেয়ারের ফুটপ্রিন্ট কমানোর চেষ্টা করুন। এখানে বেশ কিছু বিষয় বিবেচনা করতে হবে। আপনার JNI সমাধানকে এইসব নির্দেশিকা (গুরুত্বের ক্রম অনুসারে নিচে তালিকাভুক্ত করা হয়েছে, সবচেয়ে গুরুত্বপূর্ণ দিয়ে শুরু) অনুসরণ করার চেষ্টা করতে হবে:

  • JNI লেয়ার জুড়ে রিসোর্সের মার্শালিং কমান। JNI লেয়ার জুড়ে মার্শালিংয়ের খরচ অনেক বেশি। এমন একটি ইন্টারফেস ডিজাইন করার চেষ্টা করুন যা আপনার মার্শাল করার জন্য প্রয়োজনীয় ডেটার পরিমাণ এবং যে ফ্রিকোয়েন্সিতে আপনাকে ডেটা মার্শাল করতে হবে তা কমিয়ে দেয়।
  • সম্ভব হলে, ম্যানেজ করা প্রোগ্রামিং ল্যাঙ্গুয়েজে লেখা কোড এবং C++-এ লেখা কোডের মধ্যে অ্যাসিঙ্ক্রোনাস কমিউনিকেশন এড়িয়ে চলুন। এটি আপনার JNI ইন্টারফেসকে আরও সহজে ম্যানেজ করতে সাহায্য করবে। সাধারণত, আপনি UI-এর মতো একই ভাষায় অ্যাসিঙ্ক আপডেট রেখে অ্যাসিঙ্ক্রোনাস UI আপডেট সহজ করতে পারেন। যেমন, JNI-এর মাধ্যমে জাভা কোডে UI থ্রেড থেকে C++ ফাংশন ইনভোক করার পরিবর্তে, জাভা প্রোগ্রামিং ল্যাঙ্গুয়েজে দুটি থ্রেডের মধ্যে কলব্যাক করা আরও ভালো, যার মধ্যে একটি থ্রেড ব্লকিং C++ কল করবে এবং ব্লকিং কল সম্পূর্ণ হলে UI থ্রেডকে বিজ্ঞপ্তি পাঠাবে।
  • JNI-এর মাধ্যমে টাচ করতে হবে বা টাচ করা প্রয়োজন এমন থ্রেডের সংখ্যা কমান। আপনি যদি Java এবং C++ দুটি ভাষাতেই থ্রেড পুল ব্যবহার করতে চান, তাহলে আলাদা আলাদা ওয়ার্কার থ্রেডের মধ্যে JNI কমিউনিকেশন না করে, পুল মালিকদের মধ্যে JNI কমিউনিকেশন করার চেষ্টা করুন।
  • ভবিষ্যতে রিফ্যাক্টর করার সুবিধা পেতে, আপনার ইন্টারফেস কোড কম সংখ্যক সহজে শনাক্ত করা যায় এমন C++ এবং Java সোর্স লোকেশনে রাখুন। প্রয়োজন অনুযায়ী JNI অটো-জেনারেশন লাইব্রেরি ব্যবহার করার কথা বিবেচনা করুন।

JavaVM এবং JNIEnv

JNI দুটি মূল ডেটা স্ট্রাকচারকে সংজ্ঞায়িত করে, "JavaVM" এবং "JNIEnv"। এই দুটিই মূলত ফাংশন টেবিলের পয়েন্টারের পয়েন্টার। (C++ ভার্সনে, এগুলি হল এমন ক্লাস যাতে ফাংশন টেবিলের পয়েন্টার এবং প্রতিটি JNI ফাংশনের জন্য মেম্বার ফাংশন থাকে যা টেবিলের মাধ্যমে ইনডাইরেক্ট করে।) JavaVM "ইনভোকেশন ইন্টারফেস" ফাংশন প্রদান করে, যা আপনাকে JavaVM তৈরি ও ধ্বংস করার অনুমতি দেয়। তত্ত্বগতভাবে, প্রতিটি প্রসেসের জন্য একাধিক JavaVM থাকতে পারে, কিন্তু Android শুধুমাত্র একটির অনুমতি দেয়।

JNIEnv বেশিরভাগ JNI ফাংশন প্রদান করে। আপনার নেটিভ ফাংশনগুলি প্রথম আর্গুমেন্ট হিসেবে JNIEnv গ্রহণ করে, তবে @CriticalNative পদ্ধতির ক্ষেত্রে এটি প্রযোজ্য নয়, আরও দ্রুত নেটিভ কল দেখুন।

থ্রেড-লোকাল স্টোরেজের জন্য JNIEnv ব্যবহার করা হয়। এই কারণে, আপনি থ্রেডের মধ্যে JNIEnv শেয়ার করতে পারবেন না। কোনও কোডের JNIEnv পাওয়ার অন্য কোনও উপায় না থাকলে, আপনার JavaVM শেয়ার করা এবং থ্রেডের JNIEnv খুঁজে পেতে GetEnv ব্যবহার করা উচিত। (ধরে নেওয়া হচ্ছে যে, এটিতে একটি আছে; নিচে AttachCurrentThread দেখুন।)

JNIEnv এবং JavaVM-এর C ডিক্লারেশন C++ ডিক্লারেশনের থেকে আলাদা। "jni.h" ইনক্লুড ফাইলটি C বা C++-এ অন্তর্ভুক্ত কিনা তার উপর নির্ভর করে বিভিন্ন typedef প্রদান করে। এই কারণে, উভয় ভাষার অন্তর্ভুক্ত হেডার ফাইলে JNIEnv আর্গুমেন্ট অন্তর্ভুক্ত করা একটি খারাপ ধারণা। (অন্যভাবে বলা যায়: আপনার হেডার ফাইলের জন্য #ifdef __cplusplus প্রয়োজন হলে, সেই হেডারের কোনও কিছু JNIEnv-কে রেফার করলে, আপনাকে কিছু অতিরিক্ত কাজ করতে হতে পারে।)

থ্রেড

সব থ্রেড হল Linux থ্রেড, যা কার্নেল দ্বারা শিডিউল করা হয়। এগুলি সাধারণত ম্যানেজ করা কোড (Thread.start() ব্যবহার করে) থেকে শুরু হয়, তবে এগুলি অন্য কোথাও তৈরি করে JavaVM-এর সাথে অ্যাটাচ করা যেতে পারে। যেমন, pthread_create() বা std::thread দিয়ে শুরু হওয়া থ্রেড AttachCurrentThread() বা AttachCurrentThreadAsDaemon() ফাংশন ব্যবহার করে অ্যাটাচ করা যেতে পারে। কোনও থ্রেড অ্যাটাচ না করা পর্যন্ত, এটির কোনও JNIEnv থাকে না এবং JNI কল করতে পারে না।

Java কোডে কল করার প্রয়োজন আছে এমন যেকোনও থ্রেড তৈরি করতে সাধারণত Thread.start() ব্যবহার করা সবচেয়ে ভালো। এটি করলে, আপনার কাছে পর্যাপ্ত স্ট্যাক স্পেস আছে, আপনি সঠিক ThreadGroup-এ আছেন এবং আপনি Java কোডের মতো একই ClassLoader ব্যবহার করছেন কিনা তা নিশ্চিত করা যাবে। এছাড়াও, নেটিভ কোডের (আপনার কাছে pthread_t বা thread_t থাকলে pthread_setname_np() দেখুন এবং আপনার কাছে std::thread থাকলে এবং pthread_t চাইলে std::thread::native_handle() দেখুন) থেকে Java-তে ডিবাগিংয়ের জন্য থ্রেডের নাম সেট করা আরও সহজ।

নেটিভভাবে তৈরি থ্রেড অ্যাটাচ করলে java.lang.Thread অবজেক্ট তৈরি হয় এবং "প্রধান" ThreadGroup-এ যোগ হয়, যার ফলে এটি ডিবাগার দেখতে পায়। আগে থেকেই অ্যাটাচ করা থ্রেডে AttachCurrentThread() কল করা হল নো-অপ।

Android নেটিভ কোড এক্সিকিউট করা থ্রেড সাসপেন্ড করে না। যদি গার্বেজ কালেকশন প্রসেস চলছে, অথবা ডিবাগার সাসপেন্ড করার অনুরোধ করেছে, তাহলে Android পরের বার JNI কল করার সময় থ্রেড পজ করবে।

JNI-এর মাধ্যমে অ্যাটাচ করা থ্রেড বেরিয়ে যাওয়ার আগে অবশ্যই DetachCurrentThread() কল করতে হবে। সরাসরি এটি কোড করা কঠিন হলে, Android 2.0 (Eclair) এবং তার পরবর্তী ভার্সনে আপনি pthread_key_create() ব্যবহার করে ডেস্ট্রাক্টর ফাংশন সংজ্ঞায়িত করতে পারেন যা থ্রেড এক্সিট করার আগে কল করা হবে এবং সেখান থেকে DetachCurrentThread() কল করতে পারেন। (থ্রেড-লোকাল-স্টোরেজে JNIEnv স্টোর করতে pthread_setspecific()-এর সাথে সেই কী ব্যবহার করুন; এইভাবে এটি আপনার ডেস্ট্রাক্টরে আর্গুমেন্ট হিসেবে পাস করা হবে।)

jclass, jmethodID, and 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()
    }
}

জাভা

    /*
     * 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-এর মাধ্যমে ম্যানুয়ালি সেগুলি ফ্রি করা উচিত। ১৬টি লোকাল রেফারেন্সের জন্য স্লট রিজার্ভ করার জন্য ইমপ্লিমেন্টেশন প্রয়োজন, তাই আপনার এর থেকে বেশি প্রয়োজন হলে, আপনি হয় কাজ করার সময় মুছে দিন অথবা আরও রিজার্ভ করার জন্য 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 স্ট্রিং ফাংশনের সাথে ব্যবহার করার জন্য উপযুক্ত। খারাপ দিকটি হল, আপনি JNI-তে ইচ্ছামতো UTF-8 ডেটা পাস করতে পারবেন না এবং এটি সঠিকভাবে কাজ করবে বলে আশা করতে পারবেন না।

String-এর UTF-16 উপস্থাপনা পেতে, GetStringChars ব্যবহার করুন। মনে রাখবেন যে UTF-16 স্ট্রিং জিরো-টার্মিনেটেড নয় এবং \u0000 অনুমোদিত, তাই আপনাকে স্ট্রিংয়ের দৈর্ঘ্য এবং jchar পয়েন্টারও ধরে রাখতে হবে।

আপনার Get করা স্ট্রিং Release করতে ভুলবেন না। The string functions return jchar* or jbyte*, which are C-style pointers to primitive data rather than local references. Release কল না করা পর্যন্ত এগুলি বৈধ থাকার গ্যারেন্টি দেওয়া হয়, যার অর্থ হল নেটিভ পদ্ধতি রিটার্ন করার সময় এগুলি রিলিজ করা হয় না।

NewStringUTF-এ পাস করা ডেটা অবশ্যই পরিবর্তিত UTF-8 ফর্ম্যাটে হতে হবে। একটি সাধারণ ভুল হল ফাইল বা নেটওয়ার্ক স্ট্রিম থেকে ক্যারেক্টার ডেটা পড়া এবং ফিল্টার না করেই NewStringUTF-এ তা হ্যান্ডেল করা। ডেটা যে সঠিক MUTF-8 (বা ৭-বিট ASCII, যা একটি মানানসই সাবসেট) তা না জানলে, আপনাকে ভুল অক্ষরগুলি বাদ দিতে হবে বা সেগুলিকে সঠিক পরিবর্তিত UTF-8 ফর্মে কনভার্ট করতে হবে। আপনি তা না করলে, UTF-16 কনভার্সন থেকে অপ্রত্যাশিত ফলাফল পাওয়ার সম্ভাবনা থাকে। CheckJNI—যা এমুলেটরের জন্য ডিফল্ট হিসেবে চালু থাকে—স্ট্রিং স্ক্যান করে এবং ভুল ইনপুট পেলে VM বাতিল করে দেয়।

Android 8-এর আগে, UTF-16 স্ট্রিংয়ের মাধ্যমে কাজ করা সাধারণত দ্রুত ছিল কারণ Android-এর GetStringChars-এ কপি করার প্রয়োজন ছিল না, যেখানে GetStringUTFChars-এর জন্য UTF-8-এ অ্যালোকেশন ও কনভার্সন প্রয়োজন ছিল। Android 8, ASCII স্ট্রিংয়ের (মেমরি সেভ করার জন্য) প্রতিটি অক্ষরের জন্য 8 বিট ব্যবহার করতে String রিপ্রেজেন্টেশন পরিবর্তন করেছে এবং মুভিং গার্বেজ কালেক্টর ব্যবহার করা শুরু করেছে। এইসব ফিচার এমন ঘটনার সংখ্যা অনেক কমিয়ে দেয় যেখানে 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 কল ব্যর্থ হলে, আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনার কোডটি পরে একটি NULL পয়েন্টার Release করার চেষ্টা করবে না।

isCopy আর্গুমেন্টের জন্য non-NULL পয়েন্টার পাস করে ডেটা কপি করা হয়েছে কিনা তা আপনি নির্ধারণ করতে পারবেন। এটি খুব কমই কাজে লাগে।

Release কলে একটি mode আর্গুমেন্ট থাকে যার তিনটি ভ্যালুর মধ্যে একটি থাকতে পারে। রানটাইম দ্বারা সম্পাদিত অ্যাকশনগুলি নির্ভর করে এটি আসল ডেটার একটি পয়েন্টার বা এটির একটি কপি ফেরত দিয়েছে কিনা তার উপর:

  • 0
    • আসল: অ্যারে অবজেক্ট আন-পিন করা আছে।
    • কপি: ডেটা আবার কপি করা হয়। কপি সহ বাফারটি খালি করা হয়েছে।
  • JNI_COMMIT
    • আসল: কিছুই করে না।
    • কপি: ডেটা আবার কপি করা হয়। কপি সহ বাফার ফ্রি করা হয়নি।
  • JNI_ABORT
    • আসল: অ্যারে অবজেক্ট আন-পিন করা হয়েছে। আগের রাইট বাতিল করা হয়নি।
    • কপি: কপি সহ বাফার খালি করা হয়েছে; এতে করা যেকোনও পরিবর্তন হারিয়ে গেছে।

isCopy ফ্ল্যাগ চেক করার একটি কারণ হল, অ্যারেতে পরিবর্তন করার পরে আপনাকে JNI_COMMIT-এর সাথে Release কল করতে হবে কিনা তা জানা — আপনি যদি পরিবর্তন করা এবং অ্যারের কন্টেন্ট ব্যবহার করে এমন কোড কার্যকর করার মধ্যে পরিবর্তন করেন, তাহলে আপনি নো-অপ কমিট এড়িয়ে যেতে পারবেন। ফ্ল্যাগ চেক করার আরেকটি সম্ভাব্য কারণ হল JNI_ABORT-এর দক্ষ হ্যান্ডলিং। যেমন, আপনি হয়ত একটি অ্যারে পেতে, সেটিকে মডিফাই করতে, অন্যান্য ফাংশনে টুকরো পাস করতে এবং তারপর পরিবর্তনগুলি বাতিল করতে চান। আপনি যদি জানেন যে JNI আপনার জন্য একটি নতুন কপি তৈরি করছে, তাহলে আরেকটি "এডিট করা যাবে" এমন কপি তৈরি করার প্রয়োজন নেই। JNI আপনাকে আসলটি পাঠালে, আপনাকে নিজের কপি তৈরি করতে হবে।

*isCopy-এর মান false হলে, Release কল এড়িয়ে যাওয়া যাবে বলে ধরে নেওয়া একটি সাধারণ ভুল (উদাহরণ কোডে বারবার করা হয়েছে)। এটি সেই কেস নয়। কোনও কপি বাফার বরাদ্দ করা না হলে, আসল মেমরি অবশ্যই পিন ডাউন করতে হবে এবং গার্বেজ কালেক্টর দ্বারা সরানো যাবে না।

এছাড়াও মনে রাখবেন যে 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 থেকে অক্ষর কপি করতে পারবেন।

ব্যতিক্রম

ব্যতিক্রম পেন্ডিং থাকলে আপনাকে বেশিরভাগ JNI ফাংশন কল করলে চলবে না। আপনার কোড ব্যতিক্রমটি লক্ষ্য করবে (ফাংশনের রিটার্ন ভ্যালু, ExceptionCheck, বা ExceptionOccurred-এর মাধ্যমে) এবং রিটার্ন করবে, অথবা ব্যতিক্রমটি মুছে ফেলবে এবং এটি ম্যানেজ করবে বলে আশা করা হচ্ছে।

ব্যতিক্রম পেন্ডিং থাকার সময় আপনি শুধুমাত্র এইসব JNI ফাংশন কল করতে পারবেন:

  • DeleteGlobalRef
  • DeleteLocalRef
  • DeleteWeakGlobalRef
  • ExceptionCheck
  • ExceptionClear
  • ExceptionDescribe
  • ExceptionOccurred
  • MonitorExit
  • PopLocalFrame
  • PushLocalFrame
  • Release<PrimitiveType>ArrayElements
  • ReleasePrimitiveArrayCritical
  • ReleaseStringChars
  • ReleaseStringCritical
  • ReleaseStringUTFChars

অনেক JNI কল ব্যতিক্রম দেখাতে পারে, তবে প্রায়ই ব্যর্থতা চেক করার সহজ উপায় প্রদান করে। যেমন, NewString যদি নন-NULL ভ্যালু রিটার্ন করে, তাহলে আপনাকে ব্যতিক্রম চেক করতে হবে না। তবে, আপনি যদি কোনও মেথড কল করেন (CallObjectMethod-এর মতো ফাংশন ব্যবহার করে), আপনাকে সবসময় ব্যতিক্রম চেক করতে হবে, কারণ ব্যতিক্রম থ্রো করা হলে রিটার্ন ভ্যালু বৈধ হবে না।

মনে রাখবেন যে ম্যানেজ করা কোডের মাধ্যমে থ্রো করা ব্যতিক্রম নেটিভ স্ট্যাক ফ্রেম আনওয়াইন্ড করে না। (এবং C++ ব্যতিক্রম, সাধারণত Android-এ নিরুৎসাহিত করা হয়, JNI ট্রানজিশন বাউন্ডারি জুড়ে C++ কোড থেকে ম্যানেজ করা কোডে থ্রো করা যাবে না।) JNI Throw এবং ThrowNew নির্দেশাবলী শুধু বর্তমান থ্রেডে একটি ব্যতিক্রম পয়েন্টার সেট করে। নেটিভ কোড থেকে ম্যানেজড কোডে ফিরে আসার পরে, ব্যতিক্রমটি নোট করা হবে এবং যথাযথভাবে ম্যানেজ করা হবে।

নেটিভ কোড ExceptionCheck বা ExceptionOccurred কল করে ব্যতিক্রম "ক্যাচ" করতে পারে এবং ExceptionClear-এর মাধ্যমে এটি মুছে দিতে পারে। সাধারণত, ব্যতিক্রমগুলি সমাধান না করে বাতিল করলে সমস্যা হতে পারে।

Throwable অবজেক্ট ম্যানিপুলেট করার জন্য কোনও বিল্ট-ইন ফাংশন নেই, তাই আপনি যদি (ধরা যাক) ব্যতিক্রম স্ট্রিং পেতে চান, তাহলে আপনাকে Throwable ক্লাস খুঁজে বের করতে হবে, getMessage "()Ljava/lang/String;"-এর জন্য মেথড আইডি খুঁজতে হবে, এটি চালু করতে হবে এবং ফলাফল নন-NULL হলে, GetStringUTFChars ব্যবহার করে এমন কিছু পেতে হবে যা আপনি printf(3) বা সমতুল্য কিছুতে হ্যান্ডওভার করতে পারবেন।

এক্সটেন্ডেড চেকিং

JNI খুব কম ত্রুটি চেক করে। সাধারণত সমস্যার কারণে ক্র্যাশ হয়। Android-এ CheckJNI নামে একটি মোডও আছে, যেখানে JavaVM এবং JNIEnv ফাংশন টেবিল পয়েন্টারগুলি ফাংশনের টেবিলে পাল্টে যায় যা স্ট্যান্ডার্ড ইমপ্লিমেন্টেশন কল করার আগে চেকের একটি এক্সটেন্ডেড সিরিজ পারফর্ম করে।

অতিরিক্ত চেকের মধ্যে এগুলি অন্তর্ভুক্ত:

  • অ্যারে: নেগেটিভ-সাইজের অ্যারে বরাদ্দ করার চেষ্টা করা হচ্ছে।
  • খারাপ পয়েন্টার: JNI কলে খারাপ jarray/jclass/jobject/jstring পাস করা অথবা নালেবল নয় এমন আর্গুমেন্ট সহ JNI কলে NULL পয়েন্টার পাস করা।
  • ক্লাসের নাম: JNI কলে ক্লাসের নামের “java/lang/String” স্টাইল ছাড়া অন্য কিছু পাস করা।
  • গুরুত্বপূর্ণ কল: "গুরুত্বপূর্ণ" গেট ও তার সাথে সম্পর্কিত রিলিজের মধ্যে JNI কল করা।
  • Direct ByteBuffers: NewDirectByteBuffer-এ ভুল আর্গুমেন্ট পাস করা হচ্ছে।
  • ব্যতিক্রম: ব্যতিক্রম পেন্ডিং থাকাকালীন JNI কল করা।
  • JNIEnv*s: ভুল থ্রেড থেকে JNIEnv* ব্যবহার করা হচ্ছে।
  • jfieldIDs: NULL jfieldID ব্যবহার করা অথবা ভুল ধরনের মান সহ ফিল্ড সেট করার জন্য jfieldID ব্যবহার করা (যেমন, String ফিল্ডে StringBuilder অ্যাসাইন করার চেষ্টা করা) অথবা ইনস্ট্যান্স ফিল্ড সেট করার জন্য স্ট্যাটিক ফিল্ডের jfieldID ব্যবহার করা বা এর উল্টোটা করা অথবা অন্য ক্লাসের ইনস্ট্যান্স সহ একটি ক্লাসের jfieldID ব্যবহার করা।
  • jmethodIDs: Call*Method JNI কল করার সময় ভুল ধরনের jmethodID ব্যবহার করা: ভুল রিটার্ন টাইপ, স্ট্যাটিক/নন-স্ট্যাটিক অমিল, ‘this’-এর জন্য ভুল টাইপ (নন-স্ট্যাটিক কলের জন্য) বা ভুল ক্লাস (স্ট্যাটিক কলের জন্য)।
  • রেফারেন্স: ভুল ধরনের রেফারেন্সে DeleteGlobalRef/DeleteLocalRef ব্যবহার করা।
  • রিলিজ মোড: রিলিজ কলে ভুল রিলিজ মোড পাস করা (0, JNI_ABORT বা JNI_COMMIT ছাড়া অন্য কিছু)।
  • টাইপ নিরাপত্তা: আপনার নেটিভ পদ্ধতি থেকে একটি বেমানান টাইপ রিটার্ন করা (যেমন, একটি স্ট্রিং রিটার্ন করার জন্য ঘোষিত পদ্ধতি থেকে একটি StringBuilder রিটার্ন করা)।
  • UTF-8: JNI কলে ভুল পরিবর্তিত UTF-8 বাইট সিকোয়েন্স পাস করা হচ্ছে।

(পদ্ধতি ও ফিল্ডের অ্যাক্সেসিবিলিটি এখনও চেক করা হয়নি: অ্যাক্সেস সংক্রান্ত বিধিনিষেধ নেটিভ কোডের ক্ষেত্রে প্রযোজ্য নয়।)

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()-এর মাধ্যমে ডাইনামিকভাবে সেগুলি খুঁজতে দিতে পারেন। অধিকাংশ অ্যাপের ক্ষেত্রে JNI_OnLoad() থেকে RegisterNatives() কল করা উচিত। এটি স্পষ্ট ম্যাপিং সহ নেটিভ পদ্ধতির আপ-ফ্রন্ট রেজিস্ট্রেশন করে। এই কোড লিখতে একটু বেশি আপ-ফ্রন্ট কাজ করতে হয়, তবে এর নিম্নলিখিত সুবিধাগুলি রয়েছে:

  1. ভুলগুলি আগেই ধরা পড়ে। লাইব্রেরি লোড করার সময় ভুল নামযুক্ত ফাংশনের কারণে সমস্যা হবে।
  2. সাধারণত, শুধুমাত্র JNI_OnLoad() চিহ্নটি এক্সপোর্ট করতে হবে। কিছু ব্যবহারের ক্ষেত্রে অতিরিক্তভাবে ANativeActivity_OnCreate() বা একই ধরনের কিছু প্রয়োজন হতে পারে।
  3. Android Studio আপনাকে সতর্ক করতে পারে এবং ক্লাস নেম ও ফাংশন সিগনেচারে হওয়া সাধারণ ভুলগুলি অটোমেটিক ঠিক করতে পারে।

এর বিপরীতে, রানটাইমকে আপনার ফাংশনগুলি আবিষ্কার করতে দেওয়ার সুবিধা dlsym() হল যে এটি লেখার জন্য একটু কম কোড, এবং সস্তা যেখানে আপনার JNI ফাংশন সাধারণত কল করা হয় না।

RegisterNatives ব্যবহার করতে:

  • JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) ফাংশন প্রদান করুন।
  • আপনার JNI_OnLoad-এ, RegisterNatives ব্যবহার করে আপনার সব নেটিভ পদ্ধতি রেজিস্টার করুন।
  • ভার্সন স্ক্রিপ্ট (পছন্দসই) দিয়ে তৈরি করুন অথবা -fvisibility=hidden ব্যবহার করুন যাতে আপনার লাইব্রেরি থেকে শুধু JNI_OnLoad এক্সপোর্ট করা হয়। এটি আরও দ্রুত এবং ছোট কোড তৈরি করে এবং আপনার অ্যাপে লোড করা অন্যান্য লাইব্রেরির সাথে সম্ভাব্য সংঘর্ষ এড়ায় (তবে আপনার অ্যাপ নেটিভ কোডে ক্র্যাশ করলে এটি কম উপযোগী স্ট্যাক ট্রেস তৈরি করে)।

স্ট্যাটিক ইনিশিয়ালাইজারটি এইরকম দেখতে হওয়া উচিত:

Kotlin

companion object {
    init {
        System.loadLibrary("fubar")
    }
}

জাভা

static {
    System.loadLibrary("fubar");
}

C++-এ লেখা হলে JNI_OnLoad ফাংশনটি দেখতে এইরকম হবে:

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;
}

পরিবর্তে নেটিভ পদ্ধতির "ডিসকভারি" ব্যবহার করতে, আপনাকে একটি নির্দিষ্ট উপায়ে সেগুলির নাম দিতে হবে (বিস্তারিত জানার জন্য JNI স্পেসিফিকেশন দেখুন)। এর অর্থ হল, কোনও মেথড সিগনেচার ভুল হলে, প্রথমবার মেথডটি আসলে ইনভোক না করা পর্যন্ত আপনি সেটি জানতে পারবেন না।

JNI_OnLoad থেকে করা যেকোনও FindClass কল, শেয়ার করা লাইব্রেরি লোড করার জন্য ব্যবহৃত ক্লাস লোডারের প্রসঙ্গে ক্লাস সমাধান করবে। অন্যান্য প্রসঙ্গ থেকে কল করা হলে, FindClass জাভা স্ট্যাকের শীর্ষে থাকা পদ্ধতির সাথে যুক্ত ক্লাস লোডার ব্যবহার করে, অথবা যদি কোনওটি না থাকে (কারণ কলটি একটি নেটিভ থ্রেড থেকে এসেছে যা এইমাত্র অ্যাটাচ করা হয়েছে) এটি "সিস্টেম" ক্লাস লোডার ব্যবহার করে। সিস্টেম ক্লাস লোডার আপনার অ্যাপ্লিকেশনের ক্লাস সম্পর্কে জানে না, তাই আপনি সেই প্রসঙ্গে FindClass-এর মাধ্যমে নিজের ক্লাস দেখতে পাবেন না। এর ফলে JNI_OnLoad ক্লাস খোঁজা ও ক্যাশে করার জন্য একটি সুবিধাজনক জায়গা হয়ে ওঠে: আপনার কাছে একটি সঠিক jclass গ্লোবাল রেফারেন্স থাকলে, আপনি যেকোনও অ্যাটাচ করা থ্রেড থেকে এটি ব্যবহার করতে পারবেন।

@FastNative ও @CriticalNative-এর সাথে আরও দ্রুত নেটিভ কল

ম্যানেজ করা ও নেটিভ কোডের মধ্যে ট্রানজিশন দ্রুত করতে নেটিভ পদ্ধতিকে @FastNative বা @CriticalNative (কিন্তু দুটিই নয়) দিয়ে অ্যানোটেট করা যেতে পারে। তবে, এই অ্যানোটেশনগুলি ব্যবহারের আগে কিছু পরিবর্তন সম্পর্কে সতর্ক থাকতে হবে। আমরা নিচে এইসব পরিবর্তন সংক্ষেপে উল্লেখ করলেও, বিস্তারিত জানতে ডকুমেন্টেশন দেখুন।

@CriticalNative অ্যানোটেশন শুধুমাত্র নেটিভ পদ্ধতিতে প্রয়োগ করা যেতে পারে যা ম্যানেজ করা অবজেক্ট ব্যবহার করে না (প্যারামিটার বা রিটার্ন ভ্যালুতে, বা অন্তর্নিহিত this হিসেবে), এবং এই অ্যানোটেশন JNI ট্রানজিশন ABI পরিবর্তন করে। নেটিভ ইমপ্লিমেন্টেশনে ফাংশন সিগনেচার থেকে JNIEnv এবং jclass প্যারামিটার বাদ দিতে হবে।

@FastNative বা @CriticalNative পদ্ধতি এক্সিকিউট করার সময়, গার্বেজ কালেকশন প্রয়োজনীয় কাজের জন্য থ্রেড সাসপেন্ড করতে পারে না এবং ব্লক হয়ে যেতে পারে। সাধারণত দ্রুত, কিন্তু সাধারণত সীমাহীন, পদ্ধতি সহ দীর্ঘ-চলমান পদ্ধতির জন্য এই অ্যানোটেশনগুলি ব্যবহার করবেন না। বিশেষ করে, কোডটি যেন উল্লেখযোগ্য I/O অপারেশন না করে বা নেটিভ লক না নেয় যা দীর্ঘ সময়ের জন্য ধরে রাখা যেতে পারে।

এইসব অ্যানোটেশন Android 8 থেকে সিস্টেম ব্যবহারের জন্য প্রয়োগ করা হয়েছে এবং Android 14-এ CTS-পরীক্ষিত পাবলিক API হয়েছে। এই অপ্টিমাইজেশনগুলি Android 8-13 ডিভাইসেও কাজ করতে পারে (যদিও শক্তিশালী CTS গ্যারান্টি ছাড়াই) তবে নেটিভ পদ্ধতির ডাইনামিক লুকআপ শুধুমাত্র Android 12+ ভার্সনে কাজ করে, Android ভার্সন 8-11-এ চালানোর জন্য JNI RegisterNatives-এর সাথে এক্সপ্লিসিট রেজিস্ট্রেশন কঠোরভাবে প্রয়োজন। Android 7-এ এই অ্যানোটেশনগুলি উপেক্ষা করা হয়, @CriticalNative-এর জন্য ABI অমিল ভুল আর্গুমেন্ট মার্শালিং এবং সম্ভাব্য ক্র্যাশের দিকে পরিচালিত করবে।

পারফর্ম্যান্সের জন্য গুরুত্বপূর্ণ যেসব পদ্ধতিতে এই অ্যানোটেশন প্রয়োজন, সেগুলির ক্ষেত্রে নেটিভ পদ্ধতির নাম-ভিত্তিক "ডিসকভারি"-র উপর নির্ভর না করে JNI RegisterNatives-এর মাধ্যমে পদ্ধতি(গুলি) স্পষ্টভাবে রেজিস্টার করার জন্য জোরালোভাবে সাজেস্ট করা হয়। অ্যাপ স্টার্টআপের সেরা পারফর্ম্যান্স পেতে, বেসলাইন প্রোফাইলে @FastNative বা @CriticalNative পদ্ধতির কলার অন্তর্ভুক্ত করার সাজেশন দেওয়া হয়। Android 12 থেকে, কম্পাইল করা ম্যানেজড মেথড থেকে @CriticalNative নেটিভ মেথডে কল করা, C/C++-এ নন-ইনলাইন কলের মতোই সস্তা, যতক্ষণ না সব আর্গুমেন্ট রেজিস্টারে ফিট করে (যেমন, arm64-এ ৮টি পর্যন্ত ইন্টিগ্রাল এবং ৮টি পর্যন্ত ফ্লোটিং পয়েন্ট আর্গুমেন্ট)।

কখনও কখনও নেটিভ পদ্ধতিকে দু'ভাগে ভাগ করা ভাল হতে পারে, একটি খুব দ্রুত পদ্ধতি যা ব্যর্থ হতে পারে এবং অন্যটি যা ধীরগতির কেসগুলি পরিচালনা করে। যেমন:

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)

জাভা

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);

৬৪-বিট সংক্রান্ত বিবেচ্য বিষয়

৬৪-বিট পয়েন্টার ব্যবহার করে এমন আর্কিটেকচারকে সাপোর্ট করতে, জাভা ফিল্ডে নেটিভ স্ট্রাকচারের পয়েন্টার সেভ করার সময় int-এর পরিবর্তে long ফিল্ড ব্যবহার করুন।

কাজ করে না এমন ফিচার/ব্যাকওয়ার্ড কম্প্যাটিবিলিটি

নিম্নলিখিত ব্যতিক্রম সহ সব JNI 1.6 ফিচার কাজ করে:

  • DefineClass প্রয়োগ করা হয়নি। Android, Java বাইটকোড বা ক্লাস ফাইল ব্যবহার করে না, তাই বাইনারি ক্লাস ডেটা পাস করলে কাজ হয় না।

Android-এর পুরনো রিলিজের সাথে ব্যাকওয়ার্ড কম্প্যাটিবিলিটির জন্য, আপনাকে এইসব বিষয়ে সচেতন থাকতে হবে:

  • নেটিভ ফাংশনের ডায়নামিক লুকআপ

    Android 2.0 (Eclair) পর্যন্ত, মেথডের নাম সার্চ করার সময় '$' অক্ষরটি "_00024"-এ সঠিকভাবে কনভার্ট করা হয়নি। এটি সমাধান করতে, স্পষ্ট রেজিস্ট্রেশন ব্যবহার করতে হবে অথবা নেটিভ পদ্ধতিগুলি ইনার ক্লাস থেকে সরাতে হবে।

  • থ্রেড ডিটাচ করা হচ্ছে

    Android 2.0 (Eclair) পর্যন্ত, "বেরিয়ে আসার আগে থ্রেড ডিটাচ করতে হবে" চেক এড়িয়ে যেতে pthread_key_create ডেস্ট্রাক্টর ফাংশন ব্যবহার করা সম্ভব ছিল না। (রানটাইম pthread কী ডেস্ট্রাক্টর ফাংশনও ব্যবহার করে, তাই কোনটি প্রথমে কল করা হবে তা দেখার জন্য এটি একটি রেস হবে।)

  • দুর্বল গ্লোবাল রেফারেন্স

    Android 2.2 (Froyo) পর্যন্ত, দুর্বল গ্লোবাল রেফারেন্স প্রয়োগ করা হয়নি। পুরনো ভার্সনগুলি সেগুলি ব্যবহার করার প্রচেষ্টাকে জোরালোভাবে প্রত্যাখ্যান করবে। আপনি Android প্ল্যাটফর্ম ভার্সন কনস্ট্যান্ট ব্যবহার করে সাপোর্ট টেস্ট করতে পারেন।

    Android 4.0 (Ice Cream Sandwich) পর্যন্ত, দুর্বল গ্লোবাল রেফারেন্স শুধুমাত্র NewLocalRef, NewGlobalRef এবং DeleteWeakGlobalRef-এ পাস করা যেত। (স্পেক প্রোগ্রামারদের উইক গ্লোবাল নিয়ে কিছু করার আগে সেগুলির হার্ড রেফারেন্স তৈরি করতে উৎসাহিত করে, তাই এটি কোনওভাবেই সীমাবদ্ধতা তৈরি করবে না।)

    Android 4.0 (Ice Cream Sandwich) থেকে, দুর্বল গ্লোবাল রেফারেন্স অন্য যেকোনও JNI রেফারেন্সের মতো ব্যবহার করা যেতে পারে।

  • স্থানীয় রেফারেন্স

    Android 4.0 (Ice Cream Sandwich) পর্যন্ত, স্থানীয় রেফারেন্স আসলে সরাসরি পয়েন্টার ছিল। Ice Cream Sandwich-এ আরও ভালো গার্বেজ কালেক্টরকে সাপোর্ট করার জন্য প্রয়োজনীয় ইনডিরেকশন যোগ করা হয়েছে, তবে এর অর্থ হল যে পুরনো রিলিজে অনেক JNI সমস্যা শনাক্ত করা যায় না। আরও তথ্যের জন্য ICS-এ JNI লোকাল রেফারেন্স পরিবর্তন দেখুন।

    Android 8.0-এর আগের Android ভার্সনে, লোকাল রেফারেন্সের সংখ্যা ভার্সন-নির্দিষ্ট সীমাতে সীমাবদ্ধ থাকে। Android 8.0 থেকে শুরু করে, Android-এ আনলিমিটেড লোকাল রেফারেন্সের সুবিধা রয়েছে।

  • GetObjectRefType-এর সাথে রেফারেন্সের ধরন নির্ধারণ করা

    Android 4.0 (Ice Cream Sandwich) পর্যন্ত, ডাইরেক্ট পয়েন্টার (উপরে দেখুন) ব্যবহার করার ফলে, GetObjectRefType সঠিকভাবে প্রয়োগ করা অসম্ভব ছিল। পরিবর্তে আমরা একটি হিউরিস্টিক ব্যবহার করেছি যা দুর্বল গ্লোবাল টেবিল, আর্গুমেন্ট, লোকাল টেবিল এবং গ্লোবাল টেবিল সেই ক্রমে দেখেছে। প্রথমবার আপনার ডাইরেক্ট পয়েন্টার খুঁজে পেলে, এটি রিপোর্ট করবে যে আপনার রেফারেন্সটি সেই ধরনের ছিল যা এটি পরীক্ষা করছিল। এর অর্থ হল, উদাহরণস্বরূপ, আপনি যদি গ্লোবাল jclass-এ GetObjectRefType কল করেন যা আপনার স্ট্যাটিক নেটিভ পদ্ধতিতে ইমপ্লিসিট আর্গুমেন্ট হিসেবে পাস করা jclass-এর মতো একই হয়, তাহলে আপনি JNIGlobalRefType-এর পরিবর্তে JNILocalRefType পাবেন।

  • @FastNative এবং @CriticalNative

    Android 7 পর্যন্ত, এই অপ্টিমাইজেশন অ্যানোটেশন উপেক্ষা করা হয়েছিল। @CriticalNative-এর জন্য ABI অমিল হলে ভুল আর্গুমেন্ট মার্শালিং এবং ক্র্যাশ হওয়ার সম্ভাবনা থাকে।

    @FastNative এবং @CriticalNative পদ্ধতির জন্য নেটিভ ফাংশনের ডায়নামিক লুকআপ Android 8-10-এ প্রয়োগ করা হয়নি এবং Android 11-এ পরিচিত সমস্যা রয়েছে। JNI RegisterNatives-এর সাথে স্পষ্টভাবে রেজিস্টার না করে এইসব অপ্টিমাইজেশন ব্যবহার করলে Android 8-11-এ ক্র্যাশ হওয়ার সম্ভাবনা থাকে।

  • FindClass ClassNotFoundException থ্রো করে

    ব্যাকওয়ার্ড কম্প্যাটিবিলিটির জন্য, FindClass-এর মাধ্যমে কোনও ক্লাস খুঁজে না পাওয়া গেলে, Android NoClassDefFoundError-এর পরিবর্তে ClassNotFoundException থ্রো করে। এই আচরণ Java রিফ্লেকশন API Class.forName(name)-এর সাথে সামঞ্জস্যপূর্ণ।

প্রায়শই জিজ্ঞাসিত প্রশ্ন: আমি কেন UnsatisfiedLinkError পাই?

নেটিভ কোড নিয়ে কাজ করার সময় এই ধরনের সমস্যা দেখা দেওয়া অস্বাভাবিক নয়:

java.lang.UnsatisfiedLinkError: Library foo not found

কিছু ক্ষেত্রে এর অর্থ হল যা বলা হয়েছে — লাইব্রেরি খুঁজে পাওয়া যায়নি। অন্যান্য ক্ষেত্রে লাইব্রেরি থাকলেও dlopen(3) সেটি খুলতে পারেনি এবং ব্যতিক্রমের বিস্তারিত মেসেজে ব্যর্থতার বিবরণ পাওয়া যাবে।

"লাইব্রেরি খুঁজে পাওয়া যায়নি" সংক্রান্ত ব্যতিক্রমের সম্মুখীন হওয়ার সাধারণ কারণ:

  • লাইব্রেরিটি নেই বা অ্যাপের মাধ্যমে অ্যাক্সেস করা যাচ্ছে না। এটি আছে কিনা এবং এর অনুমতি চেক করতে adb shell ls -l <path> ব্যবহার করুন।
  • লাইব্রেরিটি NDK দিয়ে তৈরি করা হয়নি। এর ফলে, ডিভাইসে নেই এমন ফাংশন বা লাইব্রেরির উপর নির্ভরতা তৈরি হতে পারে।

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 ব্যবহার করতে পারেন; যদি সেগুলি বিকৃত দেখায় (Java_Foo_myfunc-এর পরিবর্তে _Z15Java_Foo_myfuncP7_JNIEnvP7_jclass-এর মতো কিছু) অথবা যদি প্রতীকের ধরন বড় হাতের 'T'-এর পরিবর্তে ছোট হাতের 't' হয়, তাহলে আপনাকে ঘোষণাটি অ্যাডজাস্ট করতে হবে।
    • স্পষ্ট রেজিস্ট্রেশনের জন্য, পদ্ধতি স্বাক্ষর লেখার সময় ছোটখাটো সমস্যা। রেজিস্ট্রেশন কলে আপনি যা পাস করছেন তা লগ ফাইলের স্বাক্ষরের সাথে মিলছে কিনা তা নিশ্চিত করুন। মনে রাখবেন যে 'B' হল byte এবং 'Z' হল boolean। স্বাক্ষরে ক্লাস নামের কম্পোনেন্ট 'L' দিয়ে শুরু হয়, ';' দিয়ে শেষ হয়, প্যাকেজ/ক্লাসের নাম আলাদা করতে '/' ব্যবহার করে এবং ইনার-ক্লাসের নাম আলাদা করতে '$' ব্যবহার করে (যেমন, Ljava/util/Map$Entry;)।

অটোমেটিক JNI হেডার জেনারেট করার জন্য 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 আপনার অ্যাপ্লিকেশনের সাথে যুক্ত ক্লাস লোডারের পরিবর্তে "সিস্টেম" ক্লাস লোডারে শুরু হবে, তাই অ্যাপ-নির্দিষ্ট ক্লাস খোঁজার চেষ্টা ব্যর্থ হবে।

এই সমস্যা সমাধানের কয়েকটি উপায় আছে:

  • JNI_OnLoad-এ একবার আপনার FindClass লুকআপ করুন এবং পরে ব্যবহারের জন্য ক্লাস রেফারেন্স ক্যাশে করুন। JNI_OnLoad কার্যকর করার অংশ হিসেবে করা যেকোনও FindClass কল System.loadLibrary কল করা ফাংশনের সাথে সম্পর্কিত ক্লাস লোডার ব্যবহার করবে (এটি একটি বিশেষ নিয়ম, যা লাইব্রেরি ইনিশিয়ালাইজেশন আরও সুবিধাজনক করার জন্য প্রদান করা হয়েছে)। আপনার অ্যাপ কোড লাইব্রেরি লোড করলে, FindClass সঠিক ক্লাস লোডার ব্যবহার করবে।
  • যেসব ফাংশনে প্রয়োজন, সেগুলিতে ক্লাসের ইনস্ট্যান্স পাস করুন। এটি করতে, ক্লাস আর্গুমেন্ট নেওয়ার জন্য আপনার নেটিভ পদ্ধতি ঘোষণা করুন এবং তারপর Foo.class পাস করুন।
  • ClassLoader অবজেক্টের রেফারেন্স ক্যাশে করুন, এবং সরাসরি loadClass কল করুন। এটি করতে কিছুটা পরিশ্রম করতে হবে।

প্রায়শই জিজ্ঞাসিত প্রশ্ন: আমি কীভাবে নেটিভ কোডের সাথে কাঁচা ডেটা শেয়ার করব?

এমন পরিস্থিতি তৈরি হতে পারে যেখানে আপনাকে ম্যানেজ করা ও নেটিভ কোড, উভয় থেকেই র ডেটার একটি বড় বাফার অ্যাক্সেস করতে হবে। সাধারণ উদাহরণের মধ্যে বিটম্যাপ বা সাউন্ড স্যাম্পেল ম্যানিপুলেট করা অন্তর্ভুক্ত। দুটি প্রাথমিক পদ্ধতি আছে।

আপনি byte[]-এ ডেটা স্টোর করতে পারবেন। এটি ম্যানেজ করা কোড থেকে খুব দ্রুত অ্যাক্সেস করার সুবিধা দেয়। তবে, নেটিভ সাইডে, কপি না করে ডেটা অ্যাক্সেস করতে পারবেন কিনা তার কোনও গ্যারান্টি নেই। কিছু ইমপ্লিমেন্টেশনে, GetByteArrayElements এবং GetPrimitiveArrayCritical ম্যানেজ করা হিপে র ডেটার প্রকৃত পয়েন্টার ফেরত দেবে, কিন্তু অন্যান্য ক্ষেত্রে এটি নেটিভ হিপে বাফার অ্যালোকেট করবে এবং ডেটা কপি করবে।

বিকল্প হিসেবে, ডেটা সরাসরি বাইট বাফারে স্টোর করা যায়। এগুলি java.nio.ByteBuffer.allocateDirect বা JNI NewDirectByteBuffer ফাংশনের মাধ্যমে তৈরি করা যেতে পারে। সাধারণ বাইট বাফারের মতো, স্টোরেজ ম্যানেজ করা হিপে বরাদ্দ করা হয় না এবং সর্বদা নেটিভ কোড থেকে সরাসরি অ্যাক্সেস করা যেতে পারে (GetDirectBufferAddress-এর মাধ্যমে ঠিকানা পান)। সরাসরি বাইট বাফার অ্যাক্সেস কীভাবে প্রয়োগ করা হয় তার উপর নির্ভর করে, ম্যানেজ করা কোড থেকে ডেটা অ্যাক্সেস করা খুব ধীর হতে পারে।

কোনটি ব্যবহার করবেন তা দুটি বিষয়ের উপর নির্ভর করে:

  1. অধিকাংশ ডেটা অ্যাক্সেস কি Java বা C/C++-এ লেখা কোড থেকে হবে?
  2. ডেটা যদি শেষ পর্যন্ত কোনও সিস্টেম API-তে পাস করা হয়, তাহলে সেটি কোন ফর্মে থাকতে হবে? (যেমন, ডেটা যদি শেষ পর্যন্ত এমন কোনও ফাংশনে পাস করা হয় যা byte[] নেয়, তাহলে সরাসরি ByteBuffer-এ প্রসেস করা বুদ্ধিমানের কাজ নাও হতে পারে।)

যদি কোনও স্পষ্ট উইনার না থাকে, তাহলে সরাসরি বাইট বাফার ব্যবহার করুন। এগুলির জন্য সহায়তা সরাসরি JNI-তে তৈরি করা হয়েছে এবং ভবিষ্যতে রিলিজের ক্ষেত্রে পারফর্ম্যান্স আরও উন্নত হবে।