প্রতীক দৃশ্যমানতা নিয়ন্ত্রণ করা APK আকার কমাতে পারে, লোডের সময় উন্নত করতে পারে এবং অন্যান্য বিকাশকারীদের বাস্তবায়নের বিবরণের উপর দুর্ঘটনাজনিত নির্ভরতা এড়াতে সহায়তা করে। এটি করার সবচেয়ে শক্তিশালী উপায় হল সংস্করণ স্ক্রিপ্টগুলির সাথে।
সংস্করণ স্ক্রিপ্ট হল ELF লিঙ্কারগুলির একটি বৈশিষ্ট্য যা -fvisibility=hidden
এর আরও শক্তিশালী ফর্ম হিসাবে ব্যবহার করা যেতে পারে। আরও বিশদ ব্যাখ্যার জন্য নীচের সুবিধাগুলি দেখুন, বা আপনার প্রকল্পে সংস্করণ স্ক্রিপ্টগুলি কীভাবে ব্যবহার করবেন তা শিখতে পড়ুন৷
উপরে লিঙ্ক করা GNU ডকুমেন্টেশনে এবং এই পৃষ্ঠার আরও কয়েকটি জায়গায়, আপনি "প্রতীক সংস্করণ" এর উল্লেখ দেখতে পাবেন। এর কারণ এই ফাইলগুলির মূল উদ্দেশ্য ছিল লাইব্রেরিতে বাগ-সামঞ্জস্যতা সংরক্ষণের জন্য একটি লাইব্রেরিতে একটি প্রতীকের একাধিক সংস্করণ (সাধারণত একটি ফাংশন) বিদ্যমান থাকার অনুমতি দেওয়া। অ্যান্ড্রয়েড সেই ব্যবহারকেও সমর্থন করে, তবে এটি সাধারণত শুধুমাত্র OS লাইব্রেরি বিক্রেতাদের জন্যই ব্যবহারযোগ্য, এবং এমনকি আমরা অ্যান্ড্রয়েডে সেগুলি ব্যবহার করি না কারণ targetSdkVersion
আরও ইচ্ছাকৃত অপ্ট-ইন প্রক্রিয়ার সাথে একই সুবিধাগুলি অফার করে৷ এই ডকটির বিষয়ের জন্য, "প্রতীক সংস্করণ" এর মতো পদগুলি নিয়ে চিন্তা করবেন না৷ আপনি যদি একই চিহ্নের একাধিক সংস্করণ সংজ্ঞায়িত না করেন, তাহলে "প্রতীক সংস্করণ" হল ফাইলে প্রতীকগুলির একটি নির্বিচারে নামকরণ করা।
আপনি যদি একজন অ্যাপ ডেভেলপারের পরিবর্তে একজন লাইব্রেরি লেখক হন (আপনার ইন্টারফেসটি C/C++ হোক বা এটি জাভা/কোটলিন হোক এবং আপনার নেটিভ কোডটি নিছক একটি বাস্তবায়ন বিশদ) তবে মিডলওয়্যার বিক্রেতাদের জন্য পরামর্শটি পড়তে ভুলবেন না।
একটি সংস্করণ স্ক্রিপ্ট লিখুন
আদর্শ ক্ষেত্রে, একটি অ্যাপ (বা AAR) যা নেটিভ কোড অন্তর্ভুক্ত করে তাতে ঠিক একটি শেয়ার করা লাইব্রেরি থাকবে, যার সমস্ত নির্ভরতা স্ট্যাটিকভাবে সেই একটি লাইব্রেরির সাথে যুক্ত থাকবে এবং সেই লাইব্রেরির সম্পূর্ণ পাবলিক ইন্টারফেস হল JNI_OnLoad
। এটি এই পৃষ্ঠায় বর্ণিত সুবিধাগুলিকে যতটা সম্ভব বিস্তৃতভাবে প্রয়োগ করার অনুমতি দেয়৷ সেক্ষেত্রে, অনুমান করে যে লাইব্রেরির নাম libapp.so
, একটি libapp.map.txt
ফাইল তৈরি করুন (নামটি মেলে না, এবং .map.txt
প্রত্যয়টি শুধুমাত্র একটি নিয়ম) সাথে নিম্নলিখিত বিষয়বস্তুগুলি (আপনি করতে পারেন) মন্তব্য বাদ দিন):
# The name used here also doesn't matter. This is the name of the "version"
# which matters when the version script is actually used to create multiple
# versions of the same symbol, but that's not what we're doing.
LIBAPP {
global:
# Every symbol named in this section will have "default" (that is, public)
# visibility. See below for how to refer to C++ symbols without mangling.
JNI_OnLoad;
local:
# Every symbol in this section will have "local" (that is, hidden)
# visibility. The wildcard * is used to indicate that all symbols not listed
# in the global section should be hidden.
*;
};
আপনার অ্যাপে যদি একাধিক শেয়ার করা লাইব্রেরি থাকে, তাহলে আপনাকে অবশ্যই প্রতি লাইব্রেরিতে একটি সংস্করণ স্ক্রিপ্ট যোগ করতে হবে।
JNI লাইব্রেরিগুলির জন্য যেগুলি JNI_OnLoad
এবং RegisterNatives()
ব্যবহার করছে না, আপনি পরিবর্তে JNI পদ্ধতিগুলির প্রতিটি তাদের JNI ম্যাঙ্গলড নামগুলির সাথে তালিকাভুক্ত করতে পারেন৷
নন-জেএনআই লাইব্রেরির জন্য (জেএনআই লাইব্রেরির নির্ভরতা, সাধারণত), আপনাকে আপনার সম্পূর্ণ API পৃষ্ঠের গণনা করতে হবে। যদি আপনার ইন্টারফেস C এর পরিবর্তে C++ হয়, আপনি একটি সংস্করণ স্ক্রিপ্টে extern "C++" { ... }
ব্যবহার করতে পারেন যেভাবে আপনি একটি হেডার ফাইলে করেন। যেমন:
LIBAPP {
global:
extern "C++" {
# A class that exposes only some methods. Note that any methods that are
# `private` in the class will still need to be visible in the library if
# they are called by `inline` or `template` functions.
#
# Non-static members do not need to be enumerated as they do not have
# symbols associated with them, but static members must be included.
#
# The * exposes all overloads of the MyClass constructor, but note that it
# will also expose methods like MyClass::MyClassNonConstructor.
MyClass::MyClass*;
MyClass::DoSomething;
MyClass::static_member;
# All members/methods of a class, including those that are `private` in
# the class.
MyOtherClass::*;
#
# If you wish to only expose some overloads, name the full signature.
# You'll need to wrap the name in quotes, otherwise you'll get a warning
# like like "ignoring invalid character '(' in script" and the symbol will
# remain hidden (pass -Wl,--no-undefined-version to convert that warning
# to an error as described below).
"MyClass::MyClass()";
"MyClass::MyClass(const MyClass&)";
"MyClass::~MyClass()";
};
local:
*;
};
নির্মাণের সময় সংস্করণ স্ক্রিপ্ট ব্যবহার করুন
বিল্ডিং করার সময় সংস্করণ স্ক্রিপ্ট লিঙ্কার পাস করা আবশ্যক. নীচে আপনার বিল্ড সিস্টেমের জন্য উপযুক্ত পদক্ষেপগুলি অনুসরণ করুন৷
সিমেক
# Assuming that your app library's target is named "app":
target_link_options(app
PRIVATE
-Wl,--version-script,${CMAKE_SOURCE_DIR}/libapp.map.txt
# This causes the linker to emit an error when a version script names a
# symbol that is not found, rather than silently ignoring that line.
-Wl,--no-undefined-version
)
# Without this, changes to the version script will not cause the library to
# relink.
set_target_properties(app
PROPERTIES
LINK_DEPENDS ${CMAKE_SOURCE_DIR}/libapp.map.txt
)
ndk-বিল্ড
# Add to an existing `BUILD_SHARED_LIBRARY` stanza (use `+=` instead of `:=` if
# the module already sets `LOCAL_LDFLAGS`):
LOCAL_LDFLAGS := -Wl,--version-script,$(LOCAL_PATH)/libapp.map.txt
# This causes the linker to emit an error when a version script names a symbol
# that is not found, rather than silently ignoring that line.
LOCAL_ALLOW_UNDEFINED_VERSION_SCRIPT_SYMBOLS := false
# ndk-build doesn't have a mechanism for specifying that libapp.map.txt is a
# dependency of the module. You may need to do a clean build or otherwise force
# the library to rebuild (such as by changing a source file) when altering the
# version script.
অন্যান্য
আপনি যে বিল্ড সিস্টেমটি ব্যবহার করছেন তার সংস্করণ স্ক্রিপ্টগুলির জন্য স্পষ্ট সমর্থন থাকলে, এটি ব্যবহার করুন।
অন্যথায়, নিম্নলিখিত লিঙ্কার পতাকা ব্যবহার করুন:
-Wl,--version-script,path/to/libapp.map.txt -Wl,--no-version-undefined
সেগুলি কীভাবে নির্দিষ্ট করা হয়েছে তা আপনার বিল্ড সিস্টেমের উপর নির্ভর করবে, তবে সাধারণত LDFLAGS
বা অনুরূপ কিছু নামে একটি বিকল্প রয়েছে। path/to/libapp.map.txt
লিঙ্কারের বর্তমান কার্যকারী ডিরেক্টরি থেকে সমাধানযোগ্য হতে হবে। একটি পরম পথ ব্যবহার করা প্রায়শই সহজ।
আপনি যদি একটি বিল্ড সিস্টেম ব্যবহার না করেন, বা একটি বিল্ড সিস্টেম রক্ষণাবেক্ষণকারী সংস্করণ স্ক্রিপ্ট সমর্থন যোগ করার জন্য খুঁজছেন, সেই পতাকাগুলিকে লিঙ্ক করার সময় clang
(বা clang++
) এ পাস করা উচিত কিন্তু কম্পাইল করার সময় নয়।
সুবিধা
একটি সংস্করণ স্ক্রিপ্ট ব্যবহার করার সময় APK আকার উন্নত করা যেতে পারে কারণ এটি একটি লাইব্রেরিতে প্রতীকগুলির দৃশ্যমান সেটকে ছোট করে। লিঙ্কারকে ঠিক কোন ফাংশনগুলি কলকারীদের কাছে অ্যাক্সেসযোগ্য তা বলে, লিঙ্কার লাইব্রেরি থেকে সমস্ত অপাগ্য কোড সরিয়ে ফেলতে পারে। এই প্রক্রিয়াটি এক ধরনের ডেড-কোড নির্মূল । লিঙ্কার ফাংশন (বা অন্য চিহ্ন) এর সংজ্ঞাটি মুছে ফেলতে পারে না যা লুকানো নেই, এমনকি ফাংশনটি কখনই কল না করা হলেও, কারণ লিঙ্কারকে অবশ্যই ধরে নিতে হবে যে একটি দৃশ্যমান প্রতীক লাইব্রেরির পাবলিক ইন্টারফেসের একটি অংশ। চিহ্নগুলি লুকিয়ে রাখা লিঙ্কারকে এমন ফাংশনগুলি সরাতে দেয় যেগুলিকে বলা হয় না, লাইব্রেরির আকার হ্রাস করে৷
লাইব্রেরি লোড কর্মক্ষমতা অনুরূপ কারণে উন্নত করা হয়েছে: দৃশ্যমান প্রতীকগুলির জন্য স্থানান্তর প্রয়োজন কারণ সেই প্রতীকগুলি ইন্টারপোজেবল । এটি প্রায় কখনই পছন্দসই আচরণ নয়, তবে এটি ELF স্পেসিফিকেশন দ্বারা প্রয়োজনীয়, তাই এটি ডিফল্ট। কিন্তু যেহেতু লিঙ্কার জানতে পারে না কোন (যদি থাকে) চিহ্নগুলিকে আপনি ইন্টারপোজেবল করতে চান, তাই এটি অবশ্যই প্রতিটি দৃশ্যমান প্রতীকের জন্য স্থানান্তর তৈরি করতে হবে। এই চিহ্নগুলি লুকিয়ে রাখা লিঙ্কারকে সরাসরি জাম্পের পক্ষে সেই স্থানান্তরগুলি বাদ দিতে দেয়, যা লাইব্রেরি লোড করার সময় গতিশীল লিঙ্কারকে যে পরিমাণ কাজ করতে হবে তা হ্রাস করে।
আপনার API পৃষ্ঠের স্পষ্টভাবে গণনা করা আপনার লাইব্রেরির ভোক্তাদেরকে ভুলভাবে আপনার লাইব্রেরির বাস্তবায়নের বিবরণের উপর নির্ভর করতে বাধা দেয়, কারণ সেই বিবরণগুলি দৃশ্যমান হবে না।
বিকল্প সঙ্গে তুলনা
সংস্করণ স্ক্রিপ্টগুলি বিকল্প হিসাবে অনুরূপ ফলাফল প্রদান করে যেমন -fvisibility=hidden
বা per-function __attribute__((visibility("hidden")))
। একটি লাইব্রেরির কোন চিহ্নগুলি অন্যান্য লাইব্রেরিতে এবং dlsym
এ দৃশ্যমান হবে তা তিনটি পদ্ধতিই নিয়ন্ত্রণ করে।
অন্য দুটি পদ্ধতির সবচেয়ে বড় নেতিবাচক দিক হল যে তারা শুধুমাত্র লাইব্রেরিতে সংজ্ঞায়িত প্রতীকগুলিকে লুকিয়ে রাখতে সক্ষম। তারা লাইব্রেরির স্ট্যাটিক লাইব্রেরি নির্ভরতা থেকে প্রতীক লুকাতে পারে না। একটি খুব সাধারণ ক্ষেত্রে যেখানে এটি libc++_static.a
ব্যবহার করার সময় একটি পার্থক্য করে। এমনকি যদি আপনার বিল্ড -fvisibility=hidden
ব্যবহার করে, যখন লাইব্রেরির নিজস্ব চিহ্ন লুকানো থাকবে, libc++_static.a
থেকে অন্তর্ভুক্ত সমস্ত প্রতীক আপনার লাইব্রেরির সর্বজনীন প্রতীক হয়ে যাবে। বিপরীতে, সংস্করণ স্ক্রিপ্টগুলি গ্রন্থাগারের সর্বজনীন ইন্টারফেসের সুস্পষ্ট নিয়ন্ত্রণ প্রদান করে; যদি প্রতীকটি সংস্করণ স্ক্রিপ্টে দৃশ্যমান হিসাবে স্পষ্টভাবে তালিকাভুক্ত না হয় তবে এটি লুকানো হবে।
অন্য পার্থক্যটি একটি প্রো এবং একটি কন উভয়ই হতে পারে: লাইব্রেরির সর্বজনীন ইন্টারফেসটি একটি সংস্করণ স্ক্রিপ্টে স্পষ্টভাবে সংজ্ঞায়িত করা আবশ্যক। JNI লাইব্রেরিগুলির জন্য এটি আসলে তুচ্ছ, কারণ JNI লাইব্রেরির জন্য একমাত্র প্রয়োজনীয় ইন্টারফেস হল JNI_OnLoad
(কারণ RegisterNatives()
এর সাথে নিবন্ধিত JNI পদ্ধতিগুলি সর্বজনীন হওয়ার প্রয়োজন নেই)। একটি বৃহৎ পাবলিক ইন্টারফেস সহ লাইব্রেরিগুলির জন্য এটি একটি অতিরিক্ত রক্ষণাবেক্ষণের বোঝা হতে পারে, তবে এটি সাধারণত সার্থক।