অ্যানোটেশন থেকে 'রাখতে হবে' সংক্রান্ত নিয়ম তৈরি করা

Jetpack Keep আপনাকে আলাদা আলাদা Keep রুল ফাইলের পরিবর্তে সরাসরি আপনার Kotlin বা Java সোর্স কোডে অ্যানোটেশন সহ রিফ্লেকশন এবং Java Native Interface (JNI) ব্যবহার ঘোষণা করতে দেয়। Jetpack Keep Gradle প্লাগ-ইন বিল্ড করার সময় এইসব অ্যানোটেশন পড়ে এবং R8-এর প্রয়োজনীয় কীপ নিয়ম তৈরি করে।

আপনার কোডবেস পরিবর্তন হওয়ার সাথে সাথে Jetpack Keep, R8 অপ্টিমাইজেশনকে কার্যকর রাখতে সাহায্য করে:

  • যে কোডের জন্য নিয়ম প্রয়োজন, তার ঠিক পাশেই নিয়ম রাখুন। রিফ্লেকশন ব্যবহার করে এমন কোডের নাম পরিবর্তন বা মুছে দিলে, আপনি একই জায়গায় অ্যানোটেশন আপডেট করেন। সিঙ্ক করে রাখার জন্য আলাদা কোনও রুল ফাইল নেই।

  • জেনারেট করা নিয়ম শর্তসাপেক্ষ। R8 শুধুমাত্র সেই কোডটি রাখলে তবেই রিফ্লেক্টিভ কলের টার্গেট রাখে যা কল করে। কলিং কোডটি অব্যবহৃত হলে এবং R8 এটি সরিয়ে দিলে (প্রায়শই ঐচ্ছিক লাইব্রেরি ফিচারের ক্ষেত্রে), R8 টার্গেটও সরিয়ে দিতে পারে, তাই অব্যবহৃত ফিচারের নিয়মগুলি ব্লট যোগ করে না।

  • Lint আপনার জন্য রিফ্লেকশন খুঁজে নেয়। লাইব্রেরিতে লিন্ট চেক অন্তর্ভুক্ত থাকে যা অ্যানোটেট করা হয়নি এমন রিফ্লেক্টিভ কল ফ্ল্যাগ করে এবং যা অটোমেটিক অনেক অ্যানোটেশন যোগ করতে পারে।

Jetpack Keep কীভাবে কাজ করে

এমন একটি ফাংশন বিবেচনা করুন যা নাম অনুসারে একটি ক্লাস ইনস্ট্যান্টিয়েট করে। আপনার কোড ক্লাস ব্যবহার করে কিনা তা R8 বুঝতে না পারলে, এটি ক্লাসের কনস্ট্রাক্টর সরিয়ে দেয় এবং রানটাইমে অ্যাপ ক্র্যাশ করে। ক্র্যাশ হওয়া আটকাতে, আপনার কিপ রুল ফাইলে নিম্নলিখিত নিয়মের মতো কোনও নিয়ম যোগ করতে পারেন:

# Unconditional: R8 keeps this constructor even if it removes the reflection that invokes it.
-keep class com.example.ResourceLocatorImpl {
    <init>();
}

Jetpack Keep-এর মাধ্যমে, আপনি সেই ফাংশনকে অ্যানোটেট করেন যা পরিবর্তে রিফ্লেকশন পারফর্ম করে:

// ResourceLocatorImpl isn't on this module's compile classpath, so both the
// annotation and the code refer to it by name.
@UsesReflectionToConstruct(
    className = "com.example.ResourceLocatorImpl",
    // Omitting parameterTypes keeps every constructor. An empty array keeps
    // only the no-argument constructor, which the code calls.
    parameterTypes = [],
)
fun loadResourceLocator(): ResourceLocator {
    val implClass = Class.forName("com.example.ResourceLocatorImpl")
    return implClass.getDeclaredConstructor().newInstance() as ResourceLocator
}

বিল্ড করার সময়, Gradle প্লাগ-ইন কনস্ট্রাক্টরের জন্য একটি 'কিপ' রুল তৈরি করে। আগে ম্যানুয়ালি লেখা নিয়মের মতো নয়, জেনারেট করা নিয়মটি শর্তসাপেক্ষ: R8 কনস্ট্রাক্টরকে তখনই রাখে যখন loadResourceLocator অপ্টিমাইজ করা অ্যাপে থাকে।

প্রয়োজনীয়তা

Jetpack Keep ব্যবহার করতে, আপনার প্রোজেক্টে Android Gradle Plugin (AGP) 8.7 বা এর পরের যেকোনও ভার্সন থাকতে হবে।

Gradle প্লাগ-ইন নিম্নলিখিত মডিউল প্রকারকে সাপোর্ট করে:

মডিউলের ধরন মডিউল যে প্লাগ-ইন প্রয়োগ করে অতিরিক্ত সেট-আপ
Android অ্যাপ com.android.application কোনওটিই নয়
Android লাইব্রেরি com.android.library কোনওটিই নয়
Android টার্গেট সহ Kotlin Multiplatform লাইব্রেরি com.android.kotlin.multiplatform.library কোনওটিই নয়
JVM লাইব্রেরি (Java বা Kotlin) java-library বা org.jetbrains.kotlin.jvm JAR ট্রান্সফর্ম রেজিস্টার করা

Jetpack Keep সেট-আপ করুন

Jetpack Keep ব্যবহার করতে, রিফ্লেকশন বা JNI ব্যবহার করে এমন প্রতিটি মডিউলে লাইব্রেরি এবং Gradle প্লাগ-ইন উভয়ই যোগ করুন। লাইব্রেরি অ্যানোটেশন ও লিন্ট চেক প্রদান করে এবং প্লাগ-ইন অ্যানোটেশনকে কীপ নিয়মে পরিবর্তন করে।

  1. আপনার libs.versions.toml ফাইলে, লাইব্রেরি ও প্লাগ-ইন যোগ করুন। লাইব্রেরি এবং প্লাগ-ইন একই ভার্সন শেয়ার করে:

    [versions]
    # The library and the plugin ship together. Use one version for both so
    # that they stay in sync when you upgrade.
    annotationKeep = "1.0.0-alpha01"
    
    [libraries]
    androidx-annotation-keep = { group = "androidx.annotation.keep", name = "keep", version.ref = "annotationKeep" }
    
    [plugins]
    androidx-annotation-keep = { id = "androidx.annotation.keep", version.ref = "annotationKeep" }
    

    Google-এর Maven রিপোজিটরি থেকে Gradle প্লাগ-ইন ডাউনলোড করে, তাই নিশ্চিত করুন যে আপনার settings.gradle.kts ফাইলে pluginManagement রিপোজিটরি google() অন্তর্ভুক্ত আছে।

  2. আপনার রুট build.gradle.kts ফাইলে, AGP-এর সাথে প্লাগ-ইন ঘোষণা করুন এটি প্রয়োগ না করেই:

    plugins {
        alias(libs.plugins.android.application) apply false
        // Other plugins.
    
        // The plugin relies on AGP classes that it doesn't bundle, so declare
        // it next to AGP. Declaring it here also gives every module the same
        // plugin version.
        alias(libs.plugins.androidx.annotation.keep) apply false
    }
    
  3. রিফ্লেকশন বা JNI ব্যবহার করে এমন প্রতিটি মডিউলের build.gradle.kts ফাইলে, প্লাগ-ইন প্রয়োগ করুন এবং লাইব্রেরি ডিপেন্ডেন্সি যোগ করুন:

    plugins {
        alias(libs.plugins.android.application)
        // The plugin reads annotations only from this module's own code, so
        // apply it in every module that uses Jetpack Keep.
        alias(libs.plugins.androidx.annotation.keep)
    }
    
    dependencies {
        implementation(libs.androidx.annotation.keep)
    }
    
  4. Gradle ফাইলের সাথে আপনার প্রোজেক্ট সিঙ্ক করুন।

Android অ্যাপ মডিউলের প্রতিটি বিল্ড ভ্যারিয়েন্টের জন্য, প্লাগ-ইন একটি <variant>ExtractKeepRules টাস্ক রেজিস্টার করে, যেমন releaseExtractKeepRules এবং সেই ভ্যারিয়েন্টের জন্য R8-এ জেনারেট করা নিয়ম পাস করে। লাইব্রেরি মডিউলের জন্য, প্লাগ-ইন তৈরি করা নিয়মগুলিকে লাইব্রেরির আউটপুটে কনজিউমার কিপ রুল হিসেবে প্যাকেজ করে। JVM লাইব্রেরি মডিউলের জন্য অতিরিক্ত সেট-আপ প্রয়োজন।

লিন্ট সহ প্রতিফলন খুঁজুন

Jetpack Keep-এ নিম্নলিখিত লিন্ট চেক অন্তর্ভুক্ত:

ReflectionWithoutKeepAnnotations
Class.forName, getDeclaredConstructor, getDeclaredMethod এবং getDeclaredField-এর মতো রিফ্লেক্টিভ কল ফ্ল্যাগ করে, যেগুলির সাথে ম্যাচিং কিপ অ্যানোটেশন নেই।
MissingKeepAnnotationPlugin
ফ্ল্যাগ মডিউল যা androidx.annotation.keep:keep-এর উপর নির্ভর করে কিন্তু androidx.annotation.keep Gradle প্লাগ-ইন প্রয়োগ করে না। প্লাগ-ইন ছাড়া, আপনার বিল্ড অ্যানোটেশন থেকে রাখার নিয়ম তৈরি করে না।

Android মডিউল অ্যানোটেট করতে লিন্ট ব্যবহার করতে, এই ধাপগুলি অনুসরণ করুন:

  1. অ্যানোটেশন প্রয়োজন এমন রিফ্লেক্টিভ কল খুঁজে পেতে মডিউলে লিন্ট রান করুন:

    ./gradlew :app:lint
    
  2. লিন্ট অটোমেটিক যেসব সমস্যার সমাধান করতে পারে সেগুলি প্রয়োগ করুন:

    ./gradlew :app:lintFix
    

    কোডটি কোন ক্লাস ও মেম্বারদের কথা বলছে তা লিন্ট বুঝতে পারলে, এটি অটোমেটিক অ্যানোটেশন যোগ করতে পারে।

  3. আপনার কোড যা করে, তার সাথে প্রতিটি যোগ করা অ্যানোটেশন মিলছে কিনা তা নিশ্চিত করতে সেগুলি পর্যালোচনা করুন। যেমন, প্যারামিটারের ধরন আপনার কোড কল করে এমন কনস্ট্রাক্টর বা মেথডের সাথে মিলছে কিনা তা চেক করুন।

  4. Android Studio-তে বাকি ফলাফলগুলি সমাধান করুন। লিন্ট যখন টার্গেট ক্লাস নির্ধারণ করতে পারে না, তখন কুইক ফিক্স আপনার সম্পূর্ণ করার জন্য TODO প্লেসহোল্ডার সহ অ্যানোটেশন যোগ করে। এছাড়াও, আপনি ম্যানুয়ালি অ্যানোটেশন যোগ করতে পারবেন।

    যেমন, নিম্নলিখিত ফাংশনটি প্যারামিটার হিসেবে ক্লাসের নাম গ্রহণ করে, তাই লিন্ট এটি কোন ক্লাস তৈরি করে তা নির্ধারণ করতে পারে না। কুইক ফিক্স প্লেসহোল্ডার সহ একটি অ্যানোটেশন যোগ করে:

    @UsesReflectionToConstruct(
        className = TODO(),
        parameterTypes = []
    )
    fun createInstance(className: String): Any =
        Class.forName(className).getDeclaredConstructor().newInstance()
    

    আপনার কোড যে ক্লাস তৈরি করে, প্লেসহোল্ডারের জায়গায় সেটি লিখুন। কম্পাইল করার সময় ক্লাস উপলভ্য থাকলে, className-এর পরিবর্তে classConstant ব্যবহার করুন। Kotlin-এ, আপনি TODO() পরিবর্তন না করা পর্যন্ত আপনার কোড কম্পাইল হয় না।

  5. আগে থেকে থাকা কোনও নিয়ম সরানোর আগে, আপনার অ্যানোটেশন থেকে প্লাগ-ইন কোন কোন নিয়ম তৈরি করে তা চেক করুন। অ্যাপ মডিউলের জন্য, রিলিজ ভ্যারিয়েন্টের জন্য এক্সট্র্যাকশন টাস্ক রান করুন:

    ./gradlew :app:releaseExtractKeepRules
    

    প্লাগ-ইনটি app/build/generated/release/androidx.annotation.keep.rules.pro-এ নিয়ম লেখে। লাইব্রেরি মডিউলের জন্য, জেনারেট করা নিয়ম যাচাই করুন।

  6. আপনার রাখার নিয়ম ফাইল থেকে নতুন অ্যানোটেশন যে রাখার নিয়মকে প্রতিস্থাপন করে তা সরিয়ে দিন। এছাড়াও, কোনও রুল এমন রিফ্লেকশন কভার করতে পারে যা লিন্ট ফ্ল্যাগ করেনি, যেমন অন্য মডিউল বা লাইব্রেরিতে রিফ্লেকশন, তাই প্রতিটি রুল সরানোর আগে চেক করুন:

    • -keep class com.example.** { *; }, প্যাকেজ-ব্যাপী নিয়মের ক্ষেত্রে, প্যাকেজের ক্লাসে সব রিফ্লেকশন যেন অ্যানোটেশন বা অন্যান্য নিয়মের আওতায় থাকে তা নিশ্চিত করুন।
    • যে নিয়মে নির্দিষ্ট ক্লাস বা মেম্বার রাখা হয়, সেটি যেন জেনারেট করা কোনও নিয়মের সাথে খুব ভালোভাবে মেলে অথবা অন্য কোনও রিফ্লেকশন যেন সেটির উপর নির্ভর না করে, তা নিশ্চিত করুন।
  7. আপনার রিলিজ বিল্ড পরীক্ষা করুন।

আপনার কোড অ্যানোটেট করা

যে কোড রিফ্লেকশন পারফর্ম করে তাতে Jetpack Keep অ্যানোটেশন যোগ করুন: যে ফাংশন, কনস্ট্রাক্টর, ফিল্ড বা ক্লাসে রিফ্লেক্টিভ কল থাকে। অ্যানোটেশন রিফ্লেকশনের টার্গেট বর্ণনা করে এবং জেনারেট করা নিয়মে সেই টার্গেট তখনই রাখা হয় যদি R8 অ্যানোটেটেড কোডটি রাখে।

আপনার কোড কী করে তার উপর ভিত্তি করে একটি অ্যানোটেশন বেছে নিন:

টিকা রচনা আপনার কোড ব্যবহার করুন
@UsesReflectionToConstruct রিফ্লেকশনের মাধ্যমে কনস্ট্রাক্টরকে কল করে, যেমন getDeclaredConstructor().newInstance()।
@UsesReflectionToAccessMethod রিফ্লেকশনের মাধ্যমে কোনও পদ্ধতি কল করে, যেমন getMethod বা getDeclaredMethod-এর মাধ্যমে।
@UsesReflectionToAccessField রিফ্লেকশনের মাধ্যমে ফিল্ড পড়ে বা লেখে, যেমন getField বা getDeclaredField।
@UnconditionallyKeep আপনার অ্যাপের Kotlin বা Java কোডের বাইরে থেকে কল করা হয়, যেমন JNI-এর মাধ্যমে নেটিভ কোড থেকে। আপনি @UsesReflection* অ্যানোটেশন ব্যবহার করতে না পারলে, শুধুমাত্র তখনই এই অ্যানোটেশন ব্যবহার করুন, যা শর্তসাপেক্ষ নিয়ম তৈরি করে।

@UsesReflection*টি অ্যানোটেশনের সবকটিই এই কনভেনশন অনুসরণ করে:

  • classConstant বা className-এর সাহায্যে টার্গেট ক্লাস শনাক্ত করুন। কম্পাইল করার সময় ক্লাস উপলভ্য থাকলে, classConstant ব্যবহার করুন। কম্পাইলার রেফারেন্স চেক করে এবং IDE রিফ্যাক্টরিং যেমন নাম পরিবর্তন করা এটি আপডেট করে। কম্পাইল করার সময় ক্লাস উপলভ্য না থাকলে, শুধুমাত্র তখনই className ব্যবহার করুন, যেমন ঐচ্ছিক ডিপেন্ডেন্সিতে থাকা ক্লাস। এই দুটি প্যারামিটার পারস্পরিকভাবে এক্সক্লুসিভ।
  • সবচেয়ে নির্দিষ্ট ক্লাস টার্গেট করুন। জেনারেট করা নিয়ম আপনার উল্লেখ করা ক্লাস এবং এর সাবক্লাস বা ইন্টারফেস ইমপ্লিমেন্টেশনে মেম্বারদের ম্যাচ করতে থাকে। ক্লাস যত বেশি সাধারণ হবে, R8 তত বেশি কোড রাখবে।
  • একটি ওভারলোড রাখতে প্যারামিটারের ধরন নির্দিষ্ট করুন। আপনি parameterTypes বা parameterTypeNames সেট না করলে, নিয়মটি সব কনস্ট্রাক্টর বা পদ্ধতির সব ওভারলোড বজায় রাখে। শুধুমাত্র নো-আর্গুমেন্ট কনস্ট্রাক্টর বা পদ্ধতি রাখতে, একটি খালি অ্যারে পাস করুন।
  • একাধিক টার্গেটের জন্য অ্যানোটেশন রিপিট করুন। একটি ফাংশন একাধিক ক্লাস বা মেম্বারের উপর প্রভাব ফেললে, প্রতিটি টার্গেটের জন্য একটি করে অ্যানোটেশন যোগ করুন।

প্রতিফলন সহ অবজেক্ট তৈরি করো

আপনার কোড রিফ্লেকশনের মাধ্যমে কোনও ক্লাস ইনস্ট্যান্টিয়েট করলে @UsesReflectionToConstruct ব্যবহার করুন। নিম্নলিখিত উদাহরণে, একটি টাস্ক রানার নাম অনুসারে StartupTask ইন্টারফেসের ইমপ্লিমেন্টেশন ইনস্ট্যান্টিয়েট করে। অ্যানোটেশন ইন্টারফেস নির্দিষ্ট করে বলে, জেনারেট করা নিয়ম প্রতিটি StartupTask ইমপ্লিমেন্টেশনের নো-আর্গুমেন্ট কনস্ট্রাক্টর রাখে, যতক্ষণ R8 execute রাখে:

interface StartupTask {
    fun run()
}

object TaskRunner {
    // The task class is known only at runtime, so target the interface that
    // every task implements.
    @UsesReflectionToConstruct(classConstant = StartupTask::class, parameterTypes = [])
    fun execute(className: String) {
        val task = Class.forName(className)
            .getDeclaredConstructor()
            .newInstance() as StartupTask
        task.run()
    }
}

রিফ্লেকশন সহ মেথড কল করুন

আপনার কোড রিফ্লেকশনের মাধ্যমে কোনও মেথড কল করলে, @UsesReflectionToAccessMethod ব্যবহার করুন। methodName-কে পদ্ধতির নাম হিসেবে সেট করুন। নিচের উদাহরণে, কোডটি একটি ঐচ্ছিক নির্ভরতার মধ্যে একটি স্ট্যাটিক পদ্ধতি কল করে। কম্পাইল করার সময় ক্লাস উপলভ্য নেই, তাই অ্যানোটেশন classConstant-এর পরিবর্তে className ব্যবহার করে:

object CrashReporting {
    @UsesReflectionToAccessMethod(
        className = "com.example.crashlib.CrashReporter",
        methodName = "log",
        // Omitting parameterTypes keeps every log overload. Keep only the one
        // that the code calls.
        parameterTypes = [String::class],
    )
    fun log(message: String) {
        val reporterClass = try {
            Class.forName("com.example.crashlib.CrashReporter")
        } catch (e: ClassNotFoundException) {
            // Crash reporting is optional, so skip it instead of failing.
            return
        }
        reporterClass.getMethod("log", String::class.java).invoke(null, message)
    }
}

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

আপনার কোড রিফ্লেকশনের মাধ্যমে কোনও ফিল্ড রিড বা রাইট করলে, @UsesReflectionToAccessField ব্যবহার করুন। ফিল্ডের নামে fieldName সেট করুন:

class UserProfileFetcher {
    // User doesn't expose internalId, so reading it requires reflection.
    @UsesReflectionToAccessField(classConstant = User::class, fieldName = "internalId")
    fun readInternalId(user: User): Any? {
        val field = User::class.java.getDeclaredField("internalId")
        field.isAccessible = true
        return field.get(user)
    }
}

আপনার অ্যাপের বাইরে থেকে কল করা কোড রাখা

আপনার অ্যাপের Kotlin বা Java কোডের বাইরে থেকে কল করা কোডের জন্য @UnconditionallyKeep ব্যবহার করুন, যেমন JNI-এর মাধ্যমে নেটিভ কোড কল করে এমন কোনও পদ্ধতি। যে এলিমেন্টকে কল করা হয়েছে সেটিকে অ্যানোটেট করুন, যে কোড এটিকে কল করেছে সেটিকে নয়:

class NativeBridge {
    // Only native code calls this method, through a JNI lookup by name that
    // R8 can't see. Keep the method and its name unconditionally.
    @UnconditionallyKeep
    fun onNativeEvent(message: String) {
        Log.d("NativeBridge", "Received from native code: $message")
    }

    // The default proguard-android-optimize.txt file already keeps native
    // method names, so this method doesn't need an annotation.
    external fun startNativeProcess()
}

জেনারেট করা নিয়মে, অ্যানোটেট করা পদ্ধতি ও তার কন্টেনিং ক্লাসকে তাদের আসল নাম সহ রাখা হয়। এটি পদ্ধতির সিগনেচার রেফার করে এমন অন্যান্য ক্লাসকে ধরে রাখে না। নেটিভ কোড যদি সেইসব ক্লাসের উপর নির্ভর করে, তাহলে সেগুলিকেও অ্যানোটেট করুন অথবা সেগুলির জন্য নিয়ম রাখুন। JNI ও R8 সম্পর্কে আরও জানতে, JNI-এর মাধ্যমে নেটিভ কোডের সাথে ইন্টার‍্যাকশন দেখুন।

ডিফল্ট হিসেবে, @UnconditionallyKeep এলিমেন্টের নাম অপরিবর্তিত রাখে। যদি এক্সটার্নাল কলার নামের উপর নির্ভর না করে, তাহলে shouldPreserveName = false সেট করুন যাতে R8 এলিমেন্টের নাম পরিবর্তন করতে পারে।

জেনারেট করা নিয়ম যাচাই করুন

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

Android অ্যাপ

কোনও অ্যাপ মডিউলের জন্য প্লাগ-ইন কোন কোন নিয়ম বজায় রাখে তা চেক করতে, বিল্ড ভ্যারিয়েন্টের জন্য এক্সট্র্যাকশন টাস্ক রান করুন:

./gradlew :app:releaseExtractKeepRules

প্লাগ-ইন প্রতিটি ভেরিয়েন্টের জন্য নিয়ম app/build/generated/<variant>/androidx.annotation.keep.rules.pro-এ লেখে। প্রতিটি নিয়মের আগে একটি # context: মন্তব্য অ্যানোটেট করা কোড শনাক্ত করে যা এটি তৈরি করেছে।

তৈরি করা নিয়মগুলি আপনার অন্যান্য রাখার নিয়মের সাথে অপ্টিমাইজেশনকে কীভাবে প্রভাবিত করে তা চেক করতে, R8 কনফিগারেশন বিশ্লেষক ব্যবহার করুন।

Android লাইব্রেরি

Android লাইব্রেরি মডিউলের জন্য কোনও অতিরিক্ত সেট-আপের প্রয়োজন নেই। আপনি যখন AAR তৈরি করেন, তখন প্লাগ-ইনটি AAR-এর proguard.txt ফাইলে জেনারেট করা নিয়মগুলি যোগ করে, AAR-এ আগে থেকেই থাকা যেকোনও কনজিউমার কিপ নিয়মের পরে। এটি করার জন্য প্লাগ-ইন প্রতিটি বিল্ড ভেরিয়েন্টের জন্য একটি <variant>KeepRulesTransformAar টাস্ক রেজিস্টার করে।

নিয়ম যাচাই করতে, AAR তৈরি করুন, যেমন ./gradlew :mylibrary:assembleRelease-এর সাহায্যে এবং জেনারেট করা AAR-এ proguard.txt খুলুন। তৈরি করা নিয়মগুলি নিম্নলিখিত হেডার সহ একটি বিভাগে দেখা যায়:

#################################################################
#   Rules generated from androidx.annotation.keep annotations   #
#################################################################

macOS ও Linux-এ, আপনি কমান্ড লাইন থেকেও নিয়ম প্রিন্ট করতে পারবেন:

unzip -p mylibrary/build/intermediates/aar/release/releaseKeepRulesTransformAar/mylibrary-release.aar proguard.txt

Kotlin Multiplatform লাইব্রেরি

Android টার্গেট সহ Kotlin Multiplatform লাইব্রেরি মডিউলের জন্য কোনও অতিরিক্ত সেট-আপের প্রয়োজন নেই। Android টার্গেটের জন্য AAR তৈরি করার সময়, প্লাগ-ইন, Android লাইব্রেরির ক্ষেত্রে যেভাবে করে, ঠিক সেইভাবে AAR-এর proguard.txt ফাইলে জেনারেট করা নিয়ম যোগ করে। এটি করার জন্য প্লাগ-ইন একটি androidMainKeepRulesTransformAar টাস্ক রেজিস্টার করে।

নিয়ম যাচাই করতে, সেই টাস্ক রান করান এবং AAR-এ proguard.txt খুলুন যা এটি তৈরি করে। macOS ও Linux-এ, আপনি কমান্ড লাইন থেকে নিয়মাবলী প্রিন্ট করতে পারবেন:

./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt

JVM লাইব্রেরি

JVM লাইব্রেরি মডিউলের জন্য নিয়ম যাচাই করার আগে, মডিউলটি কনফিগার করুন যাতে প্লাগ-ইনটি তৈরি করা নিয়মগুলি এর JAR-এর একটি কপিতে যোগ করে। তারপর, আপনার রেজিস্টার করা ট্রান্সফর্ম টাস্ক রান করুন এবং এটি যে JAR তৈরি করে তাতে রুল ফাইল চেক করুন। যেমন, আপনি টাস্কের নাম keepRulesJar রাখলে, macOS ও Linux-এ নিম্নলিখিত কমান্ডের সাহায্যে নিয়মাবলী প্রিন্ট করতে পারবেন:

./gradlew :mylibrary:keepRulesJar
unzip -p mylibrary/build/intermediates/annotation-keep/keepRulesJar/mylibrary-keep-rules.jar \
    "META-INF/com.android.tools/*" "META-INF/proguard/*"

JVM লাইব্রেরি কনফিগার করুন

JAR তৈরি করে এমন JVM লাইব্রেরি মডিউলের জন্য, প্লাগ-ইন অটোমেটিক JAR পরিবর্তন করে না। এর পরিবর্তে, annotationKeep.registerJavaArchiveTransform ফাংশন ব্যবহার করে এমন একটি টাস্ক রেজিস্টার করুন যা জেনারেট করা নিয়ম সহ JAR-এর একটি কপি তৈরি করে এবং তারপরে সেই JAR প্রকাশ করুন। লাইব্রেরি মডিউলের build.gradle.kts ফাইলে নিম্নলিখিত বিষয়গুলি যোগ করুন:

plugins {
    id("java-library")
    alias(libs.plugins.jetbrains.kotlin.jvm)
    alias(libs.plugins.androidx.annotation.keep)
}

val jarTask = tasks.named<Jar>("jar")

// Apps run R8 on this library's code, so the generated rules must be inside
// the JAR. The plugin handles this automatically only for Android modules.
val keepRulesJarTask = annotationKeep.registerJavaArchiveTransform(
    taskName = "keepRulesJar",
    input = jarTask.flatMap { it.archiveFile },
    fileName = jarTask.flatMap { it.archiveFileName }.map { it.replace(".jar", "-keep-rules.jar") },
)

// Consumers of this library resolve apiElements and runtimeElements, so add
// the JAR that contains the generated rules to them.
configurations.configureEach {
    if (name == "apiElements" || name == "runtimeElements") {
        outgoing.artifact(keepRulesJarTask.flatMap { it.outputJar })
    }
}

dependencies {
    implementation(libs.androidx.annotation.keep)
}

ট্রান্সফর্ম, JAR-এর টার্গেট করা R8 নিয়মের লোকেশনে, জেনারেট করা নিয়ম যোগ করে, META-INF/com.android.tools/r8/proguard.txt। JAR-এ আগে থেকেই META-INF/com.android.tools/ বা META-INF/proguard/-এ নিয়ম থাকলে, ট্রান্সফর্ম, জেনারেট করা নিয়মগুলি আগে থেকে থাকা নিয়ম ফাইলের সাথে যোগ করে। এইসব লোকেশন সম্পর্কে আরও তথ্যের জন্য, বিভিন্ন R8 ভার্সন সাপোর্ট করা দেখুন।

ট্রান্সফর্ম রেজিস্টার করার পরে, JAR-এ নিয়মগুলি যাচাই করুন।

উদাহরণ: একটি ঐচ্ছিক অ্যাড-অন লাইব্রেরি লোড করুন

ঐচ্ছিক অ্যাড-অন লোড করার জন্য লাইব্রেরি প্রায়শই রিফ্লেকশন ব্যবহার করে। যেমন, একটি Analytics SDK একটি কোর লাইব্রেরি এবং একটি ভিডিও অ্যাড-অন পাঠাতে পারে। অ্যাড-অনটি কোর লাইব্রেরির উপর নির্ভর করে, তাই কোর লাইব্রেরি সরাসরি অ্যাড-অনের ক্লাসকে রেফার করতে পারে না। এর পরিবর্তে, কোর লাইব্রেরি রানটাইমে নামের মাধ্যমে অ্যাড-অনের ক্লাস খুঁজে দেখে এবং ক্লাসটি খুঁজে পেলে তবেই ভিডিও অ্যানালিটিক্স চালু করে।

যেহেতু কোর লাইব্রেরি শুধুমাত্র রিফ্লেকশনের মাধ্যমে অ্যাড-অনের ক্লাস ব্যবহার করে, তাই R8 অ্যাপের রিলিজ বিল্ড থেকে ক্লাস বা এর কনস্ট্রাক্টর সরিয়ে দিতে পারে, যার ফলে লুকআপ ব্যর্থ হয়। অ্যাড-অন অন্তর্ভুক্ত থাকা অ্যাপের রিলিজ বিল্ডে শুধুমাত্র ব্যর্থতা ঘটে, তাই এটি টেস্টিংয়ে ধরা কঠিন।

Jetpack Keep না থাকলে, অ্যাড-অনটি তার ক্লাসের জন্য একটি কনজিউমার কিপ নিয়ম শিপ করে, ঐচ্ছিক নির্ভরতা সাপোর্ট করার জন্য রিফ্লেকশন-এ দেখানো হয়েছে:

# In the add-on's consumer keep rules file.
-keep class com.example.analytics.video.VideoEventTracker {
    <init>();
}

এই নিয়মটি লুক-আপের থেকে আলাদা লাইব্রেরিতে আছে, তাই দুটি সিঙ্ক করা নাও থাকতে পারে। Jetpack Keep-এর মাধ্যমে, আপনি পরিবর্তে কোর লাইব্রেরিতে লুকআপ অ্যানোটেট করেন:

// In the core library.
interface AnalyticsPlugin {
    fun start()
}

object AnalyticsManager {
    // The annotation and the lookup share this constant so that they can't
    // refer to different classes.
    private const val VIDEO_TRACKER_CLASS =
        "com.example.analytics.video.VideoEventTracker"

    @UsesReflectionToConstruct(
        className = VIDEO_TRACKER_CLASS,
        parameterTypes = [],
    )
    fun initialize() {
        val tracker = try {
            Class.forName(VIDEO_TRACKER_CLASS)
                .getDeclaredConstructor()
                .newInstance() as AnalyticsPlugin
        } catch (e: ClassNotFoundException) {
            // Apps that don't play video leave out the add-on, so a missing
            // class isn't an error.
            return
        }
        // This is an ordinary interface call, not a reflective one, so R8
        // keeps start() without an annotation.
        tracker.start()
    }
}

অ্যানোটেশন এইসব নির্দেশিকা মেনে চলে:

  • যে কোড রিফ্লেকশন পারফর্ম করে সেটি অ্যানোটেট করো। অ্যানোটেশনটি initialize-এ আছে, যা Class.forName-কে কল করে, অ্যাড-অনের ক্লাসে নয়।
  • কোডটি কী করে তার উপর ভিত্তি করে অ্যানোটেশন বেছে নিন। লুক-আপ ক্লাসের একটি ইনস্ট্যান্স তৈরি করে, তাই কোড @UsesReflectionToConstruct ব্যবহার করে।
  • এমন ক্লাসের জন্য className ব্যবহার করুন যা আপনি রেফারেন্স করতে পারবেন না। অ্যাড-অনের ক্লাস কোর লাইব্রেরির কম্পাইল ক্লাসপাথে নেই, তাই অ্যানোটেশন classConstant ব্যবহার করতে পারবে না।
  • কোড যে কনস্ট্রাক্টরকে কল করে, শুধুমাত্র সেটিই রাখুন। লুক-আপ নো-আর্গুমেন্ট কনস্ট্রাক্টরকে কল করে, তাই parameterTypes হল একটি খালি অ্যারে।

আপনি যখন কোর লাইব্রেরির AAR তৈরি করেন, তখন প্লাগ-ইন একটি নিয়ম যোগ করে যা VideoEventTracker এবং এর নো-আর্গুমেন্ট কনস্ট্রাক্টর রাখে যখনই R8 initialize রাখে। SDK ব্যবহার করে এমন অ্যাপগুলি এই নিয়ম অটোমেটিক প্রয়োগ করে, এমনকি সেগুলি Jetpack Keep ব্যবহার না করলেও করে। তাই, অ্যাড-অনের নিজস্ব কোনও নিয়মের প্রয়োজন নেই। অ্যাড-অনের নিয়মের সাথে তুলনা করলে, এই পদ্ধতির নিম্নলিখিত সুবিধাগুলি রয়েছে:

  • নিয়মটি লুকআপের পাশে থাকে। কোর লাইব্রেরি লুকআপ ও নিয়ম, দুটিই ম্যানেজ করে, তাই দুটি লাইব্রেরি জুড়ে পরিবর্তন সমন্বয় করার পরিবর্তে আপনি একসাথে সেগুলি আপডেট করেন।
  • R8 এখনও অব্যবহৃত কোড সরিয়ে দিতে পারে। কোনও অ্যাপে অ্যাড-অন অন্তর্ভুক্ত থাকলেও কখনও initialize কল না করলে, জেনারেট করা নিয়ম প্রযোজ্য হয় না এবং R8 VideoEventTracker সরিয়ে দিতে পারে। অ্যাড-অনের নিয়মের কারণে, অ্যাড-অন অন্তর্ভুক্ত করে এমন প্রতিটি অ্যাপে এটি থাকে।
  • Lint অ্যানোটেট না করা লুকআপ ধরে। কোর লাইব্রেরি পরে যদি অ্যানোটেশন ছাড়া অন্য রিফ্লেক্টিভ লুকআপ যোগ করে, তাহলে লিন্ট সেটিকে ফ্ল্যাগ করে, যাতে লাইব্রেরি রিলিজ করার আগে আপনি সেটি ঠিক করতে পারেন।

ফলাফল কনফার্ম করতে, Android লাইব্রেরিতে যেমন বর্ণনা করা হয়েছে, সেই অনুযায়ী তৈরি করা নিয়ম AAR-এর proguard.txt ফাইলে দেখা যাচ্ছে কিনা তা চেক করুন। তারপরে, অ্যাড-অন সহ অ্যাপের রিলিজ বিল্ড পরীক্ষা করুন, কারণ সেখানে কোনও নিয়ম না থাকলে ভিডিও অ্যানালিটিক্স কাজ করবে না।

অতিরিক্ত রিসোর্স