एनोटेशन से, 'रखें' नियम जनरेट करना

Jetpack Keep की मदद से, Kotlin या Java के सोर्स कोड में सीधे तौर पर एनोटेशन का इस्तेमाल करके, रिफ़्लेक्शन और Java Native Interface (JNI) के इस्तेमाल की जानकारी दी जा सकती है. इसके लिए, कीप रूल की अलग-अलग फ़ाइलें बनाने की ज़रूरत नहीं होती. Jetpack Keep का Gradle प्लगिन, बिल्ड के दौरान इन एनोटेशन को पढ़ता है और R8 के लिए कीप रूल जनरेट करता है.

Jetpack Keep, R8 के ऑप्टिमाइज़ेशन को असरदार बनाए रखने में मदद करता है. ऐसा इसलिए, क्योंकि यह आपके कोड बेस में होने वाले बदलावों के हिसाब से काम करता है:

  • कीप रूल, उस कोड के साथ ही मौजूद रहते हैं जिनके लिए इनकी ज़रूरत होती है. रिफ़्लेक्शन का इस्तेमाल करने वाले कोड का नाम बदलने या उसे मिटाने पर, उसी जगह पर एनोटेशन अपडेट किया जाता है. सिंक में रखने के लिए, नियमों की कोई अलग फ़ाइल नहीं होती.

  • जनरेट किए गए नियम, शर्तों के साथ लागू होते हैं. R8, रिफ़्लेक्टिव कॉल के टारगेट को सिर्फ़ तब सेव रखता है, जब वह कॉल करने वाले कोड को भी सेव रखता है. अगर कॉल करने वाला कोड इस्तेमाल नहीं किया जाता और R8 उसे हटा देता है, तो R8 टारगेट को भी हटा सकता है. ऐसा अक्सर, लाइब्रेरी की वैकल्पिक सुविधाओं के मामले में होता है. इसलिए, इस्तेमाल न की गई सुविधाओं के नियम, कोड को ज़रूरत से ज़्यादा बड़ा नहीं बनाते.

  • Lint, आपके लिए रिफ़्लेक्शन ढूंढता है. लाइब्रेरी में, 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 प्लगिन (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 प्लगिन, दोनों को जोड़ें. लाइब्रेरी, एनोटेशन और Lint की जांच उपलब्ध कराती है. वहीं, प्लगिन, एनोटेशन को कीप रूल में बदलता है.

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

    Gradle, प्लगिन को Google की Maven रिपॉज़िटरी से डाउनलोड करता है. इसलिए, पक्का करें कि आपकी 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 लाइब्रेरी मॉड्यूल के लिए, अतिरिक्त सेटअप की ज़रूरत होती है.

Lint की मदद से रिफ़्लेक्शन ढूंढना

Jetpack Keep में, Lint की ये जांच शामिल होती हैं:

ReflectionWithoutKeepAnnotations
यह Class.forName, getDeclaredConstructor, getDeclaredMethod, और getDeclaredField जैसी रिफ़्लेक्टिव कॉल को फ़्लैग करता है. इनमें, मैचिंग कीप एनोटेशन नहीं होता.
MissingKeepAnnotationPlugin
यह उन मॉड्यूल को फ़्लैग करता है जो androidx.annotation.keep:keep पर निर्भर करते हैं, लेकिन androidx.annotation.keep Gradle प्लगिन लागू नहीं करते. प्लगिन के बिना, आपके बिल्ड में एनोटेशन से कीप रूल जनरेट नहीं होते.

Android मॉड्यूल को एनोटेट करने के लिए, Lint का इस्तेमाल करने के लिए, यह तरीका अपनाएं:

  1. रिफ़्लेक्टिव कॉल ढूंढने के लिए, मॉड्यूल पर Lint चलाएं. इन कॉल के लिए एनोटेशन की ज़रूरत होती है:

    ./gradlew :app:lint
    
  2. Lint की मदद से, अपने-आप होने वाले सुधार लागू करें:

    ./gradlew :app:lintFix
    

    Lint, एनोटेशन को अपने-आप जोड़ सकता है. ऐसा तब होता है, जब वह यह तय कर पाता है कि कोड, किस क्लास और सदस्यों को रिफ़्लेक्ट करता है.

  3. जोड़े गए हर एनोटेशन की समीक्षा करके पुष्टि करें कि यह आपके कोड के काम से मेल खाता है. उदाहरण के लिए, यह देखें कि पैरामीटर के टाइप, आपके कोड से कॉल किए गए कंस्ट्रक्टर या तरीके से मेल खाते हैं या नहीं.

  4. Android Studio में, बाकी समस्याओं को हल करें. जब Lint, टारगेट क्लास तय नहीं कर पाता, तो क्विक फ़िक्स, एनोटेशन को TODO प्लेसहोल्डर के साथ जोड़ता है, ताकि आप इसे पूरा कर सकें. एनोटेशन को मैन्युअल तरीके से भी जोड़ा जा सकता है.

    उदाहरण के लिए, इस फ़ंक्शन को क्लास का नाम, पैरामीटर के तौर पर मिलता है. इसलिए, Lint यह तय नहीं कर पाता कि यह किस क्लास को बनाता है. क्विक फ़िक्स, प्लेसहोल्डर के साथ एनोटेशन जोड़ता है:

    @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. कीप रूल की फ़ाइलों से, उन कीप रूल को हटाएं जिन्हें नए एनोटेशन से बदला गया है. ऐसा भी हो सकता है कि किसी नियम में, ऐसे रिफ़्लेक्शन को भी शामिल किया गया हो जिसे Lint ने फ़्लैग नहीं किया. जैसे, किसी दूसरे मॉड्यूल या लाइब्रेरी में मौजूद रिफ़्लेक्शन. इसलिए, किसी नियम को हटाने से पहले उसकी जांच करें:

    • पूरे पैकेज पर लागू होने वाले नियम के लिए, जैसे कि -keep class com.example.** { *; }, पक्का करें कि एनोटेशन या अन्य नियमों में, पैकेज की क्लास पर मौजूद सभी रिफ़्लेक्शन शामिल हों.
    • खास क्लास या सदस्यों को सेव रखने वाले नियम के लिए, पक्का करें कि यह जनरेट किए गए किसी नियम से मेल खाता हो या इस पर कोई दूसरा रिफ़्लेक्शन निर्भर न हो.
  7. अपने रिलीज़ बिल्ड की जांच करें.

अपने कोड को एनोटेट करना

रिफ़्लेक्शन करने वाले कोड में, Jetpack Keep का एनोटेशन जोड़ें. जैसे, वह फ़ंक्शन, कंस्ट्रक्टर, फ़ील्ड या क्लास जिसमें रिफ़्लेक्टिव कॉल शामिल है. एनोटेशन, रिफ़्लेक्शन के टारगेट के बारे में बताता है. साथ ही, जनरेट किया गया नियम, उस टारगेट को सिर्फ़ तब सेव रखता है, जब R8, एनोटेट किए गए कोड को सेव रखता है.

अपने कोड के काम के हिसाब से कोई एनोटेशन चुनें:

एनोटेशन इसका इस्तेमाल तब करें, जब आपका कोड
@UsesReflectionToConstruct रिफ़्लेक्शन के ज़रिए कंस्ट्रक्टर को कॉल करता है. जैसे, getDeclaredConstructor().newInstance() का इस्तेमाल करके.
@UsesReflectionToAccessMethod रिफ़्लेक्शन के ज़रिए किसी तरीके को कॉल करता है. जैसे, getMethod या getDeclaredMethod का इस्तेमाल करके.
@UsesReflectionToAccessField रिफ़्लेक्शन के ज़रिए किसी फ़ील्ड को पढ़ता या लिखता है. जैसे, getField या getDeclaredField का इस्तेमाल करके.
@UnconditionallyKeep आपके ऐप्लिकेशन के Kotlin या Java कोड के बाहर से कॉल किया जाता है. जैसे, JNI के ज़रिए नेटिव कोड से. इस एनोटेशन का इस्तेमाल सिर्फ़ तब करें, जब @UsesReflection* एनोटेशन का इस्तेमाल न किया जा सके. ये एनोटेशन, शर्तों के साथ लागू होने वाले नियम जनरेट करते हैं.

@UsesReflection* के सभी एनोटेशन, इन कन्वेंशन का पालन करते हैं:

  • `classConstant`classConstant या `className`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)
    }
}

अपने ऐप्लिकेशन के बाहर से कॉल किए गए कोड को सेव रखना

ऐसे कोड के लिए @UnconditionallyKeep का इस्तेमाल करें जिसे आपके ऐप्लिकेशन के Kotlin या Java कोड के बाहर से कॉल किया जाता है. जैसे, 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 के बारे में ज़्यादा जानकारी के लिए, Interaction 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 बनाने पर, प्लगिन, जनरेट किए गए नियमों को AAR की proguard.txt फ़ाइल में जोड़ता है. यह तरीका, Android लाइब्रेरी के लिए इस्तेमाल किए जाने वाले तरीके जैसा ही होता है. प्लगिन, यह काम करने के लिए 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, कोर लाइब्रेरी और वीडियो ऐड-ऑन शिप कर सकता है. ऐड-ऑन, कोर लाइब्रेरी पर निर्भर करता है. इसलिए, कोर लाइब्रेरी, ऐड-ऑन की क्लास को सीधे तौर पर रेफ़र नहीं कर सकती. इसके बजाय, कोर लाइब्रेरी, रनटाइम में नाम के हिसाब से ऐड-ऑन की क्लास ढूंढती है. साथ ही, क्लास मिलने पर ही वीडियो Analytics चालू करती है.

कोर लाइब्रेरी, ऐड-ऑन की क्लास का इस्तेमाल सिर्फ़ रिफ़्लेक्शन के ज़रिए करती है. इसलिए, 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 को सेव रखता है. एसडीके का इस्तेमाल करने वाले ऐप्लिकेशन, इस नियम को अपने-आप लागू करते हैं. भले ही, वे Jetpack Keep का इस्तेमाल न करें. इसलिए, ऐड-ऑन को अपने नियम की ज़रूरत नहीं होती. ऐड-ऑन के नियम की तुलना में, इस तरीके के ये फ़ायदे हैं:

  • नियम, लुकअप के साथ ही मौजूद रहता है. कोर लाइब्रेरी, लुकअप और नियम, दोनों की मालिक है. इसलिए, दो लाइब्रेरी में बदलाव को कोऑर्डिनेट करने के बजाय, दोनों को एक साथ अपडेट किया जा सकता है.
  • R8, इस्तेमाल न किए गए कोड को अब भी हटा सकता है. अगर किसी ऐप्लिकेशन में ऐड-ऑन शामिल है, लेकिन वह initialize को कभी कॉल नहीं करता, तो जनरेट किया गया नियम लागू नहीं होता. साथ ही, R8, VideoEventTracker को हटा सकता है. ऐड-ऑन का नियम, इसे हर उस ऐप्लिकेशन में सेव रखता है जिसमें ऐड-ऑन शामिल होता है.
  • Lint, बिना एनोटेशन वाले लुकअप को पकड़ता है. अगर कोर लाइब्रेरी बाद में, बिना एनोटेशन वाला कोई दूसरा रिफ़्लेक्टिव लुकअप जोड़ती है, तो Lint उसे फ़्लैग करता है. इससे, लाइब्रेरी रिलीज़ करने से पहले उसे ठीक किया जा सकता है.

नतीजे की पुष्टि करने के लिए, देखें कि जनरेट किया गया नियम, AAR की proguard.txt फ़ाइल में दिखता है या नहीं. इसके लिए, Android लाइब्रेरी में बताया गया तरीका अपनाएं. इसके बाद, ऐड-ऑन शामिल करने वाले किसी ऐप्लिकेशन के रिलीज़ के लिए तैयार बिल्ड की जांच करें. ऐसा इसलिए, क्योंकि नियम मौजूद न होने पर, वीडियो Analytics में समस्या आ सकती है.

अन्य संसाधन