পুরানো ডিভাইসগুলির সাথে সামঞ্জস্য বজায় রেখে নতুন OS সংস্করণে চলাকালীন আপনার অ্যাপ কীভাবে নতুন OS কার্যকারিতা ব্যবহার করতে পারে তা এই পৃষ্ঠাটি ব্যাখ্যা করে৷
ডিফল্টরূপে, আপনার অ্যাপ্লিকেশনে NDK API-এর রেফারেন্স শক্তিশালী রেফারেন্স। আপনার লাইব্রেরি লোড হলে অ্যান্ড্রয়েডের ডায়নামিক লোডার সেগুলিকে সমাধান করতে আগ্রহী হবে৷ প্রতীক পাওয়া না গেলে, অ্যাপটি বাতিল হয়ে যাবে। এটি জাভা কীভাবে আচরণ করে তার বিপরীত, যেখানে অনুপস্থিত API কল না করা পর্যন্ত একটি ব্যতিক্রম নিক্ষেপ করা হবে না।
এই কারণে, NDK আপনাকে আপনার অ্যাপের minSdkVersion
এর চেয়ে নতুন APIগুলির শক্তিশালী রেফারেন্স তৈরি করতে বাধা দেবে। এটি আপনাকে দুর্ঘটনাক্রমে শিপিং কোড থেকে রক্ষা করে যা আপনার পরীক্ষার সময় কাজ করেছিল কিন্তু লোড করতে ব্যর্থ হবে ( UnsatisfiedLinkError
পুরানো ডিভাইসগুলিতে System.loadLibrary()
) থেকে নিক্ষেপ করা হবে। অন্যদিকে, আপনার অ্যাপের minSdkVersion
এর চেয়ে নতুন API ব্যবহার করে এমন কোড লেখা আরও কঠিন, কারণ আপনাকে অবশ্যই একটি সাধারণ ফাংশন কলের পরিবর্তে dlopen()
এবং dlsym()
ব্যবহার করে API-কে কল করতে হবে।
শক্তিশালী রেফারেন্স ব্যবহার করার বিকল্প হল দুর্বল রেফারেন্স ব্যবহার করা। একটি দুর্বল রেফারেন্স যা লাইব্রেরি লোড করার সময় পাওয়া যায় না ফলে লোড করতে ব্যর্থ হওয়ার পরিবর্তে সেই প্রতীকটির ঠিকানা nullptr
এ সেট করা হয়। সেগুলিকে এখনও নিরাপদে কল করা যায় না, তবে যতক্ষণ কলসাইটগুলি এপিআইকে কল করা প্রতিরোধ করার জন্য সুরক্ষিত থাকে যখন এটি উপলব্ধ না হয়, আপনার বাকি কোড চালানো যেতে পারে এবং আপনি dlopen()
এবং dlsym()
ব্যবহার করার প্রয়োজন ছাড়াই সাধারণত API কল করতে পারেন dlsym()
দুর্বল API রেফারেন্সগুলির জন্য ডায়নামিক লিঙ্কার থেকে অতিরিক্ত সমর্থনের প্রয়োজন হয় না, তাই সেগুলি Android এর যেকোনো সংস্করণে ব্যবহার করা যেতে পারে।
আপনার বিল্ডে দুর্বল API রেফারেন্স সক্রিয় করা হচ্ছে
সিমেক
CMake চালানোর সময় পাস -DANDROID_WEAK_API_DEFS=ON
। আপনি যদি externalNativeBuild
এর মাধ্যমে CMake ব্যবহার করেন, তাহলে আপনার build.gradle.kts
এ নিম্নলিখিত যোগ করুন (অথবা আপনি যদি এখনও build.gradle
ব্যবহার করে থাকেন তাহলে Groovy সমতুল্য):
android {
// Other config...
defaultConfig {
// Other config...
externalNativeBuild {
cmake {
arguments.add("-DANDROID_WEAK_API_DEFS=ON")
// Other config...
}
}
}
}
ndk-বিল্ড
আপনার Application.mk
ফাইলে নিম্নলিখিত যোগ করুন:
APP_WEAK_API_DEFS := true
আপনার যদি ইতিমধ্যে একটি Application.mk
ফাইল না থাকে, তাহলে আপনার Android.mk
ফাইলের মতো একই ডিরেক্টরিতে এটি তৈরি করুন। আপনার build.gradle.kts
(বা build.gradle
) ফাইলে অতিরিক্ত পরিবর্তন ndk-build-এর জন্য প্রয়োজনীয় নয়।
অন্যান্য বিল্ড সিস্টেম
আপনি যদি CMake বা ndk-build ব্যবহার না করেন, তাহলে এই বৈশিষ্ট্যটি সক্ষম করার জন্য একটি প্রস্তাবিত উপায় আছে কিনা তা দেখতে আপনার বিল্ড সিস্টেমের জন্য ডকুমেন্টেশন দেখুন। যদি আপনার বিল্ড সিস্টেম স্থানীয়ভাবে এই বিকল্পটিকে সমর্থন না করে, তাহলে আপনি কম্পাইল করার সময় নিম্নলিখিত পতাকাগুলি পাস করে বৈশিষ্ট্যটি সক্ষম করতে পারেন:
-D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability
দুর্বল রেফারেন্সের অনুমতি দেওয়ার জন্য প্রথমটি NDK হেডারগুলি কনফিগার করে। দ্বিতীয়টি অনিরাপদ API কলগুলির জন্য সতর্কতাটিকে একটি ত্রুটিতে পরিণত করে।
আরও তথ্যের জন্য বিল্ড সিস্টেম মেইনটেইনার গাইড দেখুন।
সুরক্ষিত API কল
এই বৈশিষ্ট্যটি জাদুকরীভাবে নতুন API-এ কল নিরাপদ করে না। এটি শুধুমাত্র একটি লোড-টাইম ত্রুটিকে কল-টাইম ত্রুটিতে স্থগিত করে। সুবিধা হল যে আপনি রানটাইমে সেই কলটি রক্ষা করতে পারেন এবং একটি বিকল্প বাস্তবায়ন ব্যবহার করে বা ব্যবহারকারীকে অবহিত করে যে অ্যাপটির বৈশিষ্ট্যটি তাদের ডিভাইসে উপলব্ধ নয়, অথবা সেই কোড পথটি সম্পূর্ণরূপে এড়িয়ে চলুন।
যখন আপনি আপনার অ্যাপের minSdkVersion
এর জন্য উপলব্ধ নয় এমন একটি API-তে একটি অরক্ষিত কল করেন তখন ক্ল্যাং একটি সতর্কতা ( unguarded-availability
) নির্গত করতে পারে। আপনি যদি ndk-build বা আমাদের CMake টুলচেন ফাইল ব্যবহার করেন, তাহলে এই বৈশিষ্ট্যটি সক্ষম করার সময় সেই সতর্কতাটি স্বয়ংক্রিয়ভাবে সক্ষম হবে এবং একটি ত্রুটিতে উন্নীত হবে৷
এখানে কিছু কোডের একটি উদাহরণ রয়েছে যা dlopen()
এবং dlsym()
ব্যবহার করে এই বৈশিষ্ট্যটি সক্ষম না করে একটি API এর শর্তসাপেক্ষ ব্যবহার করে:
void LogImageDecoderResult(int result) {
void* lib = dlopen("libjnigraphics.so", RTLD_LOCAL);
CHECK_NE(lib, nullptr) << "Failed to open libjnigraphics.so: " << dlerror();
auto func = reinterpret_cast<decltype(&AImageDecoder_resultToString)>(
dlsym(lib, "AImageDecoder_resultToString")
);
if (func == nullptr) {
LOG(INFO) << "cannot stringify result: " << result;
} else {
LOG(INFO) << func(result);
}
}
এটি পড়তে কিছুটা অগোছালো, ফাংশনের নামের কিছু সদৃশতা রয়েছে (এবং আপনি যদি সি লিখছেন, স্বাক্ষরগুলিও), এটি সফলভাবে তৈরি হবে তবে আপনি যদি ভুলবশত dlsym
এ পাস করা ফাংশনের নামটি টাইপ করেন তবে রানটাইমে সর্বদা ফলব্যাক নেবে , এবং আপনাকে প্রতিটি API এর জন্য এই প্যাটার্নটি ব্যবহার করতে হবে।
দুর্বল API রেফারেন্স সহ, উপরের ফাংশনটি এইভাবে পুনরায় লেখা যেতে পারে:
void LogImageDecoderResult(int result) {
if (__builtin_available(android 31, *)) {
LOG(INFO) << AImageDecoder_resultToString(result);
} else {
LOG(INFO) << "cannot stringify result: " << result;
}
}
হুডের নিচে, __builtin_available(android 31, *)
android_get_device_api_level()
কে কল করে, ফলাফলটি ক্যাশে করে এবং 31
এর সাথে তুলনা করে (এটি API স্তর যা AImageDecoder_resultToString()
) চালু করেছিল।
__builtin_available
এর জন্য কোন মান ব্যবহার করতে হবে তা নির্ধারণ করার সবচেয়ে সহজ উপায় হল গার্ড (অথবা __builtin_available(android 1, *)
) ছাড়া তৈরি করার চেষ্টা করা এবং ত্রুটি বার্তা আপনাকে যা বলে তা করা। উদাহরণস্বরূপ, minSdkVersion 24
এর সাথে AImageDecoder_createFromAAsset()
এ একটি অরক্ষিত কল তৈরি করবে:
error: 'AImageDecoder_createFromAAsset' is only available on Android 30 or newer [-Werror,-Wunguarded-availability]
এই ক্ষেত্রে কলটি __builtin_available(android 30, *)
দ্বারা রক্ষা করা উচিত। যদি কোনো বিল্ড ত্রুটি না থাকে, হয় API আপনার minSdkVersion
এর জন্য সর্বদা উপলব্ধ থাকে এবং কোনো গার্ডের প্রয়োজন হয় না, অথবা আপনার বিল্ডটি ভুল কনফিগার করা হয়েছে এবং unguarded-availability
সতর্কতা নিষ্ক্রিয় করা হয়েছে।
বিকল্পভাবে, NDK API রেফারেন্স প্রতিটি API-এর জন্য "Etroduced in API 30" এর লাইন বরাবর কিছু বলবে। যদি সেই পাঠ্যটি উপস্থিত না থাকে, তাহলে এর অর্থ হল API সমস্ত সমর্থিত API স্তরগুলির জন্য উপলব্ধ৷
API গার্ডের পুনরাবৃত্তি এড়ানো
আপনি যদি এটি ব্যবহার করে থাকেন, তাহলে সম্ভবত আপনার অ্যাপে কোডের বিভাগ থাকবে যা শুধুমাত্র নতুন যথেষ্ট ডিভাইসে ব্যবহারযোগ্য। আপনার প্রতিটি ফাংশনে __builtin_available()
চেকটি পুনরাবৃত্তি করার পরিবর্তে, আপনি একটি নির্দিষ্ট API স্তরের প্রয়োজন হিসাবে আপনার নিজের কোডটি টীকা করতে পারেন। উদাহরণস্বরূপ, ImageDecoder API গুলি নিজেই API 30 এ যোগ করা হয়েছিল, তাই যে ফাংশনগুলি সেই APIগুলির ভারী ব্যবহার করে তার জন্য আপনি কিছু করতে পারেন:
#define REQUIRES_API(x) __attribute__((__availability__(android,introduced=x)))
#define API_AT_LEAST(x) __builtin_available(android x, *)
void DecodeImageWithImageDecoder() REQUIRES_API(30) {
// Call any APIs that were introduced in API 30 or newer without guards.
}
void DecodeImageFallback() {
// Pay the overhead to call the Java APIs via JNI, or use third-party image
// decoding libraries.
}
void DecodeImage() {
if (API_AT_LEAST(30)) {
DecodeImageWithImageDecoder();
} else {
DecodeImageFallback();
}
}
এপিআই গার্ডের কুয়াশা
__builtin_available
কীভাবে ব্যবহার করা হয় সে সম্পর্কে ঝনঝন খুব বিশেষ। শুধুমাত্র একটি আক্ষরিক (যদিও সম্ভবত ম্যাক্রো-প্রতিস্থাপিত) if (__builtin_available(...))
কাজ করে। এমনকি তুচ্ছ অপারেশন যেমন if (!__builtin_available(...))
কাজ করবে না (ক্ল্যাং unsupported-availability-guard
সতর্কতা নির্গত করবে, সেইসাথে unguarded-availability
)। এটি ঝনঝন ভবিষ্যৎ সংস্করণে উন্নতি হতে পারে। আরও তথ্যের জন্য LLVM ইস্যু 33161 দেখুন।
unguarded-availability
জন্য চেক শুধুমাত্র ফাংশন স্কোপ যেখানে তারা ব্যবহার করা হয় প্রযোজ্য। ক্ল্যাং সতর্কতা নির্গত করবে এমনকি যদি API কলের সাথে ফাংশনটি শুধুমাত্র একটি রক্ষিত সুযোগের মধ্যে থেকে কল করা হয়। আপনার নিজের কোডে গার্ডের পুনরাবৃত্তি এড়াতে, API গার্ডের পুনরাবৃত্তি এড়ানো দেখুন।
কেন এই ডিফল্ট নয়?
সঠিকভাবে ব্যবহার না করা হলে, শক্তিশালী API রেফারেন্স এবং দুর্বল API রেফারেন্সের মধ্যে পার্থক্য হল যে আগেরটি দ্রুত এবং স্পষ্টতই ব্যর্থ হবে, যেখানে পরবর্তীটি ব্যর্থ হবে না যতক্ষণ না ব্যবহারকারী এমন একটি পদক্ষেপ নেয় যার কারণে অনুপস্থিত API কল করা হয়। যখন এটি ঘটবে, ত্রুটি বার্তাটি একটি পরিষ্কার কম্পাইল-টাইম হবে না "AFoo_bar() is not available" ত্রুটি, এটি একটি segfault হবে। শক্তিশালী রেফারেন্সের সাথে, ত্রুটি বার্তাটি অনেক বেশি পরিষ্কার, এবং ব্যর্থ-দ্রুত একটি নিরাপদ ডিফল্ট।
কারণ এটি একটি নতুন বৈশিষ্ট্য, এই আচরণটি নিরাপদে পরিচালনা করার জন্য খুব কম বিদ্যমান কোড লেখা হয়েছে। থার্ড-পার্টি কোড যা অ্যান্ড্রয়েডকে মাথায় রেখে লেখা হয়নি সেগুলির সম্ভবত সর্বদা এই সমস্যাটি থাকবে, তাই ডিফল্ট আচরণের পরিবর্তনের জন্য বর্তমানে কোনও পরিকল্পনা নেই।
আমরা আপনাকে এটি ব্যবহার করার পরামর্শ দিই , তবে যেহেতু এটি সনাক্তকরণ এবং ডিবাগ করা সমস্যাগুলিকে আরও কঠিন করে তুলবে, তাই আপনার অজান্তেই আচরণ পরিবর্তনের পরিবর্তে আপনার সেই ঝুঁকিগুলিকে জ্ঞাতসারে গ্রহণ করা উচিত।
সতর্কতা
এই বৈশিষ্ট্যটি বেশিরভাগ API-এর জন্য কাজ করে, কিন্তু কিছু ক্ষেত্রে এটি কাজ করে না।
সবচেয়ে কম সমস্যা হওয়ার সম্ভাবনা হল নতুন libc API। বাকি অ্যান্ড্রয়েড এপিআইগুলির থেকে ভিন্ন, সেগুলি হেডারে #if __ANDROID_API__ >= X
দিয়ে সুরক্ষিত থাকে এবং শুধুমাত্র __INTRODUCED_IN(X)
নয়, যা এমনকি দুর্বল ঘোষণাকেও দেখা থেকে বাধা দেয়। যেহেতু প্রাচীনতম API স্তরের আধুনিক NDKs সমর্থন হল r21, তাই সর্বাধিক প্রয়োজনীয় libc APIগুলি ইতিমধ্যেই উপলব্ধ। নতুন libc API গুলি প্রতিটি রিলিজে যোগ করা হয় ( status.md দেখুন), কিন্তু সেগুলি যত নতুন হবে, সেগুলি খুব কম ডেভেলপারদের প্রয়োজন হবে। তাতে বলা হয়েছে, আপনি যদি সেই ডেভেলপারদের মধ্যে একজন হয়ে থাকেন, তাহলে আপনার minSdkVersion
যদি API-এর থেকে পুরানো হয় তাহলে আপাতত সেই APIগুলিকে কল করার জন্য আপনাকে dlsym()
ব্যবহার চালিয়ে যেতে হবে। এটি একটি সমাধানযোগ্য সমস্যা, তবে এটি করা সমস্ত অ্যাপের জন্য উত্স সামঞ্জস্যতা ভঙ্গ করার ঝুঁকি বহন করে (লিবিসি এপিআইগুলির পলিফিল ধারণ করে এমন যেকোন কোড libc এবং স্থানীয় ঘোষণাগুলিতে অমিল availability
বৈশিষ্ট্যগুলির কারণে কম্পাইল করতে ব্যর্থ হবে), তাই আমরা করছি আমরা এটি ঠিক করব কিনা বা কখন তা নিশ্চিত নই।
নতুন API ধারণ করা লাইব্রেরিটি আপনার minSdkVersion
এর চেয়ে নতুন হলে আরও বেশি বিকাশকারীর সম্মুখীন হওয়ার সম্ভাবনা রয়েছে। এই বৈশিষ্ট্যটি শুধুমাত্র দুর্বল প্রতীক রেফারেন্স সক্ষম করে; একটি দুর্বল লাইব্রেরি রেফারেন্স হিসাবে কোন জিনিস নেই. উদাহরণস্বরূপ, যদি আপনার minSdkVersion
24 হয়, তাহলে আপনি libvulkan.so
লিঙ্ক করতে পারেন এবং vkBindBufferMemory2
এ একটি সুরক্ষিত কল করতে পারেন, কারণ libvulkan.so
এপিআই 24 দিয়ে শুরু হওয়া ডিভাইসগুলিতে উপলব্ধ। অন্যদিকে, আপনার minSdkVersion
23 হলে, আপনাকে অবশ্যই পড়ে যেতে হবে। dlopen
এবং dlsym
এ ফিরে যান কারণ লাইব্রেরি এমন ডিভাইসে থাকবে না যেগুলি শুধুমাত্র API 23 সমর্থন করে। ) নতুন লাইব্রেরি তৈরি করতে আর নতুন API-কে অনুমতি দেয় না।
লাইব্রেরি লেখকদের জন্য
আপনি যদি অ্যান্ড্রয়েড অ্যাপ্লিকেশানগুলিতে ব্যবহার করার জন্য একটি লাইব্রেরি তৈরি করছেন, তাহলে আপনার সর্বজনীন শিরোনামে এই বৈশিষ্ট্যটি ব্যবহার করা এড়ানো উচিত। এটি আউট-অফ-লাইন কোডে নিরাপদে ব্যবহার করা যেতে পারে, কিন্তু আপনি যদি আপনার শিরোনামের যেকোনো কোডে __builtin_available
উপর নির্ভর করেন, যেমন ইনলাইন ফাংশন বা টেমপ্লেট সংজ্ঞা, আপনি আপনার সমস্ত গ্রাহককে এই বৈশিষ্ট্যটি সক্ষম করতে বাধ্য করেন৷ একই কারণে আমরা NDK-তে ডিফল্টরূপে এই বৈশিষ্ট্যটি সক্ষম করি না, আপনার গ্রাহকদের পক্ষে এই পছন্দটি করা এড়ানো উচিত।
আপনার যদি আপনার সর্বজনীন শিরোনামগুলিতে এই আচরণের প্রয়োজন হয়, তাহলে নিশ্চিত করুন যে নথিভুক্ত করুন যাতে আপনার ব্যবহারকারীরা উভয়ই জানেন যে তাদের বৈশিষ্ট্যটি সক্ষম করতে হবে এবং এটি করার ঝুঁকি সম্পর্কে সচেতন।