Jetpack Keep की मदद से, Kotlin या Java सोर्स कोड में एनोटेशन का इस्तेमाल करके, रिफ़्लेक्शन और Java नेटिव इंटरफ़ेस (जेएनआई) के इस्तेमाल के बारे में बताया जा सकता है. इसके लिए, आपको अलग से कीप रूल फ़ाइलें बनाने की ज़रूरत नहीं पड़ती. Jetpack Keep Gradle प्लग इन, बिल्ड के समय इन एनोटेशन को पढ़ता है और R8 के लिए ज़रूरी 'कीप रूल' जनरेट करता है.
Jetpack Keep, आपके कोड बेस में बदलाव होने पर भी R8 ऑप्टिमाइज़ेशन को असरदार बनाए रखने में मदद करता है:
Keep नियमों को उस कोड के बगल में रखें जिसके लिए उनकी ज़रूरत है. रिफ़्लेक्शन का इस्तेमाल करने वाले कोड का नाम बदलने या उसे मिटाने पर, एनोटेशन को उसी जगह पर अपडेट किया जाता है. सिंक करने के लिए, नियमों की कोई अलग फ़ाइल नहीं होती.
जनरेट किए गए नियम, शर्त के साथ लागू होते हैं. 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 प्लगिन (AGP) 8.7 या इसके बाद का वर्शन होना चाहिए.
Gradle प्लग इन, इन मॉड्यूल टाइप के साथ काम करता है:
| मॉड्यूल का टाइप | मॉड्यूल में इस्तेमाल किया जाने वाला प्लग इन | अतिरिक्त सेटअप |
|---|---|---|
| Android ऐप्लिकेशन | com.android.application |
कोई नहीं |
| Android लाइब्रेरी | com.android.library |
कोई नहीं |
| Android टारगेट वाली Kotlin Multiplatform लाइब्रेरी | com.android.kotlin.multiplatform.library |
कोई नहीं |
| जेवीएम लाइब्रेरी (Java या Kotlin) | java-library या org.jetbrains.kotlin.jvm |
JAR ट्रांसफ़ॉर्म रजिस्टर करना |
Jetpack Keep सेट अप करना
Jetpack Keep का इस्तेमाल करने के लिए, लाइब्रेरी और Gradle प्लग इन, दोनों को हर उस मॉड्यूल में जोड़ें जो रिफ़्लेक्शन या जेएनआई का इस्तेमाल करता है. लाइब्रेरी, एनोटेशन और लिंट चेक उपलब्ध कराती है. साथ ही, प्लगिन, एनोटेशन को कीप रूल में बदल देता है.
अपनी
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()शामिल हो.अपनी रूट
build.gradle.ktsफ़ाइल में, एजीपी के साथ प्लगिन के बारे में जानकारी दें. हालांकि, इसे लागू न करें: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 }रिफ़्लेक्शन या जेएनआई का इस्तेमाल करने वाले हर मॉड्यूल की
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) }अपने प्रोजेक्ट को Gradle फ़ाइलों के साथ सिंक करें.
Android ऐप्लिकेशन मॉड्यूल के हर बिल्ड वैरिएंट के लिए, प्लग इन एक <variant>ExtractKeepRules टास्क रजिस्टर करता है, जैसे कि releaseExtractKeepRules. साथ ही, जनरेट किए गए नियमों को उस वैरिएंट के लिए R8 को पास करता है. लाइब्रेरी मॉड्यूल के लिए, प्लगिन जनरेट किए गए नियमों को लाइब्रेरी के आउटपुट में, उपभोक्ता के डेटा को सुरक्षित रखने से जुड़े नियमों के तौर पर पैकेज करता है.
जेवीएम लाइब्रेरी मॉड्यूल के लिए, अतिरिक्त सेटअप की ज़रूरत होती है.
लिंट की मदद से रिफ़्लेक्शन ढूंढना
Jetpack Keep में ये लिंट चेक शामिल हैं:
ReflectionWithoutKeepAnnotations- यह
Class.forName,getDeclaredConstructor,getDeclaredMethod, औरgetDeclaredFieldजैसे रिफ़्लेक्टिव कॉल को फ़्लैग करता है. इनमें, मैच करने वाला कीप एनोटेशन नहीं होता. MissingKeepAnnotationPlugin- उन मॉड्यूल को फ़्लैग करता है जो
androidx.annotation.keep:keepपर निर्भर करते हैं, लेकिनandroidx.annotation.keepGradle प्लग इन लागू नहीं करते. प्लग इन के बिना, आपका बिल्ड एनोटेशन से कीप रूल जनरेट नहीं करता.
किसी Android मॉड्यूल के बारे में जानकारी देने के लिए, लिंट का इस्तेमाल करने का तरीका यहां बताया गया है:
मॉड्यूल पर लिंट चलाएं, ताकि ऐसे रिफ़्लेक्टिव कॉल ढूंढे जा सकें जिनके लिए एनोटेशन की ज़रूरत है:
./gradlew :app:lintलिंट की मदद से, अपने-आप होने वाले सुधार लागू करें:
./gradlew :app:lintFixलिंट टूल, एनोटेशन को अपने-आप जोड़ सकता है. ऐसा तब होता है, जब वह यह तय कर पाता है कि कोड किस क्लास और सदस्यों को दिखाता है.
जोड़े गए हर एनोटेशन की समीक्षा करके पुष्टि करें कि वह आपके कोड के काम करने के तरीके से मेल खाता है. उदाहरण के लिए, देखें कि पैरामीटर टाइप, कंस्ट्रक्टर या उस तरीके से मैच करते हैं या नहीं जिसे आपका कोड कॉल करता है.
Android Studio में बाकी बचे नतीजों को ठीक करें. जब लिंट, टारगेट क्लास का पता नहीं लगा पाता, तब क्विक फ़िक्स,
TODOप्लेसहोल्डर के साथ एनोटेशन को शामिल करता है, ताकि आप उसे पूरा कर सकें. एनोटेशन को मैन्युअल तरीके से भी जोड़ा जा सकता है.उदाहरण के लिए, यहां दिए गए फ़ंक्शन को क्लास का नाम पैरामीटर के तौर पर मिलता है. इसलिए, लिंट यह तय नहीं कर सकता कि यह किस क्लास को बनाता है. क्विक फ़िक्स, प्लेसहोल्डर के साथ एक एनोटेशन जोड़ता है:
@UsesReflectionToConstruct( className = TODO(), parameterTypes = [] ) fun createInstance(className: String): Any = Class.forName(className).getDeclaredConstructor().newInstance()प्लेसहोल्डर की जगह वह क्लास डालें जिसे आपका कोड बनाता है. अगर कंपाइल के समय क्लास उपलब्ध है, तो
classNameके बजायclassConstantका इस्तेमाल करें. Kotlin में, आपका कोड तब तक कंपाइल नहीं होता, जब तकTODO()को बदला नहीं जाता.किसी भी मौजूदा नियम को हटाने से पहले, यह देखें कि प्लग इन आपके एनोटेशन से कौनसे नियम जनरेट करता है. किसी ऐप्लिकेशन मॉड्यूल के लिए, रिलीज़ वैरिएंट के लिए एक्सट्रैक्शन टास्क चलाएं:
./gradlew :app:releaseExtractKeepRulesयह प्लगिन, नियमों को
app/build/generated/release/androidx.annotation.keep.rules.proमें लिखता है. लाइब्रेरी मॉड्यूल के लिए, जनरेट किए गए नियमों की पुष्टि करना देखें.अपने कीप रूल की फ़ाइलों से, कीप रूल हटाएं. इनकी जगह नए एनोटेशन इस्तेमाल किए जाएंगे. किसी नियम में ऐसा रिफ़्लेक्शन भी शामिल हो सकता है जिसे लिंट ने फ़्लैग न किया हो. जैसे, किसी दूसरे मॉड्यूल या लाइब्रेरी में मौजूद रिफ़्लेक्शन. इसलिए, किसी नियम को हटाने से पहले उसकी जांच कर लें:
-keep class com.example.** { *; }जैसे पैकेज-वाइड नियम के लिए, पक्का करें कि एनोटेशन या अन्य नियम, पैकेज की क्लास पर सभी रिफ़्लेक्शन को कवर करते हों.- किसी खास क्लास या कोड कॉम्पोनेंट को ऑप्टिमाइज़ेशन की प्रोसेस से सुरक्षित रखने वाले नियम के लिए, पक्का करें कि वह जनरेट किए गए किसी नियम से पूरी तरह मेल खाता हो या उस पर कोई अन्य रिफ़्लेक्शन निर्भर न हो.
रिलीज़ के लिए तैयार बिल्ड को टेस्ट करें.
अपने कोड में एनोटेशन जोड़ना
रिफ़्लेक्शन करने वाले कोड में Jetpack Keep एनोटेशन जोड़ें: वह फ़ंक्शन, कंस्ट्रक्टर, फ़ील्ड या क्लास जिसमें रिफ़्लेक्टिव कॉल शामिल है. एनोटेशन, रिफ़्लेक्शन के टारगेट के बारे में बताता है. साथ ही, जनरेट किया गया नियम उस टारगेट को सिर्फ़ तब बनाए रखता है, जब R8 एनोटेट किए गए कोड को बनाए रखता है.
आपका कोड क्या करता है, इसके आधार पर कोई एनोटेशन चुनें:
| टिप्पणी | कोड का इस्तेमाल तब करें, जब |
|---|---|
@UsesReflectionToConstruct |
रिफ़्लेक्शन के ज़रिए कंस्ट्रक्टर को कॉल करता है. उदाहरण के लिए, getDeclaredConstructor().newInstance() के साथ. |
@UsesReflectionToAccessMethod |
रिफ़्लेक्शन के ज़रिए किसी तरीके को कॉल करना. उदाहरण के लिए, getMethod या getDeclaredMethod के साथ. |
@UsesReflectionToAccessField |
रिफ़्लेक्शन की मदद से किसी फ़ील्ड को पढ़ता या लिखता है. उदाहरण के लिए, getField या getDeclaredField के साथ. |
@UnconditionallyKeep |
इसे आपके ऐप्लिकेशन के Kotlin या Java कोड के बाहर से कॉल किया जाता है. उदाहरण के लिए, जेएनआई के ज़रिए नेटिव कोड से. इस एनोटेशन का इस्तेमाल सिर्फ़ तब करें, जब @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)
}
}
अपने ऐप्लिकेशन के बाहर से कॉल किए गए कोड को बनाए रखना
@UnconditionallyKeep का इस्तेमाल ऐसे कोड के लिए करें जिसे आपके ऐप्लिकेशन के Kotlin या Java कोड के बाहर से कॉल किया जाता है. जैसे, ऐसा तरीका जिसे नेटिव कोड, जेएनआई के ज़रिए कॉल करता है. कॉल किए गए एलिमेंट को एनोटेट करें, न कि उसे कॉल करने वाले कोड को:
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()
}
जनरेट किया गया नियम, एनोटेट किए गए तरीके और उसमें मौजूद क्लास को उनके ओरिजनल नामों के साथ सुरक्षित रखता है. यह उन अन्य क्लास को सेव नहीं करता है जिन्हें मेथड का सिग्नेचर रेफ़र करता है. अगर नेटिव कोड उन क्लास पर निर्भर करता है, तो उन्हें भी एनोटेट करें या उनके लिए कीप रूल जोड़ें. जेएनआई और R8 के बारे में ज़्यादा जानकारी के लिए, जेएनआई की मदद से नेटिव कोड के साथ इंटरैक्शन लेख पढ़ें.
डिफ़ॉल्ट रूप से, @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 लाइब्रेरी मॉड्यूल के लिए, किसी अन्य सेटअप की ज़रूरत नहीं होती. एएआर बनाते समय, प्लग इन जनरेट किए गए नियमों को एएआर की proguard.txt फ़ाइल में जोड़ता है. ऐसा, एएआर में पहले से मौजूद उपभोक्ता के लिए बनाए गए नियमों के बाद किया जाता है. ऐसा करने के लिए, प्लग इन हर बिल्ड वैरिएंट के लिए एक <variant>KeepRulesTransformAar टास्क रजिस्टर करता है.
नियमों की पुष्टि करने के लिए, एएआर बनाएं. उदाहरण के लिए, ./gradlew
:mylibrary:assembleRelease की मदद से एएआर बनाएं और जनरेट किए गए एएआर में 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 टारगेट के लिए एएआर बनाते समय, प्लग इन जनरेट किए गए नियमों को एएआर की proguard.txt फ़ाइल में जोड़ता है. यह ठीक उसी तरह होता है जैसे Android लाइब्रेरी के लिए होता है. प्लग इन, ऐसा करने के लिए एक androidMainKeepRulesTransformAar टास्क को रजिस्टर करता है.
नियमों की पुष्टि करने के लिए, उस टास्क को चलाएं और उससे बनने वाले एएआर में proguard.txt खोलें. macOS और Linux पर, कमांड लाइन से नियमों को प्रिंट किया जा सकता है:
./gradlew :mylibrary:androidMainKeepRulesTransformAar
unzip -p mylibrary/build/intermediates/aar/androidMain/androidMainKeepRulesTransformAar/mylibrary.aar proguard.txt
जेवीएम लाइब्रेरी
जेवीएम लाइब्रेरी मॉड्यूल के नियमों की पुष्टि करने से पहले, मॉड्यूल को कॉन्फ़िगर करें, ताकि प्लग इन, जनरेट किए गए नियमों को अपने 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/*"
जेवीएम लाइब्रेरी कॉन्फ़िगर करना
JAR बनाने वाले जेवीएम लाइब्रेरी मॉड्यूल के लिए, प्लग इन 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एक खाली अरे है.
जब कोर लाइब्रेरी का एएआर बनाया जाता है, तो प्लगिन एक नियम जोड़ता है. यह नियम, VideoEventTracker और इसके बिना आर्ग्युमेंट वाले कंस्ट्रक्टर को तब बनाए रखता है, जब R8 initialize को बनाए रखता है. एसडीके का इस्तेमाल करने वाले ऐप्लिकेशन पर यह नियम अपने-आप लागू हो जाता है. भले ही, वे Jetpack Keep का इस्तेमाल न करते हों. इसलिए, ऐड-ऑन के लिए अलग से नियम बनाने की ज़रूरत नहीं होती.
ऐड-ऑन के नियम की तुलना में, इस तरीके के ये फ़ायदे हैं:
- नियम, लुकअप के बगल में रहता है. कोर लाइब्रेरी में लुकअप और नियम, दोनों होते हैं. इसलिए, दो लाइब्रेरी में बदलाव करने के बजाय, उन्हें एक साथ अपडेट किया जाता है.
- R8 अब भी इस्तेमाल न होने वाले कोड को हटा सकता है. अगर किसी ऐप्लिकेशन में ऐड-ऑन शामिल है, लेकिन वह कभी भी
initializeको कॉल नहीं करता है, तो जनरेट किया गया नियम लागू नहीं होता है. साथ ही, R8VideoEventTrackerको हटा सकता है. ऐड-ऑन का नियम, इसे हर उस ऐप्लिकेशन में बनाए रखता है जिसमें ऐड-ऑन शामिल होता है. - लिंट, बिना एनोटेशन वाले लुकअप को पकड़ता है. अगर कोर लाइब्रेरी में बाद में बिना एनोटेशन के एक और रिफ़्लेक्टिव लुकअप जोड़ा जाता है, तो लिंट उसे फ़्लैग कर देता है. इससे लाइब्रेरी को रिलीज़ करने से पहले, उसे ठीक किया जा सकता है.
नतीजे की पुष्टि करने के लिए, देखें कि जनरेट किया गया नियम, AAR की proguard.txt फ़ाइल में दिखता है या नहीं. इसके बारे में Android लाइब्रेरी में बताया गया है. इसके बाद,
ऐप्लिकेशन के रिलीज़ के लिए तैयार बिल्ड की जांच करें, जिसमें ऐड-ऑन शामिल हो. ऐसा इसलिए, क्योंकि
नियम मौजूद न होने पर, वीडियो के आंकड़े नहीं दिखेंगे.
सीमाएं
Jetpack Keep, अब तक रिफ़्लेक्शन का पता नहीं लगाता है. रिफ़्लेक्शन, एनोटेशन के हिसाब से क्लास और सदस्यों को ढूंढता है या इसके लिए Keep के नियम जनरेट करता है. इस सुविधा को लॉन्च किया जाएगा.
तब तक, इस तरह के रिफ़्लेक्शन के लिए 'रखें' नियम का इस्तेमाल करें. उदाहरण के लिए, यहां दिया गया नियम, R8 की ओर से सुरक्षित रखी गई क्लास में, @SomeAnnotation से एनोटेट किए गए हर कोड कॉम्पोनेंट को सुरक्षित रखता है:
-keepclassmembers class * {
@com.example.annotation.SomeAnnotation <methods>;
@com.example.annotation.SomeAnnotation <fields>;
}
अगर Jetpack Keep आपके इस्तेमाल के उदाहरण के लिए काम नहीं करता है या आपके पास कोई अन्य सुझाव/राय है, तो समस्या की शिकायत करें.
अन्य संसाधन
- Jetpack Keep का सैंपल: Android Studio का एक प्रोजेक्ट, जो किसी ऐप्लिकेशन, Android लाइब्रेरी, Kotlin मल्टीप्लैटफ़ॉर्म लाइब्रेरी, और JVM लाइब्रेरी में Jetpack Keep का इस्तेमाल करता है.
- Keep नियम के इस्तेमाल के उदाहरण: सामान्य रिफ़्लेक्शन और जेएनआई स्थितियों के लिए Keep नियम.
- सबसे सही तरीके अपनाएं: डेटा को सेव रखने के लिए, कम समय के लिए लागू होने वाले नियम लिखने के बारे में दिशा-निर्देश.