JNI হল জাভা নেটিভ ইন্টারফেস। এটি বাইটকোডের জন্য একটি উপায় সংজ্ঞায়িত করে যা অ্যান্ড্রয়েড পরিচালিত কোড থেকে সংকলন করে (জাভা বা কোটলিন প্রোগ্রামিং ভাষায় লেখা) নেটিভ কোডের সাথে ইন্টারঅ্যাক্ট করার জন্য (C/C++ এ লেখা)। JNI বিক্রেতা-নিরপেক্ষ, ডাইনামিক শেয়ার্ড লাইব্রেরি থেকে কোড লোড করার জন্য সমর্থন আছে এবং মাঝে মাঝে কষ্টকর হলেও যুক্তিসঙ্গতভাবে দক্ষ।
দ্রষ্টব্য: যেহেতু অ্যান্ড্রয়েড জাভা প্রোগ্রামিং ভাষার মতোই কোটলিনকে এআরটি-বান্ধব বাইটকোডের সাথে কম্পাইল করে, আপনি এই পৃষ্ঠার নির্দেশিকাটি জেএনআই আর্কিটেকচার এবং এর সাথে সম্পর্কিত খরচের ক্ষেত্রে কোটলিন এবং জাভা প্রোগ্রামিং উভয় ভাষাতেই প্রয়োগ করতে পারেন। আরও জানতে, কোটলিন এবং অ্যান্ড্রয়েড দেখুন।
আপনি যদি ইতিমধ্যে এটির সাথে পরিচিত না হন, তাহলে JNI কীভাবে কাজ করে এবং কী কী বৈশিষ্ট্য উপলব্ধ তা বোঝার জন্য জাভা নেটিভ ইন্টারফেস স্পেসিফিকেশন পড়ুন। ইন্টারফেসের কিছু দিক প্রথম পড়ার সময় অবিলম্বে স্পষ্ট হয় না, তাই আপনি পরবর্তী কয়েকটি বিভাগ সহজ খুঁজে পেতে পারেন।
গ্লোবাল জেএনআই রেফারেন্স ব্রাউজ করতে এবং গ্লোবাল জেএনআই রেফারেন্সগুলি কোথায় তৈরি এবং মুছে ফেলা হয়েছে তা দেখতে, অ্যান্ড্রয়েড স্টুডিও 3.2 এবং উচ্চতর মেমরি প্রোফাইলে জেএনআই হিপ ভিউ ব্যবহার করুন।
সাধারণ টিপস
আপনার JNI স্তরের পদচিহ্ন ছোট করার চেষ্টা করুন। এখানে বিবেচনা করার জন্য বিভিন্ন মাত্রা আছে। আপনার JNI সমাধানের এই নির্দেশিকাগুলি অনুসরণ করার চেষ্টা করা উচিত (গুরুত্বের ক্রম অনুসারে নীচে তালিকাভুক্ত, সবচেয়ে গুরুত্বপূর্ণ দিয়ে শুরু):
- JNI স্তর জুড়ে সম্পদের মার্শালিং কম করুন। JNI স্তর জুড়ে মার্শালিং অ তুচ্ছ খরচ আছে. একটি ইন্টারফেস ডিজাইন করার চেষ্টা করুন যা আপনাকে মার্শাল করার জন্য প্রয়োজনীয় ডেটার পরিমাণ এবং যে ফ্রিকোয়েন্সি দিয়ে আপনাকে ডেটা মার্শাল করতে হবে তা কম করে।
- একটি পরিচালিত প্রোগ্রামিং ভাষায় লেখা কোড এবং সম্ভব হলে C++ এ লেখা কোডের মধ্যে অসিঙ্ক্রোনাস যোগাযোগ এড়িয়ে চলুন। এটি আপনার JNI ইন্টারফেস বজায় রাখা সহজ করবে। আপনি সাধারণত UI এর মতো একই ভাষায় অ্যাসিঙ্ক আপডেট রেখে অ্যাসিঙ্ক্রোনাস UI আপডেটগুলিকে সহজ করতে পারেন। উদাহরণস্বরূপ, JNI এর মাধ্যমে জাভা কোডে UI থ্রেড থেকে একটি C++ ফাংশন আহ্বান করার পরিবর্তে, জাভা প্রোগ্রামিং ভাষায় দুটি থ্রেডের মধ্যে একটি কলব্যাক করা ভাল, তাদের মধ্যে একটি ব্লকিং C++ কল করে এবং তারপরে UI থ্রেডকে অবহিত করে। ব্লকিং কল সম্পূর্ণ হলে।
- JNI দ্বারা স্পর্শ করা বা স্পর্শ করা প্রয়োজন এমন থ্রেডের সংখ্যা কমিয়ে দিন। আপনার যদি জাভা এবং C++ উভয় ভাষাতেই থ্রেড পুল ব্যবহার করার প্রয়োজন হয়, তবে পৃথক কর্মী থ্রেডের মধ্যে না হয়ে পুলের মালিকদের মধ্যে JNI যোগাযোগ রাখার চেষ্টা করুন।
- ভবিষ্যৎ রিফ্যাক্টরদের সুবিধার্থে আপনার ইন্টারফেস কোড সহজে চিহ্নিত C++ এবং জাভা সোর্স লোকেশনের কম সংখ্যায় রাখুন। উপযুক্ত হিসাবে একটি JNI অটো-জেনারেশন লাইব্রেরি ব্যবহার করার কথা বিবেচনা করুন।
JavaVM এবং JNIEnv
JNI দুটি মূল তথ্য কাঠামো সংজ্ঞায়িত করে, "JavaVM" এবং "JNIEnv"। এই দুটি মূলত ফাংশন টেবিলের পয়েন্টার পয়েন্টার. (C++ সংস্করণে, তারা একটি ফাংশন টেবিলের একটি পয়েন্টার সহ ক্লাস এবং প্রতিটি JNI ফাংশনের জন্য একটি সদস্য ফাংশন যা টেবিলের মাধ্যমে পরোক্ষ করে।) JavaVM "ইনভোকেশন ইন্টারফেস" ফাংশন প্রদান করে, যা আপনাকে একটি তৈরি এবং ধ্বংস করতে দেয়। জাভাভিএম। তাত্ত্বিকভাবে আপনার প্রতি প্রক্রিয়াতে একাধিক JavaVM থাকতে পারে, কিন্তু অ্যান্ড্রয়েড শুধুমাত্র একটিকে অনুমতি দেয়।
JNIEnv বেশিরভাগ JNI ফাংশন প্রদান করে। @CriticalNative
পদ্ধতি ব্যতীত, আপনার নেটিভ ফাংশনগুলি প্রথম যুক্তি হিসাবে একটি JNIEnv পায়, দ্রুত নেটিভ কলগুলি দেখুন।
JNIEnv থ্রেড-স্থানীয় স্টোরেজের জন্য ব্যবহৃত হয়। এই কারণে, আপনি থ্রেডগুলির মধ্যে একটি JNIEnv ভাগ করতে পারবেন না । যদি কোডের একটি অংশের JNIEnv পাওয়ার অন্য কোনো উপায় না থাকে, তাহলে আপনার JavaVM শেয়ার করা উচিত এবং থ্রেডের JNIEnv আবিষ্কার করতে GetEnv
ব্যবহার করা উচিত। (ধরে নিচ্ছি এটির একটি আছে; নীচে AttachCurrentThread
দেখুন।)
JNIEnv এবং JavaVM-এর C ঘোষণা C++ ঘোষণা থেকে আলাদা। "jni.h"
অন্তর্ভুক্ত ফাইলটি C বা C++ এর মধ্যে অন্তর্ভুক্ত কিনা তার উপর নির্ভর করে বিভিন্ন টাইপডেফ প্রদান করে। এই কারণে উভয় ভাষা দ্বারা অন্তর্ভুক্ত হেডার ফাইলগুলিতে JNIEnv আর্গুমেন্টগুলি অন্তর্ভুক্ত করা একটি খারাপ ধারণা। (অন্য উপায়ে বলুন: আপনার হেডার ফাইলের জন্য যদি #ifdef __cplusplus
প্রয়োজন হয়, তাহলে সেই হেডারে কিছু JNIEnv-কে নির্দেশ করলে আপনাকে কিছু অতিরিক্ত কাজ করতে হতে পারে।)
থ্রেড
সমস্ত থ্রেড হল লিনাক্স থ্রেড, কার্নেল দ্বারা নির্ধারিত। এগুলি সাধারণত পরিচালিত কোড থেকে শুরু হয় ( Thread.start()
ব্যবহার করে), তবে সেগুলি অন্য কোথাও তৈরি করা যেতে পারে এবং তারপর JavaVM
এর সাথে সংযুক্ত করা যেতে পারে। উদাহরণস্বরূপ, pthread_create()
বা std::thread
thread দিয়ে শুরু হওয়া একটি থ্রেড AttachCurrentThread()
বা AttachCurrentThreadAsDaemon()
ফাংশন ব্যবহার করে সংযুক্ত করা যেতে পারে। একটি থ্রেড সংযুক্ত না হওয়া পর্যন্ত, এটির কোন JNIEnv নেই, এবং JNI কল করতে পারে না ।
জাভা কোডে কল করার জন্য যেকোন থ্রেড তৈরি করতে সাধারণত Thread.start()
ব্যবহার করা ভালো। এটি করা নিশ্চিত করবে যে আপনার কাছে পর্যাপ্ত স্ট্যাক স্পেস আছে, আপনি সঠিক ThreadGroup
এ আছেন এবং আপনি আপনার Java কোডের মতো একই ClassLoader
ব্যবহার করছেন। জাভাতে ডিবাগিংয়ের জন্য থ্রেডের নামটি নেটিভ কোডের চেয়ে সেট করাও সহজ (দেখুন pthread_setname_np()
যদি আপনার একটি pthread_t
বা thread_t
থাকে , এবং std::thread::native_handle()
আপনার যদি একটি std::thread
থাকে এবং একটি চান pthread_t
)।
একটি স্থানীয়ভাবে তৈরি থ্রেড সংযুক্ত করার ফলে একটি java.lang.Thread
অবজেক্ট তৈরি হয় এবং "প্রধান" ThreadGroup
এ যোগ করা হয়, এটি ডিবাগারের কাছে দৃশ্যমান হয়। ইতিমধ্যে সংযুক্ত থ্রেডে AttachCurrentThread()
কল করা একটি নো-অপ।
অ্যান্ড্রয়েড নেটিভ কোড নির্বাহ করা থ্রেড স্থগিত করে না। যদি আবর্জনা সংগ্রহের কাজ চলছে, বা ডিবাগার একটি স্থগিত করার অনুরোধ জারি করে, তাহলে পরের বার যখন এটি একটি 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
সাথে একটি কল দিয়ে সুরক্ষিত থাকতে হবে (পরবর্তী বিভাগটি দেখুন)।
আপনি যদি একটি ক্লাস লোড হওয়ার সময় আইডিগুলি ক্যাশে করতে চান এবং ক্লাসটি কখনও আনলোড এবং পুনরায় লোড হলে স্বয়ংক্রিয়ভাবে সেগুলি পুনরায় ক্যাশে করতে চান, তাহলে আইডিগুলি শুরু করার সঠিক উপায় হল উপযুক্ত কোডের একটি অংশ যুক্ত করা যা এইরকম দেখাচ্ছে ক্লাস:
কোটলিন
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 ফাংশন দ্বারা প্রত্যাবর্তিত প্রায় প্রতিটি বস্তু একটি "স্থানীয় রেফারেন্স"। এর মানে হল যে এটি বর্তমান থ্রেডে বর্তমান নেটিভ পদ্ধতির সময়কালের জন্য বৈধ। এমনকি যদি নেটিভ পদ্ধতিটি ফিরে আসার পরেও বস্তুটি নিজেই টিকে থাকে তবে রেফারেন্সটি বৈধ নয়।
এটি jclass
, jstring
এবং jarray
সহ jobject
সমস্ত সাব-ক্লাসের ক্ষেত্রে প্রযোজ্য। (যখন বর্ধিত 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
s এবং jmethodID
s অস্বচ্ছ প্রকার, অবজেক্ট রেফারেন্স নয় এবং NewGlobalRef
এ পাস করা উচিত নয়। GetStringUTFChars
এবং GetByteArrayElements
এর মত ফাংশন দ্বারা প্রত্যাবর্তিত কাঁচা ডেটা পয়েন্টারগুলিও অবজেক্ট নয়। (এগুলি থ্রেডের মধ্যে পাস করা যেতে পারে, এবং মিলিত রিলিজ কল না হওয়া পর্যন্ত বৈধ।)
একটি অস্বাভাবিক কেস আলাদা উল্লেখের দাবি রাখে। আপনি AttachCurrentThread
এর সাথে একটি নেটিভ থ্রেড সংযুক্ত করলে, আপনি যে কোডটি চালাচ্ছেন তা থ্রেডটি বিচ্ছিন্ন না হওয়া পর্যন্ত স্বয়ংক্রিয়ভাবে স্থানীয় রেফারেন্সগুলিকে মুক্ত করবে না। আপনার তৈরি করা যেকোনো স্থানীয় রেফারেন্স ম্যানুয়ালি মুছে ফেলতে হবে। সাধারণভাবে, লুপে স্থানীয় রেফারেন্স তৈরি করে এমন কোনো নেটিভ কোডকে সম্ভবত কিছু ম্যানুয়াল ডিলিট করতে হবে।
বিশ্বব্যাপী রেফারেন্স ব্যবহার করে সতর্কতা অবলম্বন করুন। গ্লোবাল রেফারেন্সগুলি অনিবার্য হতে পারে, তবে সেগুলি ডিবাগ করা কঠিন এবং মেমরি নির্ণয় করা কঠিন (ভুল) আচরণের কারণ হতে পারে। অন্য সব সমান হচ্ছে, কম গ্লোবাল রেফারেন্স সহ একটি সমাধান সম্ভবত ভাল।
UTF-8 এবং UTF-16 স্ট্রিং
জাভা প্রোগ্রামিং ভাষা UTF-16 ব্যবহার করে। সুবিধার জন্য, JNI এমন পদ্ধতিগুলি সরবরাহ করে যা পরিবর্তিত UTF-8 এর সাথেও কাজ করে। পরিবর্তিত এনকোডিং সি কোডের জন্য উপযোগী কারণ এটি 0x00 এর পরিবর্তে \u0000 0xc0 0x80 হিসাবে এনকোড করে। এই সম্পর্কে চমৎকার জিনিস হল যে আপনি সি-স্টাইলের জিরো-টার্মিনেটেড স্ট্রিং থাকার উপর নির্ভর করতে পারেন, স্ট্যান্ডার্ড libc স্ট্রিং ফাংশনগুলির সাথে ব্যবহারের জন্য উপযুক্ত। নিচের দিকটি হল যে আপনি JNI তে নির্বিচারে UTF-8 ডেটা পাস করতে পারবেন না এবং এটি সঠিকভাবে কাজ করবে বলে আশা করতে পারেন।
একটি 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 রূপান্তর অপ্রত্যাশিত ফলাফল প্রদান করতে পারে। চেকজেএনআই—যা এমুলেটরদের জন্য ডিফল্টরূপে চালু থাকে—স্ট্রিং স্ক্যান করে এবং VM বাতিল করে যদি এটি অবৈধ ইনপুট পায়।
অ্যান্ড্রয়েড 8 এর আগে, সাধারণত UTF-16 স্ট্রিংগুলির সাথে কাজ করা আরও দ্রুত ছিল কারণ Android-এর GetStringChars
এ একটি অনুলিপির প্রয়োজন ছিল না, যেখানে GetStringUTFChars
জন্য একটি বরাদ্দ এবং UTF-8 তে রূপান্তরের প্রয়োজন ছিল। অ্যান্ড্রয়েড 8 ASCII স্ট্রিংগুলির জন্য (মেমরি সংরক্ষণের জন্য) প্রতি অক্ষরে 8 বিট ব্যবহার করতে String
উপস্থাপনা পরিবর্তন করেছে এবং একটি চলমান আবর্জনা সংগ্রহকারী ব্যবহার করা শুরু করেছে। এই বৈশিষ্ট্যগুলি এমন ক্ষেত্রের সংখ্যাকে ব্যাপকভাবে হ্রাস করে যেখানে ART একটি অনুলিপি না করে String
ডেটাতে একটি পয়েন্টার প্রদান করতে পারে, এমনকি GetStringCritical
এর জন্যও। যাইহোক, যদি কোড দ্বারা প্রসেস করা বেশিরভাগ স্ট্রিং ছোট হয়, তবে বেশিরভাগ ক্ষেত্রে স্ট্যাক-অ্যালোকেটেড বাফার এবং GetStringRegion
বা GetStringUTFRegion
ব্যবহার করে বরাদ্দ এবং ডিলোকেশন এড়ানো সম্ভব। যেমন:
constexpr size_t kStackBufferSize = 64u; jchar stack_buffer[kStackBufferSize]; std::unique_ptrheap_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 অ্যারে অবজেক্টের বিষয়বস্তু অ্যাক্সেস করার জন্য ফাংশন প্রদান করে। বস্তুর অ্যারেগুলিকে একবারে একটি এন্ট্রি অ্যাক্সেস করতে হবে, আদিম অ্যারেগুলি সরাসরি পড়তে এবং লেখা যেতে পারে যেন সেগুলি সি-তে ঘোষণা করা হয়েছিল।
VM বাস্তবায়নে বাধা না দিয়ে ইন্টারফেসটিকে যতটা সম্ভব দক্ষ করে তুলতে, Get<PrimitiveType>ArrayElements
ফ্যামিলি অফ কল রানটাইমকে হয় প্রকৃত উপাদানগুলিতে একটি পয়েন্টার ফেরত দিতে, অথবা কিছু মেমরি বরাদ্দ করে একটি অনুলিপি তৈরি করতে দেয়। যেভাবেই হোক, Release
কল জারি না হওয়া পর্যন্ত যে কাঁচা পয়েন্টার ফেরত দেওয়া হয় তা বৈধ হওয়ার গ্যারান্টি দেওয়া হয় (যা বোঝায় যে, যদি ডেটা কপি করা না হয়, তাহলে অ্যারে অবজেক্টটি পিন করা হবে এবং কম্প্যাক্ট করার অংশ হিসেবে স্থানান্তরিত করা যাবে না। স্তূপ)। আপনি Get
প্রতিটি অ্যারে Release
হবে। এছাড়াও, কল Get
ব্যর্থ হলে, আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনার কোডটি পরে একটি NULL পয়েন্টার Release
করার চেষ্টা করে না।
isCopy
আর্গুমেন্টের জন্য নন-NULL পয়েন্টারে পাস করে ডেটা কপি করা হয়েছে কিনা তা আপনি নির্ধারণ করতে পারেন। এটি খুব কমই দরকারী।
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);
এটির বেশ কয়েকটি সুবিধা রয়েছে:
- ওভারহেড হ্রাস করে 2টির পরিবর্তে একটি JNI কল প্রয়োজন৷
- পিনিং বা অতিরিক্ত ডেটা কপির প্রয়োজন নেই।
- প্রোগ্রামার ত্রুটির ঝুঁকি হ্রাস করে — কিছু ব্যর্থ হওয়ার পরে
Release
কল করতে ভুলে যাওয়ার ঝুঁকি নেই।
একইভাবে, আপনি একটি অ্যারেতে ডেটা অনুলিপি করতে Set<Type>ArrayRegion
কল এবং একটি String
থেকে অক্ষর অনুলিপি করতে GetStringRegion
বা GetStringUTFRegion
ব্যবহার করতে পারেন।
ব্যতিক্রম
একটি ব্যতিক্রম মুলতুবি থাকা অবস্থায় আপনি অবশ্যই বেশিরভাগ 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++ ব্যতিক্রমগুলি, সাধারণত অ্যান্ড্রয়েডে নিরুৎসাহিত করা হয়, অবশ্যই C++ কোড থেকে পরিচালিত কোডে JNI ট্রানজিশন সীমানা পেরিয়ে যাবে না।) JNI Throw
এবং ThrowNew
নির্দেশাবলী বর্তমান থ্রেডে একটি ব্যতিক্রম পয়েন্টার সেট করেছে। নেটিভ কোড থেকে পরিচালিত এ ফিরে আসার পরে, ব্যতিক্রমটি নোট করা হবে এবং যথাযথভাবে পরিচালনা করা হবে।
নেটিভ কোড ExceptionCheck
বা ExceptionOccurred
কল করে একটি ব্যতিক্রম "ক্যাচ" করতে পারে এবং ExceptionClear
দিয়ে এটি সাফ করতে পারে। যথারীতি, তাদের পরিচালনা না করে ব্যতিক্রমগুলি বাদ দিলে সমস্যা হতে পারে।
Throwable
অবজেক্ট নিজেই ম্যানিপুলেট করার জন্য কোনও অন্তর্নির্মিত ফাংশন নেই, তাই আপনি যদি ব্যতিক্রম স্ট্রিং পেতে চান (বলুন) তবে আপনাকে Throwable
ক্লাস খুঁজে বের করতে হবে, getMessage "()Ljava/lang/String;"
এর জন্য পদ্ধতি আইডিটি সন্ধান করুন getMessage "()Ljava/lang/String;"
, এটি চালু করুন, এবং ফলাফলটি নন-NULL হলে GetStringUTFChars
ব্যবহার করুন কিছু পেতে যা আপনি printf(3)
বা সমতুল্য হাতে দিতে পারেন।
বর্ধিত চেকিং
JNI খুব কম ত্রুটি পরীক্ষা করে। ত্রুটি সাধারণত একটি ক্র্যাশ ফলাফল. অ্যান্ড্রয়েড চেকজেএনআই নামে একটি মোডও অফার করে, যেখানে জাভাভিএম এবং জেএনআইইএনভি ফাংশন টেবিল পয়েন্টারগুলি ফাংশনের টেবিলে স্যুইচ করা হয় যা স্ট্যান্ডার্ড বাস্তবায়ন কল করার আগে একটি বর্ধিত সিরিজ চেক সম্পাদন করে।
অতিরিক্ত চেক অন্তর্ভুক্ত:
- অ্যারে: একটি নেতিবাচক আকারের অ্যারে বরাদ্দ করার চেষ্টা করা হচ্ছে।
- খারাপ পয়েন্টার: একটি JNI কলে একটি খারাপ jarray/jclass/jobject/jstring পাস করা, অথবা একটি NULL পয়েন্টার একটি JNI কলে একটি নন-নালযোগ্য যুক্তি দিয়ে পাস করা।
- ক্লাসের নাম: একটি JNI কলে ক্লাস নামের "java/lang/String" শৈলী ছাড়া অন্য কিছু পাস করা।
- সমালোচনামূলক কল: একটি "সমালোচনামূলক" পেতে এবং এর সংশ্লিষ্ট প্রকাশের মধ্যে একটি JNI কল করা।
- Direct ByteBuffers:
NewDirectByteBuffer
এ খারাপ আর্গুমেন্ট পাস করা। - ব্যতিক্রম: একটি ব্যতিক্রম মুলতুবি থাকা অবস্থায় একটি JNI কল করা।
- JNIEnv*s: ভুল থ্রেড থেকে JNIEnv* ব্যবহার করে।
- jfieldIDs: একটি NULL jfieldID ব্যবহার করে, অথবা একটি jfieldID ব্যবহার করে একটি ভুল টাইপের একটি ক্ষেত্র সেট করা (একটি স্ট্রিং ফিল্ডে একটি StringBuilder বরাদ্দ করার চেষ্টা করা, বলুন), অথবা একটি স্থির ক্ষেত্রের জন্য একটি jfieldID ব্যবহার করে একটি দৃষ্টান্ত ক্ষেত্র সেট করা বা তদ্বিপরীত, বা অন্য ক্লাসের উদাহরণ সহ একটি ক্লাস থেকে একটি jfieldID ব্যবহার করা।
- jmethodIDs:
Call*Method
করার সময় ভুল ধরনের jmethodID ব্যবহার করে - তথ্যসূত্র: ভুল ধরনের রেফারেন্সে
DeleteGlobalRef
/DeleteLocalRef
ব্যবহার করে। - রিলিজ মোড: রিলিজ কলে একটি খারাপ রিলিজ মোড পাস করা (
0
,JNI_ABORT
বাJNI_COMMIT
ছাড়া অন্য কিছু)। - টাইপ নিরাপত্তা: আপনার নেটিভ পদ্ধতি থেকে একটি বেমানান টাইপ ফেরত দেওয়া (একটি স্ট্রিং ফেরত দেওয়ার জন্য ঘোষিত পদ্ধতি থেকে একটি স্ট্রিংবিল্ডার ফিরিয়ে দেওয়া, বলুন)।
- UTF-8: একটি JNI কলে একটি অবৈধ পরিবর্তিত UTF-8 বাইট সিকোয়েন্স পাস করা।
(পদ্ধতি এবং ক্ষেত্রগুলির অ্যাক্সেসযোগ্যতা এখনও পরীক্ষা করা হয়নি: অ্যাক্সেস সীমাবদ্ধতা নেটিভ কোডে প্রযোজ্য নয়।)
CheckJNI সক্রিয় করার বিভিন্ন উপায় আছে।
আপনি যদি এমুলেটর ব্যবহার করেন, চেকজেএনআই ডিফল্টরূপে চালু থাকে।
আপনার যদি রুটেড ডিভাইস থাকে, তাহলে আপনি চেকজেএনআই সক্ষম করে রানটাইম পুনরায় চালু করতে নিম্নলিখিত ক্রমটি ব্যবহার করতে পারেন:
adb shell stop adb shell setprop dalvik.vm.checkjni true adb shell start
এই উভয় ক্ষেত্রেই, রানটাইম শুরু হলে আপনি আপনার লগক্যাট আউটপুটে এরকম কিছু দেখতে পাবেন:
D AndroidRuntime: CheckJNI is ON
আপনার যদি নিয়মিত ডিভাইস থাকে তবে আপনি নিম্নলিখিত কমান্ডটি ব্যবহার করতে পারেন:
adb shell setprop debug.checkjni 1
এটি ইতিমধ্যেই চলমান অ্যাপগুলিকে প্রভাবিত করবে না, তবে সেই বিন্দু থেকে চালু হওয়া যে কোনও অ্যাপ চেকজেএনআই সক্ষম হবে। (সম্পত্তিটিকে অন্য কোনো মানতে পরিবর্তন করুন বা কেবল রিবুট করলে CheckJNI আবার নিষ্ক্রিয় হবে।) এই ক্ষেত্রে, পরের বার যখন কোনো অ্যাপ শুরু হবে তখন আপনি আপনার লগক্যাট আউটপুটে এরকম কিছু দেখতে পাবেন:
D Late-enabling CheckJNI
আপনি শুধুমাত্র আপনার অ্যাপের জন্য CheckJNI চালু করতে আপনার অ্যাপ্লিকেশনের ম্যানিফেস্টে android:debuggable
বৈশিষ্ট্য সেট করতে পারেন। মনে রাখবেন যে Android বিল্ড সরঞ্জামগুলি নির্দিষ্ট বিল্ড ধরণের জন্য স্বয়ংক্রিয়ভাবে এটি করবে৷
নেটিভ লাইব্রেরি
আপনি স্ট্যান্ডার্ড System.loadLibrary
দিয়ে শেয়ার করা লাইব্রেরি থেকে নেটিভ কোড লোড করতে পারেন।
অনুশীলনে, অ্যান্ড্রয়েডের পুরানো সংস্করণগুলির প্যাকেজ ম্যানেজারে বাগ ছিল যা স্থানীয় লাইব্রেরিগুলির ইনস্টলেশন এবং আপডেটকে অবিশ্বস্ত করে তোলে। 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
আপনার লাইব্রেরি থেকে এক্সপোর্ট করা হয়। এটি দ্রুত এবং ছোট কোড তৈরি করে এবং আপনার অ্যাপে লোড করা অন্যান্য লাইব্রেরির সাথে সম্ভাব্য সংঘর্ষ এড়ায় (কিন্তু আপনার অ্যাপটি নেটিভ কোডে ক্র্যাশ হলে এটি কম দরকারী স্ট্যাক ট্রেস তৈরি করে)।
স্ট্যাটিক ইনিশিয়ালাইজারটি এইরকম হওয়া উচিত:
কোটলিন
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 ক্রিয়াকলাপ সম্পাদন করবে না বা দীর্ঘ সময়ের জন্য ধরে রাখা যাবে এমন নেটিভ লকগুলি অর্জন করবে না।
এই টীকাগুলি অ্যান্ড্রয়েড 8 থেকে সিস্টেম ব্যবহারের জন্য প্রয়োগ করা হয়েছিল এবং অ্যান্ড্রয়েড 14-এ CTS-পরীক্ষিত পাবলিক API হয়ে উঠেছে৷ এই অপ্টিমাইজেশানগুলি সম্ভবত Android 8-13 ডিভাইসগুলিতেও কাজ করবে (যদিও শক্তিশালী CTS গ্যারান্টি ছাড়াই) তবে নেটিভ পদ্ধতিগুলির গতিশীল লুকআপ শুধুমাত্র Android 12+ এ সমর্থিত, JNI RegisterNatives
এর সাথে সুস্পষ্ট নিবন্ধন কঠোরভাবে Android সংস্করণ 8-11-এ চালানোর জন্য প্রয়োজন। এই টীকাগুলিকে Android 7- এ উপেক্ষা করা হয়েছে, @CriticalNative
এর জন্য ABI অমিলের ফলে ভুল আর্গুমেন্ট মার্শালিং এবং সম্ভবত ক্র্যাশ হতে পারে।
পারফরম্যান্সের সমালোচনামূলক পদ্ধতিগুলির জন্য যেগুলির জন্য এই টীকাগুলির প্রয়োজন, স্থানীয় পদ্ধতিগুলির নাম-ভিত্তিক "আবিষ্কার" এর উপর নির্ভর না করে JNI RegisterNatives
এর সাথে স্পষ্টভাবে পদ্ধতি(গুলি) নিবন্ধন করার জন্য দৃঢ়ভাবে সুপারিশ করা হয়। সর্বোত্তম অ্যাপ স্টার্টআপ পারফরম্যান্স পেতে, বেসলাইন প্রোফাইলে @FastNative
বা @CriticalNative
পদ্ধতির কলকারীদের অন্তর্ভুক্ত করার পরামর্শ দেওয়া হয়। অ্যান্ড্রয়েড 12 থেকে, একটি সংকলিত পরিচালিত পদ্ধতি থেকে একটি @CriticalNative
নেটিভ পদ্ধতিতে কল করা প্রায় C/C++-এ একটি নন-ইনলাইন কলের মতোই সস্তা, যতক্ষণ না সমস্ত আর্গুমেন্ট রেজিস্টারে ফিট হয়ে যায় (উদাহরণস্বরূপ 8টি অবিচ্ছেদ্য পর্যন্ত এবং 8টি পর্যন্ত arm64 এ ফ্লোটিং পয়েন্ট আর্গুমেন্ট)।
কখনও কখনও এটি একটি নেটিভ পদ্ধতিকে দুটি ভাগে ভাগ করা বাঞ্ছনীয় হতে পারে, একটি খুব দ্রুত পদ্ধতি যা ব্যর্থ হতে পারে এবং আরেকটি যা ধীরগতির ক্ষেত্রে পরিচালনা করে। যেমন:
কোটলিন
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);
64-বিট বিবেচনা
64-বিট পয়েন্টার ব্যবহার করে এমন আর্কিটেকচারগুলিকে সমর্থন করার জন্য, একটি জাভা ক্ষেত্রে একটি নেটিভ কাঠামোতে একটি পয়েন্টার সংরক্ষণ করার সময় একটি int
পরিবর্তে একটি long
ক্ষেত্র ব্যবহার করুন৷
অসমর্থিত বৈশিষ্ট্য/পিছন দিকে সামঞ্জস্য
নিম্নলিখিত ব্যতিক্রম সহ সমস্ত JNI 1.6 বৈশিষ্ট্য সমর্থিত:
-
DefineClass
বাস্তবায়িত হয় না। অ্যান্ড্রয়েড জাভা বাইটকোড বা ক্লাস ফাইল ব্যবহার করে না, তাই বাইনারি ক্লাস ডেটা পাস করা কাজ করে না।
পুরানো অ্যান্ড্রয়েড রিলিজের সাথে পিছিয়ে থাকা সামঞ্জস্যের জন্য, আপনাকে সচেতন হতে হবে:
- নেটিভ ফাংশনগুলির গতিশীল লুকআপ
Android 2.0 (Eclair) পর্যন্ত, পদ্ধতির নাম অনুসন্ধানের সময় '$' অক্ষরটি সঠিকভাবে "_00024" এ রূপান্তরিত হয়নি। এটিকে ঘিরে কাজ করার জন্য স্পষ্ট নিবন্ধন ব্যবহার করা বা অভ্যন্তরীণ ক্লাসের বাইরে নেটিভ পদ্ধতিগুলি সরিয়ে নেওয়া প্রয়োজন।
- বিচ্ছিন্ন থ্রেড
অ্যান্ড্রয়েড 2.0 (ইক্লেয়ার) পর্যন্ত, "প্রস্থান করার আগে থ্রেডকে আলাদা করতে হবে" চেক এড়াতে একটি
pthread_key_create
ধ্বংসকারী ফাংশন ব্যবহার করা সম্ভব ছিল না। (রানটাইমটি একটি pthread কী ধ্বংসকারী ফাংশনও ব্যবহার করে, তাই কোনটি প্রথমে ডাকা হবে তা দেখার জন্য এটি একটি প্রতিযোগিতা হবে।) - দুর্বল গ্লোবাল রেফারেন্স
অ্যান্ড্রয়েড 2.2 (Froyo) পর্যন্ত, দুর্বল গ্লোবাল রেফারেন্স প্রয়োগ করা হয়নি। পুরানো সংস্করণগুলি তাদের ব্যবহার করার প্রচেষ্টাকে দৃঢ়ভাবে প্রত্যাখ্যান করবে। আপনি সমর্থন পরীক্ষা করার জন্য Android প্ল্যাটফর্ম সংস্করণ ধ্রুবক ব্যবহার করতে পারেন।
অ্যান্ড্রয়েড 4.0 (আইসক্রিম স্যান্ডউইচ) পর্যন্ত, দুর্বল গ্লোবাল রেফারেন্সগুলি শুধুমাত্র
NewLocalRef
,NewGlobalRef
, এবংDeleteWeakGlobalRef
এ দেওয়া যেতে পারে। (স্পেকটি দৃঢ়ভাবে প্রোগ্রামারদের তাদের সাথে কিছু করার আগে দুর্বল গ্লোবালের হার্ড রেফারেন্স তৈরি করতে উত্সাহিত করে, তাই এটি মোটেও সীমাবদ্ধ হওয়া উচিত নয়।)অ্যান্ড্রয়েড 4.0 (আইসক্রিম স্যান্ডউইচ) থেকে, দুর্বল গ্লোবাল রেফারেন্স অন্য যেকোনো JNI রেফারেন্সের মতো ব্যবহার করা যেতে পারে।
- স্থানীয় রেফারেন্স
অ্যান্ড্রয়েড 4.0 (আইসক্রিম স্যান্ডউইচ) পর্যন্ত, স্থানীয় রেফারেন্সগুলি আসলে সরাসরি নির্দেশক ছিল। আইসক্রিম স্যান্ডউইচ আরও ভাল আবর্জনা সংগ্রহকারীদের সমর্থন করার জন্য প্রয়োজনীয় নির্দেশ যোগ করেছে, কিন্তু এর মানে হল যে অনেকগুলি JNI বাগ পুরানো রিলিজে সনাক্ত করা যায় না। আরও বিস্তারিত জানার জন্য ICS-এ JNI স্থানীয় রেফারেন্স পরিবর্তনগুলি দেখুন।
Android 8.0 এর পূর্বের Android সংস্করণগুলিতে, স্থানীয় রেফারেন্সের সংখ্যা একটি সংস্করণ-নির্দিষ্ট সীমাতে সীমাবদ্ধ। Android 8.0 থেকে শুরু করে, Android সীমাহীন স্থানীয় রেফারেন্স সমর্থন করে।
-
GetObjectRefType
দিয়ে রেফারেন্স টাইপ নির্ধারণ করা হচ্ছেঅ্যান্ড্রয়েড 4.0 (আইসক্রিম স্যান্ডউইচ) পর্যন্ত, সরাসরি পয়েন্টার ব্যবহারের ফলে (উপরে দেখুন),
GetObjectRefType
সঠিকভাবে বাস্তবায়ন করা অসম্ভব ছিল। পরিবর্তে আমরা একটি হিউরিস্টিক ব্যবহার করেছি যা দুর্বল গ্লোবাল টেবিল, আর্গুমেন্ট, লোকাল টেবিল এবং সেই ক্রমে গ্লোবাল টেবিলের মধ্য দিয়ে দেখেছে। প্রথমবার যখন এটি আপনার সরাসরি পয়েন্টার খুঁজে পেয়েছিল, তখন এটি রিপোর্ট করবে যে আপনার রেফারেন্সটি যে ধরনের পরীক্ষা করা হয়েছে তা ছিল। এর মানে হল, উদাহরণস্বরূপ, আপনি যদি একটি গ্লোবাল jclass-এGetObjectRefType
কল করেন যেটি আপনার স্ট্যাটিক নেটিভ পদ্ধতিতে একটি অন্তর্নিহিত আর্গুমেন্ট হিসাবে পাস করা jclass এর মতই হয়েছে, আপনিJNIGlobalRefType
এর পরিবর্তেJNILocalRefType
পাবেন। -
@FastNative
এবং@CriticalNative
অ্যান্ড্রয়েড 7 পর্যন্ত, এই অপ্টিমাইজেশান টীকাগুলি উপেক্ষা করা হয়েছিল৷
@CriticalNative
এর জন্য ABI অমিলের ফলে ভুল আর্গুমেন্ট মার্শালিং হবে এবং সম্ভবত ক্র্যাশ হবে।@FastNative
এবং@CriticalNative
পদ্ধতির জন্য নেটিভ ফাংশনগুলির গতিশীল লুকআপ Android 8-10-এ প্রয়োগ করা হয়নি এবং Android 11-এ পরিচিত বাগ রয়েছে৷ JNIRegisterNatives
এর সাথে স্পষ্ট নিবন্ধন ছাড়াই এই অপ্টিমাইজেশনগুলি ব্যবহার করার ফলে Android 8-11-এ ক্র্যাশ হওয়ার সম্ভাবনা রয়েছে৷ -
FindClass
ClassNotFoundException
নিক্ষেপ করেঅনগ্রসর সামঞ্জস্যের জন্য, যখন
FindClass
দ্বারা একটি ক্লাস খুঁজে পাওয়া যায় না তখন AndroidNoClassDefFoundError
এর পরিবর্তেClassNotFoundException
থ্রো করে। এই আচরণ জাভা প্রতিফলন APIClass.forName(name)
এর সাথে সামঞ্জস্যপূর্ণ।
FAQ: কেন আমি 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)
লগক্যাটে, আপনি দেখতে পাবেন:
W/dalvikvm( 880): No implementation found for native LFoo;.myfunc ()V
এর মানে হল যে রানটাইম একটি মিলে যাওয়া পদ্ধতি খুঁজে বের করার চেষ্টা করেছে কিন্তু ব্যর্থ হয়েছে। এর কিছু সাধারণ কারণ হল:
- লাইব্রেরি লোড হচ্ছে না। লাইব্রেরি লোডিং সম্পর্কে বার্তাগুলির জন্য লগক্যাট আউটপুট পরীক্ষা করুন৷
- নাম বা স্বাক্ষর অমিলের কারণে পদ্ধতিটি খুঁজে পাওয়া যাচ্ছে না। এটি সাধারণত এর কারণে হয়:
- অলস পদ্ধতির সন্ধানের জন্য,
extern "C"
এবং উপযুক্ত দৃশ্যমানতার সাথে C++ ফাংশন ঘোষণা করতে ব্যর্থ হচ্ছে (JNIEXPORT
)। মনে রাখবেন যে আইসক্রিম স্যান্ডউইচের আগে, JNIEXPORT ম্যাক্রোটি ভুল ছিল, তাই একটি পুরানোjni.h
এর সাথে একটি নতুন GCC ব্যবহার করা কাজ করবে না৷ লাইব্রেরিতে প্রদর্শিত প্রতীকগুলি দেখতে আপনিarm-eabi-nm
ব্যবহার করতে পারেন; যদি তারা মঙ্গলে দেখাচ্ছে (_Z15Java_Foo_myfuncP7_JNIEnvP7_jclass
এর পরিবর্তেJava_Foo_myfunc
এর পরিবর্তে), বা যদি প্রতীক প্রকারটি একটি বড় হাতের 'টি' এর পরিবর্তে একটি ছোট হাতের 'টি' হয় তবে আপনাকে ঘোষণাটি সামঞ্জস্য করতে হবে। - সুস্পষ্ট নিবন্ধকরণের জন্য, পদ্ধতি স্বাক্ষর প্রবেশের সময় ছোটখাটো ত্রুটিগুলি। নিশ্চিত হয়ে নিন যে আপনি নিবন্ধকরণ কলটিতে যা পার করছেন তা লগ ফাইলে স্বাক্ষরের সাথে মেলে। মনে রাখবেন যে 'বি'
byte
এবং 'জেড'boolean
। স্বাক্ষরগুলিতে শ্রেণীর নামের উপাদানগুলি 'এল' দিয়ে শুরু হয়, 'এল' দিয়ে শেষ হয় 'Ljava/util/Map$Entry;
)
- অলস পদ্ধতির সন্ধানের জন্য,
জেএনআই শিরোনামগুলি স্বয়ংক্রিয়ভাবে উত্পন্ন করতে javah
ব্যবহার করা কিছু সমস্যা এড়াতে সহায়তা করতে পারে।
এফএকিউ: কেন FindClass
আমার ক্লাসটি খুঁজে পেল না?
(এই পরামর্শের বেশিরভাগটি GetMethodID
বা GetStaticMethodID
, বা GetFieldID
বা GetStaticFieldID
সাথে ক্ষেত্রগুলি খুঁজে পেতে ব্যর্থতার ক্ষেত্রে সমানভাবে প্রযোজ্য))
শ্রেণীর নাম স্ট্রিংয়ের সঠিক ফর্ম্যাট রয়েছে তা নিশ্চিত করুন। জেএনআই শ্রেণীর নামগুলি প্যাকেজের নাম দিয়ে শুরু হয় এবং java/lang/String
মতো স্ল্যাশগুলি দিয়ে পৃথক করা হয়। আপনি যদি একটি অ্যারে ক্লাস সন্ধান করছেন তবে আপনার যথাযথ বর্গাকার বন্ধনীগুলির সাথে শুরু করতে হবে এবং অবশ্যই 'এল' এবং ';' দিয়ে ক্লাসটি গুটিয়ে রাখতে হবে, সুতরাং String
এক-মাত্রিক অ্যারেটি হবে [Ljava/lang/String;
. আপনি যদি কোনও অভ্যন্তরীণ শ্রেণীর সন্ধান করছেন তবে '' 'এর চেয়ে' $ 'ব্যবহার করুন। সাধারণভাবে, .class ফাইলটিতে javap
ব্যবহার করা আপনার শ্রেণীর অভ্যন্তরীণ নামটি সন্ধান করার একটি ভাল উপায়।
আপনি যদি কোড সঙ্কুচিত করতে সক্ষম করেন তবে নিশ্চিত হয়ে নিন যে আপনি কোন কোডটি রাখবেন তা কনফিগার করেছেন । যথাযথ কিপ বিধিগুলি কনফিগার করা গুরুত্বপূর্ণ কারণ কোড সঙ্কুচিতকারী অন্যথায় ক্লাস, পদ্ধতি বা ক্ষেত্রগুলি কেবল জেএনআই থেকে ব্যবহৃত হয় তা সরিয়ে ফেলতে পারে।
যদি শ্রেণীর নামটি সঠিক দেখায় তবে আপনি কোনও ক্লাস লোডার ইস্যুতে চলতে পারেন। FindClass
আপনার কোডের সাথে সম্পর্কিত ক্লাস লোডারে ক্লাস অনুসন্ধান শুরু করতে চায়। এটি কল স্ট্যাকটি পরীক্ষা করে, যা দেখতে এমন কিছু হবে:
Foo.myfunc(Native Method) Foo.main(Foo.java:10)
শীর্ষতম পদ্ধতিটি হ'ল Foo.myfunc
। FindClass
Foo
ক্লাসের সাথে সম্পর্কিত ClassLoader
অবজেক্টটি সন্ধান করে এবং এটি ব্যবহার করে।
এটি সাধারণত আপনি যা চান তা করে। আপনি যদি নিজে একটি থ্রেড তৈরি করেন তবে আপনি সমস্যায় পড়তে পারেন (সম্ভবত pthread_create
কল করে এবং তারপরে এটি AttachCurrentThread
দিয়ে সংযুক্ত করে)। এখন আপনার অ্যাপ্লিকেশন থেকে কোনও স্ট্যাক ফ্রেম নেই। আপনি যদি এই থ্রেড থেকে FindClass
কল করেন তবে জাভাভম আপনার অ্যাপ্লিকেশনটির সাথে যুক্ত একটির পরিবর্তে "সিস্টেম" ক্লাস লোডারে শুরু হবে, সুতরাং অ্যাপ্লিকেশন-নির্দিষ্ট ক্লাসগুলি সন্ধানের চেষ্টা ব্যর্থ হবে।
এই চারপাশে কাজ করার কয়েকটি উপায় রয়েছে:
- আপনার
FindClass
লুকআপগুলি একবারে,JNI_OnLoad
করুন এবং পরবর্তী ব্যবহারের জন্য ক্লাস রেফারেন্সগুলি ক্যাশে করুন।JNI_OnLoad
কার্যকর করার অংশ হিসাবে তৈরি যে কোনওFindClass
কলগুলিSystem.loadLibrary
নামে পরিচিত ফাংশনের সাথে সম্পর্কিত ক্লাস লোডার ব্যবহার করবে (এটি একটি বিশেষ নিয়ম, লাইব্রেরি ইনিশিয়ালাইজেশনকে আরও সুবিধাজনক করার জন্য সরবরাহ করা)। যদি আপনার অ্যাপ্লিকেশন কোডটি লাইব্রেরিটি লোড করা হয় তবেFindClass
সঠিক শ্রেণীর লোডার ব্যবহার করবে। - শ্রেণীর একটি উদাহরণ যা প্রয়োজন তা ফাংশনগুলিতে পাস করুন, আপনার স্থানীয় পদ্ধতিটি একটি শ্রেণি যুক্তি নেওয়ার জন্য ঘোষণা করে এবং তারপরে
Foo.class
ইন করে দিন। - ক্যাশে কোথাও
ClassLoader
অবজেক্টের একটি রেফারেন্স ক্যাশে এবং সরাসরিloadClass
কলগুলি জারি করুন। এর জন্য কিছু প্রচেষ্টা প্রয়োজন।
এফএকিউ: আমি কীভাবে নেটিভ কোডের সাথে কাঁচা ডেটা ভাগ করব?
আপনি নিজেকে এমন পরিস্থিতিতে খুঁজে পেতে পারেন যেখানে আপনাকে পরিচালিত এবং নেটিভ কোড উভয় থেকে কাঁচা ডেটার একটি বৃহত বাফার অ্যাক্সেস করতে হবে। সাধারণ উদাহরণগুলির মধ্যে বিটম্যাপ বা শব্দ নমুনাগুলির ম্যানিপুলেশন অন্তর্ভুক্ত। দুটি মৌলিক পন্থা আছে.
আপনি ডেটা byte[]
। এটি পরিচালিত কোড থেকে খুব দ্রুত অ্যাক্সেসের অনুমতি দেয়। নেটিভ সাইডে, তবে, আপনি এটি অনুলিপি না করে ডেটা অ্যাক্সেস করতে সক্ষম হওয়ার গ্যারান্টিযুক্ত নন। কিছু বাস্তবায়নে, GetByteArrayElements
এবং GetPrimitiveArrayCritical
পরিচালিত স্তূপের কাঁচা ডেটাতে প্রকৃত পয়েন্টারগুলি ফিরিয়ে দেবে, তবে অন্যদের মধ্যে এটি দেশীয় স্তূপের উপর একটি বাফার বরাদ্দ করবে এবং ডেটা অনুলিপি করবে।
বিকল্পটি হ'ল সরাসরি বাইট বাফারে ডেটা সঞ্চয় করা। এগুলি java.nio.ByteBuffer.allocateDirect
, বা Jni NewDirectByteBuffer
ফাংশন দিয়ে তৈরি করা যেতে পারে। নিয়মিত বাইট বাফারগুলির বিপরীতে, স্টোরেজটি পরিচালিত স্তূপে বরাদ্দ করা হয় না এবং সর্বদা সরাসরি নেটিভ কোড থেকে অ্যাক্সেস করা যায় ( GetDirectBufferAddress
সহ ঠিকানা পান)। কীভাবে সরাসরি বাইট বাফার অ্যাক্সেস প্রয়োগ করা হয় তার উপর নির্ভর করে পরিচালিত কোড থেকে ডেটা অ্যাক্সেস করা খুব ধীর হতে পারে।
যা ব্যবহার করতে হবে তার পছন্দ দুটি কারণের উপর নির্ভর করে:
- জাভাতে বা সি/সি ++ এ লিখিত কোড থেকে বেশিরভাগ ডেটা অ্যাক্সেস ঘটবে?
- যদি শেষ পর্যন্ত কোনও সিস্টেম এপিআইতে ডেটা পাস করা হয় তবে এটি কোন রূপটিতে থাকতে হবে? (উদাহরণস্বরূপ, যদি ডেটাটি শেষ পর্যন্ত কোনও ফাংশনে প্রেরণ করা হয় যা বাইট গ্রহণ করে [], সরাসরি
ByteBuffer
প্রক্রিয়াজাতকরণ করা বোকামি হতে পারে))
যদি কোনও পরিষ্কার বিজয়ী না থাকে তবে সরাসরি বাইট বাফার ব্যবহার করুন। তাদের জন্য সমর্থন সরাসরি জেএনআই -তে নির্মিত এবং ভবিষ্যতের প্রকাশগুলিতে পারফরম্যান্সের উন্নতি করা উচিত।