নিম্নলিখিত উদাহরণগুলি সাধারণ পরিস্থিতির উপর ভিত্তি করে যেখানে আপনি অপ্টিমাইজেশানের জন্য R8 ব্যবহার করেন, কিন্তু রাখার নিয়ম খসড়া করতে উন্নত নির্দেশিকা প্রয়োজন।
প্রতিফলন
সাধারণভাবে, সর্বোত্তম কর্মক্ষমতার জন্য, প্রতিফলন ব্যবহার করার পরামর্শ দেওয়া হয় না। যাইহোক, কিছু পরিস্থিতিতে, এটি অনিবার্য হতে পারে। নিম্নলিখিত উদাহরণগুলি প্রতিফলন ব্যবহার করে এমন সাধারণ পরিস্থিতিতে নিয়মগুলি রাখার জন্য নির্দেশিকা প্রদান করে।
নাম দ্বারা লোড ক্লাস সঙ্গে প্রতিফলন
লাইব্রেরি প্রায়ই ক্লাসের নাম String
হিসাবে ব্যবহার করে গতিশীলভাবে ক্লাস লোড করে। যাইহোক, R8 এই পদ্ধতিতে লোড করা ক্লাসগুলি সনাক্ত করতে পারে না এবং এটি অব্যবহৃত বলে মনে করা ক্লাসগুলি সরিয়ে ফেলতে পারে।
উদাহরণ স্বরূপ, আপনার কাছে একটি লাইব্রেরি এবং একটি অ্যাপ আছে যেখানে লাইব্রেরি ব্যবহার করে নিচের দৃশ্যটি বিবেচনা করুন- কোডটি একটি লাইব্রেরি লোডার প্রদর্শন করে যা একটি অ্যাপ দ্বারা বাস্তবায়িত একটি MyWorker
ইন্টারফেসকে ত্বরান্বিত করে।
লাইব্রেরি কোড নিম্নরূপ:
// The interface for a task that runs once.
interface StartupTask {
fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
fun execute(className: String) {
// R8 won't retain classes specified by this string value at runtime
val taskClass = Class.forName(className)
val task = taskClass.getDeclaredConstructor().newInstance() as StartupTask
task.run()
}
}
যে অ্যাপটি লাইব্রেরি ব্যবহার করে তার নিম্নলিখিত কোড রয়েছে:
// The app's task to pre-cache data.
// R8 will remove this class because it's only referenced by a string.
class PreCacheTask : StartupTask {
override fun run() {
// This log will never appear if the class is removed by R8.
Log.d("AppTask", "Warming up the cache...")
}
}
fun onCreate() {
// The library is told to run the app's task by its name.
TaskRunner.execute("com.example.app.PreCacheTask")
}
এই পরিস্থিতিতে, আপনার লাইব্রেরিতে নিম্নলিখিত রাখার নিয়মগুলির সাথে একটি ভোক্তা রাখার নিয়ম ফাইল অন্তর্ভুক্ত করা উচিত:
-keep class * implements com.example.library.StartupTask {
<init>();
}
এই নিয়ম ছাড়া, R8 অ্যাপ থেকে PreCacheTask
সরিয়ে দেয় কারণ অ্যাপটি সরাসরি ক্লাস ব্যবহার করে না, ইন্টিগ্রেশন ভঙ্গ করে। নিয়মটি সেই ক্লাসগুলি খুঁজে পায় যা আপনার লাইব্রেরির StartupTask
ইন্টারফেস বাস্তবায়ন করে এবং তাদের নো-আর্গুমেন্ট কনস্ট্রাক্টর সহ সেগুলিকে সংরক্ষণ করে, যা লাইব্রেরীকে সফলভাবে PreCacheTask
চালু এবং কার্যকর করতে দেয়।
::class.java
দিয়ে প্রতিফলন
লাইব্রেরিগুলি অ্যাপটি Class
অবজেক্টটি সরাসরি পাস করে ক্লাস লোড করতে পারে, যা নাম অনুসারে ক্লাস লোড করার চেয়ে আরও শক্তিশালী পদ্ধতি। এটি ক্লাসের একটি শক্তিশালী রেফারেন্স তৈরি করে যা R8 সনাক্ত করতে পারে। যাইহোক, যদিও এটি R8-কে ক্লাস অপসারণ করতে বাধা দেয়, তবুও আপনাকে ক্লাসটি প্রতিফলিতভাবে তাত্ক্ষণিকভাবে ঘোষণা করতে এবং কনস্ট্রাক্টরের মতো প্রতিফলিতভাবে অ্যাক্সেস করা সদস্যদের রক্ষা করার জন্য একটি Keep নিয়ম ব্যবহার করতে হবে।
উদাহরণ স্বরূপ, নিচের দৃশ্যটি বিবেচনা করুন যেখানে আপনার একটি লাইব্রেরি এবং একটি অ্যাপ আছে যা লাইব্রেরি ব্যবহার করে- লাইব্রেরি লোডার সরাসরি ক্লাস রেফারেন্স পাস করে একটি StartupTask
ইন্টারফেস ইনস্ট্যান্টিয়েট করে।
লাইব্রেরি কোড নিম্নরূপ:
// The interface for a task that runs once.
interface StartupTask {
fun run()
}
// The library object that loads and executes the task.
object TaskRunner {
fun execute(taskClass: Class<out StartupTask>) {
// The class isn't removed, but its constructor might be.
val task = taskClass.getDeclaredConstructor().newInstance()
task.run()
}
}
যে অ্যাপটি লাইব্রেরি ব্যবহার করে তার নিম্নলিখিত কোড রয়েছে:
// The app's task is to pre-cache data.
class PreCacheTask : StartupTask {
override fun run() {
Log.d("AppTask", "Warming up the cache...")
}
}
fun onCreate() {
// The library is given a direct reference to the app's task class.
TaskRunner.execute(PreCacheTask::class.java)
}
এই পরিস্থিতিতে, আপনার লাইব্রেরিতে নিম্নলিখিত রাখার নিয়মগুলির সাথে একটি ভোক্তা রাখার নিয়ম ফাইল অন্তর্ভুক্ত করা উচিত:
# Allow any implementation of StartupTask to be removed if unused.
-keep,allowobfuscation,allowshrinking class * implements com.example.library.StartupTask
# Keep the default constructor, which is called via reflection.
-keepclassmembers class * implements com.example.library.StartupTask {
<init>();
}
এই নিয়মগুলি এই ধরণের প্রতিফলনের সাথে পুরোপুরি কাজ করার জন্য ডিজাইন করা হয়েছে, কোডটি সঠিকভাবে কাজ করে কিনা তা নিশ্চিত করার সময় সর্বাধিক অপ্টিমাইজেশনের অনুমতি দেয়। নিয়মগুলি R8 কে ক্লাসের নামটি অস্পষ্ট করতে দেয় এবং StartupTask
ক্লাসের প্রয়োগকে সঙ্কুচিত বা অপসারণ করতে দেয় যদি অ্যাপটি কখনও এটি ব্যবহার না করে। যাইহোক, যে কোনো বাস্তবায়নের জন্য, যেমন PrecacheTask
উদাহরণে ব্যবহৃত হয়, তারা ডিফল্ট কনস্ট্রাক্টর ( <init>()
) সংরক্ষণ করে যেটিকে আপনার লাইব্রেরি কল করতে হবে।
-
-keep,allowobfuscation,allowshrinking class * implements com.example.library.StartupTask
: এই নিয়মটি আপনারStartupTask
ইন্টারফেস প্রয়োগ করে এমন যেকোনো ক্লাসকে লক্ষ্য করে।-
-keep class * implements com.example.library.StartupTask
*
-
,allowobfuscation
: এটি R8 কে নির্দেশ দেয় যে ক্লাসটি রাখা সত্ত্বেও, এটি এর নাম পরিবর্তন করতে পারে বা অস্পষ্ট করতে পারে। এটি নিরাপদ কারণ আপনার লাইব্রেরি ক্লাসের নামের উপর নির্ভর করে না; এটি সরাসরিClass
অবজেক্ট পায়। -
,allowshrinking
: এই মডিফায়ারটি R8 কে নির্দেশ দেয় যে এটি অব্যবহৃত থাকলে ক্লাসটি সরিয়ে ফেলতে পারে। এটি R8 কে নিরাপদেStartupTask
এর একটি বাস্তবায়ন মুছে ফেলতে সাহায্য করে যাTaskRunner.execute()
এ কখনই পাস করা হয় না। সংক্ষেপে, এই নিয়মটি নিম্নলিখিতগুলি বোঝায়: যদি একটি অ্যাপ এমন একটি ক্লাস ব্যবহার করে যাStartupTask
প্রয়োগ করে, R8 ক্লাসটি রাখে। R8 এর আকার কমাতে ক্লাসের নাম পরিবর্তন করতে পারে এবং অ্যাপটি ব্যবহার না করলে এটি মুছে ফেলতে পারে।
-
-
-keepclassmembers class * implements com.example.library.StartupTask { <init>(); }
: এই নিয়মটি ক্লাসের নির্দিষ্ট সদস্যদের লক্ষ্য করে যা প্রথম নিয়মে চিহ্নিত করা হয়েছিল—এই ক্ষেত্রে, কনস্ট্রাক্টর।-
-keepclassmembers class * implements com.example.library.StartupTask
: এটি ক্লাসের নির্দিষ্ট সদস্যদের (পদ্ধতি, ক্ষেত্র) সংরক্ষণ করে যাStartupTask
ইন্টারফেস প্রয়োগ করে, কিন্তু শুধুমাত্র যদি বাস্তবায়িত ক্লাসটি রাখা হয়। -
{ <init>(); }
: এটি সদস্য নির্বাচক।<init>
জাভা বাইটকোডে একটি কনস্ট্রাক্টরের জন্য বিশেষ অভ্যন্তরীণ নাম। এই অংশটি বিশেষভাবে ডিফল্ট, নো-আর্গুমেন্ট কনস্ট্রাক্টরকে লক্ষ্য করে। - এই নিয়মটি গুরুত্বপূর্ণ কারণ আপনার কোডটি কোনো আর্গুমেন্ট ছাড়া
getDeclaredConstructor().newInstance()
কল করে, যা প্রতিফলিতভাবে ডিফল্ট কনস্ট্রাক্টরকে আহ্বান করে। এই নিয়ম ব্যতীত, R8 দেখে যে কোনও কোড সরাসরিnew PreCacheTask()
কল করে না, অনুমান করে যে কনস্ট্রাক্টরটি অব্যবহৃত, এবং এটি সরিয়ে দেয়। এটি একটিInstantiationException
সহ রানটাইমে আপনার অ্যাপ ক্র্যাশ করে।
-
পদ্ধতি টীকা উপর ভিত্তি করে প্রতিফলন
লাইব্রেরিগুলি প্রায়শই টীকাগুলিকে সংজ্ঞায়িত করে যা বিকাশকারীরা ট্যাগ পদ্ধতি বা ক্ষেত্রগুলি ব্যবহার করে৷ লাইব্রেরি রানটাইমে এই টীকা করা সদস্যদের খুঁজে পেতে প্রতিফলন ব্যবহার করে। উদাহরণস্বরূপ, @OnLifecycleEvent
টীকাটি রানটাইমে প্রয়োজনীয় পদ্ধতিগুলি খুঁজে পেতে ব্যবহৃত হয়।
উদাহরণ স্বরূপ, নিচের দৃশ্যটি বিবেচনা করুন যেখানে আপনার একটি লাইব্রেরি এবং একটি অ্যাপ রয়েছে যা লাইব্রেরি ব্যবহার করে- উদাহরণটি একটি ইভেন্ট বাস প্রদর্শন করে যা @OnEvent
সাথে টীকাযুক্ত পদ্ধতিগুলি খুঁজে পায় এবং আহ্বান করে।
লাইব্রেরি কোড নিম্নরূপ:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class OnEvent
class EventBus {
fun dispatch(listener: Any) {
// Find all methods annotated with @OnEvent and invoke them
listener::class.java.declaredMethods.forEach { method ->
if (method.isAnnotationPresent(OnEvent::class.java)) {
try {
method.invoke(listener)
} catch (e: Exception) { /* ... */ }
}
}
}
}
যে অ্যাপটি লাইব্রেরি ব্যবহার করে তার নিম্নলিখিত কোড রয়েছে:
class MyEventListener {
@OnEvent
fun onSomethingHappened() {
// This method will be removed by R8 without a keep rule
Log.d(TAG, "Event received!")
}
}
fun onCreate() {
// Instantiate the listener and the event bus
val listener = MyEventListener()
val eventBus = EventBus()
// Dispatch the listener to the event bus
eventBus.dispatch(listener)
}
লাইব্রেরিতে একটি ভোক্তা রাখার নিয়ম ফাইল অন্তর্ভুক্ত করা উচিত যা স্বয়ংক্রিয়ভাবে তার টীকা ব্যবহার করে যেকোনো পদ্ধতি সংরক্ষণ করে:
-keepattributes RuntimeVisibleAnnotations
-keep @interface com.example.library.OnEvent;
-keepclassmembers class * {
@com.example.library.OnEvent <methods>;
}
-
-keepattributes RuntimeVisibleAnnotations
: এই নিয়ম টিকা সংরক্ষণ করে যেগুলো রানটাইমে পড়ার জন্য। -
-keep @interface com.example.library.OnEvent
: এই নিয়মটিOnEvent
টীকা ক্লাস নিজেই সংরক্ষণ করে। -
-keepclassmembers class * {@com.example.library.OnEvent <methods>;}
: এই নিয়মটি একটি ক্লাস এবং নির্দিষ্ট সদস্য সংরক্ষণ করে শুধুমাত্র যদি ক্লাসটি ব্যবহার করা হয় এবং ক্লাসে সেই সদস্য থাকে।-
-keepclassmembers
: এই নিয়মটি একটি শ্রেণী এবং নির্দিষ্ট সদস্যদের সংরক্ষণ করে শুধুমাত্র যদি ক্লাসটি ব্যবহার করা হয় এবং ক্লাসে সেই সদস্য থাকে। -
class *
: নিয়মটি যেকোন শ্রেণীর জন্য প্রযোজ্য। -
@com.example.library.OnEvent <methods>;
: এটি@com.example.library.OnEvent
এর সাথে টীকাযুক্ত এক বা একাধিক পদ্ধতি (<methods>
) আছে এমন যেকোন শ্রেণীকে সংরক্ষণ করে এবং টীকাকৃত পদ্ধতিগুলিকেও সংরক্ষণ করে।
-
বর্গ টীকা উপর ভিত্তি করে প্রতিফলন
লাইব্রেরি একটি নির্দিষ্ট টীকা আছে এমন ক্লাসের জন্য স্ক্যান করতে প্রতিফলন ব্যবহার করতে পারে। এই ক্ষেত্রে, টাস্ক রানার ক্লাস প্রতিফলন ব্যবহার করে ReflectiveExecutor
সাথে টীকাযুক্ত সমস্ত ক্লাস খুঁজে বের করে এবং execute
মেথড চালায়।
উদাহরণস্বরূপ, নিম্নলিখিত দৃশ্যটি বিবেচনা করুন যেখানে আপনার একটি লাইব্রেরি এবং একটি অ্যাপ রয়েছে যা লাইব্রেরি ব্যবহার করে।
লাইব্রেরিতে নিম্নলিখিত কোড আছে:
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class ReflectiveExecutor
class TaskRunner {
fun process(task: Any) {
val taskClass = task::class.java
if (taskClass.isAnnotationPresent(ReflectiveExecutor::class.java)) {
val methodToCall = taskClass.getMethod("execute")
methodToCall.invoke(task)
}
}
}
যে অ্যাপটি লাইব্রেরি ব্যবহার করে তার নিম্নলিখিত কোড রয়েছে:
// In consumer app
@ReflectiveExecutor
class ImportantBackgroundTask {
fun execute() {
// This class will be removed by R8 without a keep rule
Log.e("ImportantBackgroundTask", "Executing the important background task...")
}
}
// Usage of ImportantBackgroundTask
fun onCreate(){
val task = ImportantBackgroundTask()
val runner = TaskRunner()
runner.process(task)
}
যেহেতু লাইব্রেরি নির্দিষ্ট ক্লাস পেতে প্রতিফলন ব্যবহার করে, তাই লাইব্রেরিতে নিম্নলিখিত রাখার নিয়মগুলির সাথে একটি ভোক্তা রাখার নিয়ম ফাইল অন্তর্ভুক্ত করা উচিত:
# Retain annotation metadata for runtime reflection.
-keepattributes RuntimeVisibleAnnotations
# Keep the annotation interface itself.
-keep @interface com.example.library.ReflectiveExecutor
# Keep the execute method in the classes which are being used
-keepclassmembers @com.example.library.ReflectiveExecutor class * {
public void execute();
}
এই কনফিগারেশনটি অত্যন্ত দক্ষ কারণ এটি R8 কে ঠিক কী সংরক্ষণ করতে হবে তা বলে।
ঐচ্ছিক নির্ভরতা সমর্থন প্রতিফলন
প্রতিফলনের জন্য একটি সাধারণ ব্যবহারের ক্ষেত্রে একটি মূল লাইব্রেরি এবং একটি ঐচ্ছিক অ্যাড-অন লাইব্রেরির মধ্যে একটি নরম নির্ভরতা তৈরি করা। কোর লাইব্রেরি অ্যাপটিতে অ্যাড-অন অন্তর্ভুক্ত আছে কিনা তা পরীক্ষা করতে পারে এবং যদি এটি থাকে তবে অতিরিক্ত বৈশিষ্ট্যগুলি সক্ষম করতে পারে। এটি আপনাকে কোর লাইব্রেরীকে তাদের উপর সরাসরি নির্ভরতা না করেই অ্যাড-অন মডিউল পাঠাতে দেয়।
মূল লাইব্রেরি প্রতিফলন ব্যবহার করে ( Class.forName
) এর নামের দ্বারা একটি নির্দিষ্ট শ্রেণীর সন্ধান করতে। ক্লাস পাওয়া গেলে, বৈশিষ্ট্য সক্রিয় করা হয়. যদি না হয়, এটা gracefully ব্যর্থ হয়.
উদাহরণ স্বরূপ, নিম্নলিখিত কোডটি বিবেচনা করুন যেখানে একটি কোর AnalyticsManager
ভিডিও বিশ্লেষণ সক্ষম করার জন্য একটি ঐচ্ছিক VideoEventTracker
ক্লাস পরীক্ষা করে।
মূল লাইব্রেরিতে নিম্নলিখিত কোড রয়েছে:
object AnalyticsManager {
private const val VIDEO_TRACKER_CLASS = "com.example.analytics.video.VideoEventTracker"
fun initialize() {
try {
// Attempt to load the optional module's class using reflection
Class.forName(VIDEO_TRACKER_CLASS).getDeclaredConstructor().newInstance()
Log.d(TAG, "Video tracking enabled.")
} catch (e: ClassNotFoundException) {
Log.d(TAG,"Video tracking module not found. Skipping.")
} catch (e: Exception) {
Log.e(TAG, e.printStackTrace())
}
}
}
ঐচ্ছিক ভিডিও লাইব্রেরিতে নিম্নলিখিত কোড রয়েছে:
package com.example.analytics.video
class VideoEventTracker {
// This constructor must be kept for the reflection call to succeed.
init { /* ... */ }
}
ঐচ্ছিক গ্রন্থাগারের বিকাশকারী প্রয়োজনীয় ভোক্তা রাখার নিয়ম প্রদানের জন্য দায়ী। এই রাখার নিয়মটি নিশ্চিত করে যে ঐচ্ছিক লাইব্রেরি ব্যবহার করে যেকোন অ্যাপ মূল লাইব্রেরির খুঁজে বের করতে হবে এমন কোড সংরক্ষণ করে।
# In the video library's consumer keep rules file
-keep class com.example.analytics.video.VideoEventTracker {
<init>();
}
এই নিয়ম ব্যতীত, R8 সম্ভবত ঐচ্ছিক লাইব্রেরি থেকে VideoEventTracker
মুছে ফেলবে কারণ সেই মডিউলের কিছুই সরাসরি এটি ব্যবহার করে না। Keep নিয়ম ক্লাস এবং এর কনস্ট্রাক্টরকে সংরক্ষণ করে, মূল লাইব্রেরীকে সফলভাবে এটিকে ইনস্ট্যান্টিয়েট করতে দেয়।
ব্যক্তিগত সদস্যদের অ্যাক্সেসের প্রতিফলন
ব্যক্তিগত বা সুরক্ষিত কোড অ্যাক্সেস করতে প্রতিফলন ব্যবহার করে যা একটি লাইব্রেরির পাবলিক API এর অংশ নয় তা উল্লেখযোগ্য সমস্যা প্রবর্তন করতে পারে। এই ধরনের কোড নোটিশ ছাড়াই পরিবর্তন সাপেক্ষে, যা আপনার অ্যাপ্লিকেশনে অপ্রত্যাশিত আচরণ বা ক্র্যাশ হতে পারে।
আপনি যখন অ-পাবলিক API-এর প্রতিফলনের উপর নির্ভর করেন, তখন আপনি নিম্নলিখিত সমস্যার সম্মুখীন হতে পারেন:
- অবরুদ্ধ আপডেট: ব্যক্তিগত বা সুরক্ষিত কোডে পরিবর্তন আপনাকে উচ্চতর লাইব্রেরি সংস্করণে আপডেট করা থেকে বাধা দিতে পারে।
- মিস করা সুবিধা: আপনি নতুন কার্যকারিতা, গুরুত্বপূর্ণ ক্র্যাশ ফিক্স বা প্রয়োজনীয় নিরাপত্তা আপডেটগুলি মিস করতে পারেন৷
R8 অপ্টিমাইজেশান এবং প্রতিফলন
আপনি যদি একটি লাইব্রেরির ব্যক্তিগত বা সুরক্ষিত কোড প্রতিফলিত করতে পারেন, R8 এর অপ্টিমাইজেশানগুলিতে গভীর মনোযোগ দিন৷ যদি এই সদস্যদের কোন সরাসরি উল্লেখ না থাকে, তাহলে R8 অনুমান করতে পারে যে তারা অব্যবহৃত এবং পরবর্তীতে তাদের অপসারণ বা নাম পরিবর্তন করে। এটি রানটাইম ক্র্যাশের দিকে নিয়ে যেতে পারে, প্রায়শই NoSuchMethodException
বা NoSuchFieldException
এর মতো বিভ্রান্তিকর ত্রুটি বার্তা সহ।
উদাহরণস্বরূপ, নিম্নলিখিত পরিস্থিতি বিবেচনা করুন যা প্রদর্শন করে যে আপনি কীভাবে একটি লাইব্রেরি ক্লাস থেকে একটি ব্যক্তিগত ক্ষেত্রে অ্যাক্সেস করতে পারেন।
আপনার মালিকানাধীন নয় এমন একটি লাইব্রেরিতে নিম্নলিখিত কোড রয়েছে:
class LibraryClass {
private val secretMessage = "R8 will remove me"
}
আপনার অ্যাপে নিম্নলিখিত কোড রয়েছে:
fun accessSecretMessage(instance: LibraryClass) {
// Use Java reflection from Kotlin to access the private field
val secretField = instance::class.java.getDeclaredField("secretMessage")
secretField.isAccessible = true
// This will crash at runtime with R8 enabled
val message = secretField.get(instance) as String
}
R8 কে প্রাইভেট ফিল্ড অপসারণ থেকে আটকাতে আপনার অ্যাপে একটি -keep
নিয়ম যোগ করুন:
-keepclassmembers class com.example.LibraryClass {
private java.lang.String secretMessage;
}
-keepclassmembers
: এটি একটি ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করে শুধুমাত্র যদি ক্লাসটি নিজেই বজায় থাকে।-
class com.example.LibraryClass
: এটি ক্ষেত্র সম্বলিত সঠিক শ্রেণিকে লক্ষ্য করে। -
private java.lang.String secretMessage;
: এটি নির্দিষ্ট ব্যক্তিগত ক্ষেত্রটিকে তার নাম এবং প্রকার দ্বারা চিহ্নিত করে৷
জাভা নেটিভ ইন্টারফেস (JNI)
নেটিভ (C/C++ কোড) থেকে Java বা Kotlin-এ আপকলের সাথে কাজ করার সময় R8-এর অপ্টিমাইজেশানে সমস্যা হতে পারে। যদিও বিপরীতটিও সত্য—জাভা বা কোটলিন থেকে নেটিভ কোডে ডাউনকল করলে সমস্যা হতে পারে—ডিফল্ট ফাইল proguard-android-optimize.txt
ডাউনকলগুলি কাজ করার জন্য নিম্নলিখিত নিয়মগুলি অন্তর্ভুক্ত রয়েছে। এই নিয়ম দেশীয় পদ্ধতি ছাঁটা থেকে রক্ষা করে।
-keepclasseswithmembernames,includedescriptorclasses class * {
native <methods>;
}
জাভা নেটিভ ইন্টারফেস (JNI) এর মাধ্যমে নেটিভ কোডের সাথে মিথস্ক্রিয়া
যখন আপনার অ্যাপ JNI ব্যবহার করে নেটিভ (C/C++) কোড থেকে Java বা Kotlin-এ আপকল করতে, তখন R8 আপনার নেটিভ কোড থেকে কোন পদ্ধতিতে কল করা হয়েছে তা দেখতে পায় না। যদি আপনার অ্যাপে এই পদ্ধতিগুলির কোনও সরাসরি উল্লেখ না থাকে, তাহলে R8 ভুলভাবে ধরে নেয় যে এই পদ্ধতিগুলি অব্যবহৃত এবং সেগুলিকে সরিয়ে দেয়, যার ফলে আপনার অ্যাপ ক্র্যাশ হয়ে যায়।
নিম্নলিখিত উদাহরণটি একটি স্থানীয় লাইব্রেরি থেকে কল করার উদ্দেশ্যে একটি পদ্ধতি সহ একটি কোটলিন ক্লাস দেখায়। নেটিভ লাইব্রেরি একটি অ্যাপ্লিকেশান টাইপ ইনস্ট্যান্টিয়েট করে এবং নেটিভ কোড থেকে কোটলিন কোডে ডেটা পাস করে।
package com.example.models
// This class is used in the JNI bridge method signature
data class NativeData(val id: Int, val payload: String)
package com.example.app
// In package com.example.app
class JniBridge {
/**
* This method is called from the native side.
* R8 will remove it if it's not kept.
*/
fun onNativeEvent(data: NativeData) {
Log.d(TAG, "Received event from native code: $data")
}
// Use 'external' to declare a native method
external fun startNativeProcess()
companion object {
init {
// Load the native library
System.loadLibrary("my-native-lib")
}
}
}
এই ক্ষেত্রে, আপনাকে অবশ্যই R8 জানাতে হবে যাতে অ্যাপ্লিকেশনের ধরনটি অপ্টিমাইজ করা থেকে আটকাতে পারে। অতিরিক্তভাবে, যদি নেটিভ কোড থেকে কল করা পদ্ধতিগুলি প্যারামিটার বা রিটার্ন টাইপ হিসাবে তাদের স্বাক্ষরে আপনার নিজস্ব ক্লাস ব্যবহার করে, তাহলে আপনাকে অবশ্যই যাচাই করতে হবে যে সেই ক্লাসগুলির নাম পরিবর্তন করা হয়নি।
আপনার অ্যাপে নিম্নলিখিত রাখার নিয়মগুলি যোগ করুন:
-keepclassmembers,includedescriptorclasses class com.example.JniBridge {
public void onNativeEvent(com.example.model.NativeData);
}
-keep class NativeData{
<init>(java.lang.Integer, java.lang.String);
}
এই নিয়মগুলি R8 কে অপসারণ বা onNativeEvent
পদ্ধতির নামকরণ থেকে বাধা দেয় এবং—সমালোচনামূলকভাবে—এর পরামিতি প্রকার।
-
-keepclassmembers,includedescriptorclasses class com.example.JniBridge{ public void onNativeEvent(com.example.model.NativeData);}
: এটি একটি ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করে শুধুমাত্র তখনই যদি ক্লাসটি কোটলিন বা জাভা কোডে ইনস্ট্যান্ট করা হয়—এটি R8কে বলে যে অ্যাপটি ক্লাসের নির্দিষ্ট সদস্যদের ব্যবহার করছে এবং সেটিকে সংরক্ষণ করা উচিত।-
-keepclassmembers
: এটি শুধুমাত্র ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করে যদি ক্লাসটি প্রথমে কোটলিন বা জাভা কোডে ইনস্ট্যান্ট করা হয়—এটি R8 কে বলে যে অ্যাপটি ক্লাস ব্যবহার করছে এবং এটি ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করা উচিত। -
class com.example.JniBridge
: এটি ক্ষেত্র সম্বলিত সঠিক ক্লাসকে লক্ষ্য করে। -
includedescriptorclasses
: এই সংশোধক পদ্ধতির স্বাক্ষর, বা বর্ণনাকারীতে পাওয়া যেকোন ক্লাসও সংরক্ষণ করে। এই ক্ষেত্রে, এটি R8 কেcom.example.models.NativeData
ক্লাসের নাম পরিবর্তন বা সরানো থেকে বাধা দেয়, যা একটি প্যারামিটার হিসাবে ব্যবহৃত হয়। যদিNativeData
নাম পরিবর্তন করা হয় (উদাহরণস্বরূপ,aa
), পদ্ধতি স্বাক্ষরটি আর নেটিভ কোডের প্রত্যাশার সাথে মিলবে না, যা ক্র্যাশ ঘটায়। -
public void onNativeEvent(com.example.models.NativeData);
: এটি সংরক্ষণ করার পদ্ধতির সঠিক জাভা স্বাক্ষর নির্দিষ্ট করে।
-
-
-keep class NativeData{<init>(java.lang.Integer, java.lang.String);}
:includedescriptorclasses
স্ক্রিপ্টরক্লাসগুলি নিশ্চিত করে যেNativeData
ক্লাস নিজেই সংরক্ষিত আছে,NativeData
মধ্যে যেকোন সদস্য (ক্ষেত্র বা পদ্ধতি) যা সরাসরি আপনার নেটিভ JNI কোড থেকে অ্যাক্সেস করা হয় তাদের নিজস্ব রাখার নিয়ম প্রয়োজন।-
-keep class NativeData
: এটিNativeData
নামের ক্লাসটিকে টার্গেট করে এবং ব্লকটি নির্দিষ্ট করে যেNativeData
ক্লাসের মধ্যে কোন সদস্যরা রাখতে হবে। -
<init>(java.lang.Integer, java.lang.String)
: এটি কনস্ট্রাক্টরের স্বাক্ষর। এটি স্বতন্ত্রভাবে কনস্ট্রাক্টরকে সনাক্ত করে যা দুটি প্যারামিটার নেয়: প্রথমটি একটিInteger
এবং দ্বিতীয়টি একটিString
।
-
পরোক্ষ প্ল্যাটফর্ম কল
Parcelable
বাস্তবায়নের সাথে ডেটা স্থানান্তর করুন
অ্যান্ড্রয়েড ফ্রেমওয়ার্ক আপনার Parcelable
বস্তুর উদাহরণ তৈরি করতে প্রতিফলন ব্যবহার করে। আধুনিক কোটলিন ডেভেলপমেন্টে, আপনার kotlin-parcelize
প্লাগইন ব্যবহার করা উচিত, যা স্বয়ংক্রিয়ভাবে প্রয়োজনীয় Parcelable
বাস্তবায়ন তৈরি করে, যার মধ্যে CREATOR
ক্ষেত্র এবং ফ্রেমওয়ার্কের প্রয়োজনীয় পদ্ধতিগুলি সহ।
উদাহরণস্বরূপ, নিম্নলিখিত উদাহরণটি বিবেচনা করুন যেখানে kotlin-parcelize
প্লাগইনটি একটি Parcelable
ক্লাস তৈরি করতে ব্যবহৃত হয়:
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
// Add the @Parcelize annotation to your data class
@Parcelize
data class UserData(
val name: String,
val age: Int
) : Parcelable
এই পরিস্থিতিতে, রাখার জন্য সুপারিশ করা নিয়ম নেই। kotlin-parcelize
Gradle প্লাগইন স্বয়ংক্রিয়ভাবে আপনি @Parcelize
সাথে টীকা করা ক্লাসের জন্য প্রয়োজনীয় রাখার নিয়ম তৈরি করে। এটি আপনার জন্য জটিলতা পরিচালনা করে, নিশ্চিত করে যে তৈরি করা CREATOR
এবং কনস্ট্রাক্টরগুলি Android ফ্রেমওয়ার্কের প্রতিফলন কলগুলির জন্য সংরক্ষিত রয়েছে৷
আপনি যদি @Parcelize
ব্যবহার না করেই Kotlin-এ ম্যানুয়ালি একটি Parcelable
ক্লাস লেখেন, তাহলে আপনি CREATOR
ক্ষেত্র এবং একটি Parcel
গ্রহণকারী কনস্ট্রাক্টর রাখার জন্য দায়ী। এটি করতে ভুলে গেলে আপনার অ্যাপ ক্র্যাশ হয়ে যায় যখন সিস্টেমটি আপনার অবজেক্টকে ডিসিরিয়ালাইজ করার চেষ্টা করে। @Parcelize
ব্যবহার করা হল আদর্শ, নিরাপদ অনুশীলন।
kotlin-parcelize
প্লাগইন ব্যবহার করার সময়, নিম্নলিখিতগুলি সম্পর্কে সচেতন থাকুন:
- প্লাগইনটি সংকলনের সময় স্বয়ংক্রিয়ভাবে
CREATOR
ক্ষেত্র তৈরি করে। -
proguard-android-optimize.txt
ফাইলে সঠিক কার্যকারিতার জন্য এই ক্ষেত্রগুলিকে ধরে রাখার জন্য প্রয়োজনীয়keep
রয়েছে৷ - অ্যাপ ডেভেলপারদের অবশ্যই যাচাই করতে হবে যে সমস্ত প্রয়োজনীয়
keep
নিয়ম উপস্থিত রয়েছে, বিশেষত যে কোনও কাস্টম বাস্তবায়ন বা তৃতীয়-পক্ষ নির্ভরতার জন্য।