নিয়ম ব্যবহারের ক্ষেত্রে এবং উদাহরণ রাখুন

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

প্রতিফলন

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

নাম অনুসারে লোড করা ক্লাস সহ প্রতিফলন

লাইব্রেরিগুলি প্রায়শই ক্লাসের নাম String ব্যবহার করে গতিশীলভাবে ক্লাস লোড করে। যাইহোক, R8 এই পদ্ধতিতে লোড হওয়া ক্লাসগুলি সনাক্ত করতে পারে না এবং অব্যবহৃত বলে মনে করা ক্লাসগুলি সরিয়ে ফেলতে পারে।

উদাহরণস্বরূপ, নিম্নলিখিত পরিস্থিতিটি বিবেচনা করুন যেখানে আপনার একটি লাইব্রেরি এবং একটি অ্যাপ রয়েছে যা লাইব্রেরি ব্যবহার করে - কোডটি একটি লাইব্রেরি লোডার প্রদর্শন করে যা একটি অ্যাপ দ্বারা বাস্তবায়িত একটি 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(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 নিয়ম সহ একটি consumer keep rules ফাইল অন্তর্ভুক্ত করা উচিত:

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

এই পরিস্থিতিতে, আপনার লাইব্রেরিতে নিম্নলিখিত keep নিয়ম সহ একটি consumer keep rules ফাইল অন্তর্ভুক্ত করা উচিত:

# 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>; : এটি এমন যেকোনো ক্লাস সংরক্ষণ করে যার এক বা একাধিক পদ্ধতি ( <methods> ) @com.example.library.OnEvent দিয়ে টীকাযুক্ত থাকে, এবং টীকাযুক্ত পদ্ধতিগুলিও সংরক্ষণ করে।

ক্লাস টীকাগুলির উপর ভিত্তি করে প্রতিফলন

লাইব্রেরিগুলি প্রতিফলন ব্যবহার করে নির্দিষ্ট অ্যানোটেশনযুক্ত ক্লাসগুলি স্ক্যান করতে পারে। এই ক্ষেত্রে, টাস্ক রানার ক্লাস প্রতিফলন ব্যবহার করে 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)
}

যেহেতু লাইব্রেরি প্রতিফলিতভাবে নির্দিষ্ট ক্লাস পেতে প্রতিফলন ব্যবহার করে, তাই লাইব্রেরিতে নিম্নলিখিত keep নিয়ম সহ একটি consumer keep rules ফাইল অন্তর্ভুক্ত করা উচিত:

# 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 ) ব্যবহার করে। যদি ক্লাসটি পাওয়া যায়, তাহলে বৈশিষ্ট্যটি সক্রিয় করা হয়। যদি না পাওয়া যায়, তাহলে এটি দুর্দান্তভাবে ব্যর্থ হয়।

উদাহরণস্বরূপ, নিম্নলিখিত কোডটি বিবেচনা করুন যেখানে একটি কোর 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 যাতে private ফিল্ডটি সরাতে না পারে, তার জন্য আপনার অ্যাপে একটি -keep নিয়ম যোগ করুন:

-keepclassmembers class com.example.LibraryClass {
    private java.lang.String secretMessage;
}
  • -keepclassmembers : এটি শুধুমাত্র তখনই একটি ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করে যদি ক্লাসটি নিজেই ধরে রাখা হয়।
  • class com.example.LibraryClass : এটি ক্ষেত্রটি ধারণকারী সঠিক ক্লাসটিকে লক্ষ্য করে।
  • private java.lang.String secretMessage; : এটি নির্দিষ্ট private ক্ষেত্রটিকে তার নাম এবং প্রকার দ্বারা চিহ্নিত করে।

জাভা নেটিভ ইন্টারফেস (JNI)

নেটিভ (C/C++ কোড) থেকে জাভা বা কোটলিনে আপকল করার সময় R8 এর অপ্টিমাইজেশনে সমস্যা হতে পারে। যদিও বিপরীতটিও সত্য - জাভা বা কোটলিন থেকে নেটিভ কোডে ডাউনকল করার সময় সমস্যা হতে পারে - ডিফল্ট ফাইল proguard-android-optimize.txt এ ডাউনকলগুলি কাজ করে রাখার জন্য নিম্নলিখিত নিয়মটি অন্তর্ভুক্ত রয়েছে। এই নিয়মটি নেটিভ পদ্ধতিগুলি ছাঁটাই করা থেকে রক্ষা করে।

-keepclasseswithmembernames,includedescriptorclasses class * {
  native <methods>;
}

জাভা নেটিভ ইন্টারফেস (JNI) এর মাধ্যমে নেটিভ কোডের সাথে মিথস্ক্রিয়া

যখন আপনার অ্যাপটি JNI ব্যবহার করে নেটিভ (C/C++) কোড থেকে জাভা বা কোটলিনে আপকল করে, তখন 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);
}

এই Keep নিয়মগুলি R8 কে onNativeEvent পদ্ধতি এবং—গুরুত্বপূর্ণভাবে—এর প্যারামিটার টাইপ অপসারণ বা পুনঃনামকরণ থেকে বিরত রাখে।

  • -keepclassmembers,includedescriptorclasses class com.example.JniBridge{ public void onNativeEvent(com.example.model.NativeData);} : এটি শুধুমাত্র তখনই একটি ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করে যদি ক্লাসটি প্রথমে Kotlin বা Java কোডে ইনস্ট্যান্টিয়েট করা হয়—এটি R8 কে বলে যে অ্যাপটি ক্লাসটি ব্যবহার করছে এবং এটি ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করবে।
    • -keepclassmembers : এটি শুধুমাত্র তখনই একটি ক্লাসের নির্দিষ্ট সদস্যদের সংরক্ষণ করে যদি ক্লাসটি প্রথমে Kotlin বা Java কোডে ইন্সট্যান্ট করা হয়—এটি 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 নিয়ম প্রয়োজন।
    • -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 এবং কনস্ট্রাক্টরগুলি অ্যান্ড্রয়েড ফ্রেমওয়ার্কের প্রতিফলন কলের জন্য সংরক্ষিত আছে।

যদি আপনি @Parcelize ব্যবহার না করে Kotlin-এ Parcelable ক্লাস ম্যানুয়ালি লেখেন, তাহলে CREATOR ফিল্ড এবং Parcel গ্রহণকারী কনস্ট্রাক্টর রাখার জন্য আপনার দায়িত্ব। এটি করতে ভুলে গেলে সিস্টেম যখন আপনার অবজেক্ট ডিসিরিয়ালাইজ করার চেষ্টা করে তখন আপনার অ্যাপটি ক্র্যাশ হয়ে যায়। @Parcelize ব্যবহার করা হল স্ট্যান্ডার্ড এবং নিরাপদ পদ্ধতি।

kotlin-parcelize প্লাগইন ব্যবহার করার সময়, নিম্নলিখিত বিষয়গুলি সম্পর্কে সচেতন থাকুন:

  • সংকলনের সময় প্লাগইনটি স্বয়ংক্রিয়ভাবে CREATOR ক্ষেত্র তৈরি করে।
  • proguard-android-optimize.txt ফাইলটিতে এই ক্ষেত্রগুলি সঠিক কার্যকারিতার জন্য ধরে keep জন্য প্রয়োজনীয় নিয়মাবলী রয়েছে।
  • অ্যাপ ডেভেলপারদের অবশ্যই যাচাই করতে হবে যে সমস্ত প্রয়োজনীয় keep নিয়ম উপস্থিত আছে, বিশেষ করে যেকোনো কাস্টম বাস্তবায়ন বা তৃতীয় পক্ষের নির্ভরতার জন্য।