किसी क्लास (या सबक्लास या लागू करने का तरीका) और उस क्लास में मौजूद तरीकों, कंस्ट्रक्टर या फ़ील्ड जैसे कोड कॉम्पोनेंट को ऑप्टिमाइज़ेशन की प्रोसेस से सुरक्षित रखने के लिए, कीप रूल तय किया जाता है.
कीप रूल का सामान्य सिंटैक्स इस तरह होता है:
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
यहां, कीप रूल का उदाहरण दिया गया है. इसमें keepclassmembers को सुरक्षित रखने के विकल्प के तौर पर, allowoptimization को मॉडिफ़ायर के तौर पर इस्तेमाल किया गया है. साथ ही, com.example.MyClass से someSpecificMethod() को सुरक्षित रखा गया है:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
कीप ऑप्शन
कीप ऑप्शन, कीप रूल का पहला हिस्सा होता है. इससे यह तय किया जाता है कि कोड ऑप्टिमाइज़ेशन के दौरान क्लास के कौन-कौनसे कोड कॉम्पोनेंट को सुरक्षित रखना है. कीप रूल में छह अलग-अलग विकल्प होते हैं. जैसे, keep,
keepclassmembers, keepclasseswithmembers, keepnames,
keepclassmembernames, और keepclasseswithmembernames.
यहां दी गई टेबल में, कीप रूल के इन विकल्पों के बारे में बताया गया है:
| कीप ऑप्शन | जानकारी |
|---|---|
keepclassmembers |
यह विकल्प, क्लास में तय किए गए कोड कॉम्पोनेंट को सिर्फ़ तब सुरक्षित रखता है, जब R8 उस क्लास को न हटाए जिसमें वे कॉम्पोनेंट शामिल हैं. |
keep |
यह विकल्प, तय की गई क्लास के साथ-साथ उसके फ़ील्ड और तरीकों को भी सुरक्षित रखता है. इससे उन्हें ऑप्टिमाइज़ होने से रोका जाता है. ध्यान दें: आम तौर पर, keep का इस्तेमाल सिर्फ़ कीप ऑप्शन मॉडिफ़ायर के साथ किया जाना चाहिए. क्योंकि, keep का इस्तेमाल करने पर, मैच की गई क्लास के लिए किसी भी तरह का ऑप्टिमाइज़ेशन नहीं किया जा सकता. |
keepclasseswithmembers |
यह विकल्प, किसी क्लास में तय किए गए कोड कॉम्पोनेंट को सिर्फ़ तभी सुरक्षित रखता है, जब क्लास में उसके सभी कोड कॉम्पोनेंट शामिल हों. |
keepclassmembernames |
इससे क्लास में शामिल कोड कॉम्पोनेंट के नाम बदलने पर रोक लग जाती है. हालांकि, इससे क्लास या उसमें शामिल कोड कॉम्पोनेंट को हटाने पर कोई असर नहीं पड़ता. ध्यान दें: इस विकल्प का मतलब अक्सर गलत समझा जाता है. इसलिए, इसके बजाय ठीक वैसा ही -keepclassmembers,allowshrinking इस्तेमाल करें. |
keepnames |
इससे क्लास और उसके कोड कॉम्पोनेंट के नाम बदलने से रोका जाता है. हालांकि, अगर क्लास का इस्तेमाल नहीं किया जा रहा है, तो उसे पूरी तरह से हटाने से नहीं रोका जाता. ध्यान दें: इस विकल्प का मतलब अक्सर गलत समझा जाता है. इसलिए, इसके बजाय ठीक वैसा ही -keep,allowshrinking इस्तेमाल करें. |
keepclasseswithmembernames |
इससे क्लास और उसके तय किए गए कोड कॉम्पोनेंट के नाम बदलने से रोका जाता है. हालांकि, ऐसा सिर्फ़ तब किया जा सकता है, जब कोड कॉम्पोनेंट फ़ाइनल कोड में मौजूद हों. इससे कोड को हटाने से नहीं रोका जा सकता. ध्यान दें: इस विकल्प का मतलब अक्सर गलत समझा जाता है. इसलिए, इसके बजाय ठीक वैसा ही -keepclasseswithmembers,allowshrinking इस्तेमाल करें. |
सही कीप ऑप्शन चुनना
अपने ऐप्लिकेशन के लिए सही ऑप्टिमाइज़ेशन तय करने के लिए, सही कीप ऑप्शन चुनना अहम है. कुछ कीप ऑप्शन कोड को छोटा कर देते हैं. इस प्रोसेस में, बिना रेफ़रंस वाले कोड को हटा दिया जाता है. वहीं, अन्य विकल्पों से कोड को छिपा दिया जाता है या उसका नाम बदल दिया जाता है. यहां दी गई टेबल में, अलग-अलग कीप विकल्पों में की जाने वाली कार्रवाइयों के बारे में बताया गया है:
| कीप ऑप्शन | ऐप्लिकेशन में इस्तेमाल न होने वाली क्लास को हटा देता है | ऐप्लिकेशन की क्लास के नाम बदल कर उन्हें छिपाता है | क्लास में मौजूद कोड कॉम्पोनेंट की संख्या कम करता है | कोड कॉम्पोनेंट के नाम बदल कर उन्हें छिपाता है |
|---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
कीप ऑप्शन मॉडिफ़ायर
कीप रूल के दायरे और काम करने के तरीके को कंट्रोल करने के लिए, कीप ऑप्शन मॉडिफ़ायर का इस्तेमाल किया जाता है. कीप रूल में, 0 या उससे ज़्यादा कीप ऑप्शन मॉडिफ़ायर जोड़े जा सकते हैं.
कीप ऑप्शन मॉडिफ़ायर की वैल्यू के बारे में यहां दी गई टेबल में बताया गया है:
| वैल्यू | जानकारी |
|---|---|
allowoptimization |
इससे, तय किए गए एलिमेंट को ऑप्टिमाइज़ करने की अनुमति मिलती है. हालांकि, बताए गए एलिमेंट का नाम नहीं बदला जाता या उन्हें हटाया नहीं जाता. |
allowobfuscation |
इससे, चुने गए एलिमेंट का नाम बदला जा सकता है. हालांकि, एलिमेंट को हटाया नहीं जाता और न ही उन्हें ऑप्टिमाइज़ किया जाता है. |
allowshrinking |
अगर R8 को, बताए गए एलिमेंट के कोई रेफ़रंस नहीं मिलते हैं, तो उन्हें हटाने की अनुमति देता है. हालांकि, एलिमेंट का नाम नहीं बदला जाता है और न ही उन्हें ऑप्टिमाइज़ किया जाता है. |
includedescriptorclasses |
यह R8 को उन सभी क्लास को बनाए रखने का निर्देश देता है जो सुरक्षित रखी जा रही क्लास के तरीकों (पैरामीटर टाइप और रिटर्न टाइप) और फ़ील्ड (फ़ील्ड टाइप) के डिस्क्रिप्टर में दिखती हैं. |
allowaccessmodification |
इससे R8 को ऑप्टिमाइज़ेशन प्रोसेस के दौरान, क्लास, तरीकों, और फ़ील्ड के ऐक्सेस मॉडिफ़ायर (public, private, protected) बदलने की अनुमति मिलती है. आम तौर पर, इससे R8 को ज़्यादा क्लास, तरीकों, और फ़ील्ड को ऐक्सेस करने की अनुमति मिलती है. |
allowrepackage |
इस विकल्प की मदद से, R8 क्लास को अलग-अलग पैकेज में ले जा सकता है. इनमें डिफ़ॉल्ट (रूट) पैकेज भी शामिल है. |
क्लास की जानकारी
आपको हर कीप रूल में एक क्लास (इसमें इंटरफ़ेस, एनम, और एनोटेशन क्लास शामिल हैं) तय करनी होगी. एनोटेशन के आधार पर, नियम को सीमित किया जा सकता है. इसके लिए, सुपरक्लास या लागू किया गया इंटरफ़ेस तय करें या क्लास के लिए ऐक्सेस मॉडिफ़ायर तय करें. सभी क्लास, जिनमें java.lang नेमस्पेस की क्लास जैसे कि java.lang.String शामिल हैं, को उनके पूरी तरह क्वालिफ़ाइड Java नाम का इस्तेमाल करके तय किया जाना चाहिए. इस्तेमाल किए जाने वाले नामों को समझने के लिए, जनरेट किए गए Java नामों की जांच करना में बताए गए टूल का इस्तेमाल करके, बाइटकोड की जांच करें.
यहां दिए गए उदाहरण में, MaterialButton क्लास को तय करने का तरीका बताया गया है:
- सही:
com.google.android.material.button.MaterialButton - गलत:
MaterialButton
क्लास स्पेसिफ़िकेशन में, क्लास के उन कोड कॉम्पोनेंट के बारे में भी जानकारी दी जाती है जिन्हें सुरक्षित रखना है. उदाहरण के लिए, यहां दिया गया नियम MyClass क्लास और someSpecificMethod() तरीके को सुरक्षित रखता है:
-keep class com.example.MyClass {
void someSpecificMethod();
}
एनोटेशन के आधार पर क्लास के बारे में जानकारी देना
एनोटेशन के आधार पर क्लास तय करने के लिए, एनोटेशन के पूरी तरह क्वालिफ़ाइड Java नाम के आगे @ सिंबल लगाएं. उदाहरण के लिए:
-keep class @com.example.MyAnnotation com.example.MyClass
अगर किसी कीप रूल में एक से ज़्यादा एनोटेशन हैं, तो यह उन क्लास को सेव करता है जिनमें सूची में दिए गए सभी एनोटेशन मौजूद हैं. एक से ज़्यादा एनोटेशन लिस्ट किए जा सकते हैं. हालांकि, यह नियम सिर्फ़ तब लागू होता है, जब क्लास में लिस्ट किया गया हर एनोटेशन मौजूद हो. उदाहरण के लिए, यहां दिया गया नियम, Annotation1 और Annotation2, दोनों से एनोटेट की गई सभी क्लास को बनाए रखता है.
-keep class @com.example.Annotation1 @com.example.Annotation2 *
सबक्लास और उनके लागू होने के तरीके के बारे में बताएं
किसी सबक्लास या इंटरफ़ेस को लागू करने वाली क्लास को टारगेट करने के लिए, extend और implements का इस्तेमाल करें.
उदाहरण के लिए, अगर आपके पास Bar क्लास है और उसकी सबक्लास Foo इस तरह है:
class Foo : Bar()
यहां दिया गया कीप रूल, Bar की सभी सबक्लास को सुरक्षित रखता है. ध्यान दें कि कीप रूल में, सुपरक्लास Bar को शामिल नहीं किया जाता.
-keep class * extends Bar
अगर आपके पास Foo क्लास है, जो Bar इंटरफ़ेस को लागू करती है, तो:
class Foo : Bar
नीचे दिए गए कीप रूल से, Bar को लागू करने वाली सभी क्लास को सुरक्षित रखा जाता है. ध्यान दें कि कीप रूल में, इंटरफ़ेस Bar शामिल नहीं है.
-keep class * implements Bar
ऐक्सेस मॉडिफ़ायर के आधार पर क्लास के बारे में जानकारी देना
अपने कीप रूल को ज़्यादा सटीक बनाने के लिए, ऐक्सेस मॉडिफ़ायर तय किए जा सकते हैं. जैसे, public, private, static, और final.
उदाहरण के लिए, यहां दिया गया नियम, api पैकेज और उसके सब-पैकेज में मौजूद सभी public क्लास और इन क्लास में मौजूद सभी सार्वजनिक और सुरक्षित कोड कॉम्पोनेंट को बनाए रखता है.
-keep public class com.example.api.** { public protected *; }
किसी क्लास में शामिल कोड कॉम्पोनेंट के लिए भी मॉडिफ़ायर इस्तेमाल किए जा सकते हैं. उदाहरण के लिए, यहां दिया गया नियम, Utils क्लास के सिर्फ़ public static तरीकों को बनाए रखता है:
-keep class com.example.Utils {
public static void *(...);
}
Kotlin के लिए खास तौर पर बनाए गए मॉडिफ़ायर
R8, Kotlin के खास मॉडिफ़ायर के साथ काम नहीं करता. जैसे, internal और suspend.
ऐसे फ़ील्ड रखने के लिए, यहां दिए गए दिशा-निर्देशों का पालन करें.
internalक्लास, तरीके या फ़ील्ड को सुरक्षित रखने के लिए, उसे सार्वजनिक के तौर पर इस्तेमाल करें. उदाहरण के लिए, यहां Kotlin का सोर्स कोड दिया गया है:package com.example internal class ImportantInternalClass { internal val f: Int internal fun m() {} }Kotlin कंपाइलर से जनरेट की गई
.classफ़ाइलों में,internalक्लास, मेथड, और फ़ील्डpublicहोते हैं. इसलिए, आपकोpublicकीवर्ड का इस्तेमाल करना होगा, जैसा कि इस उदाहरण में दिखाया गया है:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }जब किसी
suspendकोड कॉम्पोनेंट को कंपाइल किया जाता है, तो कंपाइल किए गए उसके बाइट कोड सिग्नेचर का मिलान, कीप रूल में मौजूद सिग्नेचर से करें.उदाहरण के लिए, Kotlin में तय की गई रीयल-वर्ल्ड रिपॉज़िटरी क्लास को देखें:
package com.example.repository import com.example.model.User class UserRepository { suspend fun fetchUser(id: String): User { // Implementation details... } }जब Kotlin कंपाइलर इस क्लास को बाइटकोड में कंपाइल करता है, तो
suspendफ़ंक्शन, कंटीन्यूएशन-पासिंग स्टाइल (सीपीएस) ट्रांसफ़ॉर्मेशन से गुज़रते हैं.suspendमॉडिफ़ायर हटा दिया जाता है, रिटर्न टाइप कोjava.lang.Objectमें बदल दिया जाता है, और एसिंक्रोनस स्टेट मशीन को मैनेज करने के लिए,kotlin.coroutines.Continuationपैरामीटर को मेथड सिग्नेचर में जोड़ दिया जाता है.बाइट कोड में कंपाइल किए गए तरीके का सिग्नेचर इस तरह दिखता है:
public final Object fetchUser(String id, Continuation<? super User> continuation);इस फ़ंक्शन के लिए निजी डेटा के रखरखाव का नियम लिखने के लिए, Kotlin
suspendसिंटैक्स का इस्तेमाल नहीं किया जा सकता. इसके बजाय, आपको कंपाइल किए गए बाइटकोड सिग्नेचर (खास तौर परkotlin.coroutines.Continuationका रेफ़रंस देना) से मिलान करना होगा या...का इस्तेमाल करना होगा.कंपाइल किए गए सटीक बाइट कोड सिग्नेचर से मैच करने वाला उदाहरण यहां दिया गया है:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }...का इस्तेमाल करने का एक उदाहरण यहां दिया गया है:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
कोड कॉम्पोनेंट की जानकारी
क्लास स्पेसिफ़िकेशन में, क्लास के उन कोड कॉम्पोनेंट को शामिल किया जा सकता है जिन्हें सुरक्षित रखना है. अगर किसी क्लास के लिए एक या उससे ज़्यादा कोड कॉम्पोनेंट को चुना जाता है, तो यह नियम अन्य कोड कॉम्पोनेंट पर लागू नहीं होता.
एनोटेशन के आधार पर कोड कॉम्पोनेंट के बारे में जानकारी देना
एनोटेशन के आधार पर कोड कॉम्पोनेंट तय किए जा सकते हैं. क्लास की तरह ही, एनोटेशन के पूरी तरह क्वालिफ़ाइड Java नाम के आगे @ प्रीफ़िक्स लगाएं. इसकी मदद से, किसी क्लास में सिर्फ़ उन कोड कॉम्पोनेंट को रखा जा सकता है जिन्हें खास एनोटेशन के साथ मार्क किया गया है. उदाहरण के लिए, @com.example.MyAnnotation के साथ एनोटेट किए गए तरीकों और फ़ील्ड को बनाए रखने के लिए:
-keep class com.example.MyClass {
@com.example.MyAnnotation <methods>;
@com.example.MyAnnotation <fields>;
}
बेहतर तरीके से टारगेट किए गए नियमों के लिए, इसे क्लास-लेवल के एनोटेशन मैचिंग के साथ जोड़ा जा सकता है:
-keep class @com.example.ClassAnnotation * {
@com.example.MethodAnnotation <methods>;
@com.example.FieldAnnotation <fields>;
}
इससे @ClassAnnotation के साथ एनोटेट की गई क्लास सुरक्षित रहती हैं. साथ ही, उन क्लास में @MethodAnnotation के साथ एनोटेट किए गए तरीके और @FieldAnnotation के साथ एनोटेट किए गए फ़ील्ड भी सुरक्षित रहते हैं.
जहां तक हो सके, एनोटेशन के आधार पर डेटा बनाए रखने के नियमों का इस्तेमाल करें. इस तरीके से, आपके कोड और कीप रूल के बीच एक साफ़ लिंक मिलता है. साथ ही, इससे अक्सर ज़्यादा मज़बूत कॉन्फ़िगरेशन मिलते हैं. उदाहरण के लिए, androidx.annotation एनोटेशन लाइब्रेरी इस तरीके का इस्तेमाल करती है.
अपने कोड में रिफ़्लेक्शन के लिए, Jetpack Keep का भी इस्तेमाल किया जा सकता है. एनोटेशन को टारगेट करने वाले कीप रूल लिखने के बजाय, अपने कोड में एनोटेशन जोड़ें. इसके बाद, Gradle प्लग इन आपके लिए कीप रूल जनरेट करेगा.
तरीके
किसी कीप रूल के लिए, कोड कॉम्पोनेंट की खास जानकारी में किसी तरीके को तय करने का सिंटैक्स इस तरह होता है:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
उदाहरण के लिए, यहां दिया गया कीप रूल, getUserId() नाम के एक पब्लिक मेथड को सुरक्षित रखता है. यह मेथड, String दिखाता है.
-keep class com.example.model.UserData {
public java.lang.String getUserId();
}
किसी क्लास के सभी तरीकों को मैच करने के लिए, <methods> को शॉर्टकट के तौर पर इस तरह इस्तेमाल किया जा सकता है:
-keep class com.example.model.UserData {
<methods>;
}
रिटर्न टाइप और पैरामीटर टाइप के लिए टाइप तय करने के तरीके के बारे में ज़्यादा जानने के लिए, टाइप देखें.
निर्माता
कंस्ट्रक्टर के बारे में बताने के लिए, <init> का इस्तेमाल करें. निजी डेटा के रखरखाव के नियम के लिए, कोड कॉम्पोनेंट की खास जानकारी में कंस्ट्रक्टर तय करने का सिंटैक्स इस तरह होता है:
[<access_modifier>] <init>(parameter_types);
उदाहरण के लिए, यहां दिया गया कीप रूल, यूज़र इंटरफ़ेस (यूआई) स्टेट होल्डर के लिए एक कंस्ट्रक्टर को सेव रखता है. यह डेटा स्टोर करने की जगह का इंस्टेंस लेता है.
-keep class com.example.ui.state.UserViewModel {
public <init>(com.example.repository.UserDataRepository);
}
सभी सार्वजनिक कंस्ट्रक्टर को बनाए रखने के लिए, इस उदाहरण का रेफ़रंस दें:
-keep class com.example.ui.state.UserViewModel {
public <init>(...);
}
फ़ील्ड
किसी कीप रूल के लिए कोड कॉम्पोनेंट की खास जानकारी में फ़ील्ड तय करने का सिंटैक्स यहां दिया गया है:
[<access_modifier>...] [<type>] <field_name>;
उदाहरण के लिए, यहां दिया गया कीप रूल, userId नाम के एक प्राइवेट स्ट्रिंग फ़ील्ड और STATUS_ACTIVE नाम के एक पब्लिक स्टैटिक इंटिजर फ़ील्ड को सुरक्षित रखता है:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
किसी क्लास के सभी फ़ील्ड को मैच करने के लिए, <fields> को शॉर्टकट के तौर पर इस तरह इस्तेमाल किया जा सकता है:
-keep class com.example.models.User {
<fields>;
}
टाइप
इस सेक्शन में, कीप रूल के कोड कॉम्पोनेंट के स्पेसिफ़िकेशन में, रिटर्न टाइप, पैरामीटर टाइप, और फ़ील्ड टाइप तय करने का तरीका बताया गया है. अगर टाइप, Kotlin सोर्स कोड से अलग हैं, तो टाइप तय करने के लिए जनरेट किए गए Java नामों का इस्तेमाल करना न भूलें.
प्रिमिटिव टाइप
प्रिमिटिव टाइप तय करने के लिए, उसके Java कीवर्ड का इस्तेमाल करें. R8 इन प्रिमिटिव टाइप को पहचानता है: boolean, byte, short, char, int, long, float, double.
प्रिमिटिव टाइप वाले नियम का एक उदाहरण यहां दिया गया है:
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
सामान्य टाइप
कंपाइलेशन के दौरान, Kotlin/Java कंपाइलर सामान्य टाइप की जानकारी मिटा देता है. इसलिए, जेनरिक टाइप वाले कीप रूल लिखते समय, आपको अपने कोड के कंपाइल किए गए वर्शन को टारगेट करना होगा, न कि ओरिजनल सोर्स कोड को. जेनरिक टाइप में बदलाव करने के तरीके के बारे में ज़्यादा जानने के लिए, टाइप इरेज़र देखें.
उदाहरण के लिए, अगर आपके पास Box.kt में तय किए गए अनबाउंडेड जेनेरिक टाइप वाला यह कोड है:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
टाइप इरेज़र के बाद, T को Object से बदल दिया जाता है. क्लास कंस्ट्रक्टर और तरीके को सुरक्षित रखने के लिए, आपके नियम में सामान्य T के बजाय java.lang.Object का इस्तेमाल किया जाना चाहिए.
कीप रूल का एक उदाहरण यहां दिया गया है:
# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
public init(java.lang.Object);
public java.lang.Object getItem();
}
अगर आपके पास NumberBox.kt में बाउंडेड जेनरिक टाइप वाला यह कोड है:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
इस मामले में, टाइप इरेज़र T को उसके बाउंड java.lang.Number से बदल देता है.
कीप रूल का एक उदाहरण यहां दिया गया है:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
ऐप्लिकेशन के हिसाब से जेनरिक टाइप को बेस क्लास के तौर पर इस्तेमाल करते समय, बेस क्लास के लिए भी कीप रूल को शामिल करना ज़रूरी है.
उदाहरण के लिए, इस कोड के लिए:
package com.myapp.data
data class UnpackOptions(val useHighPriority: Boolean)
// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}
includedescriptorclasses के साथ कीप रूल का इस्तेमाल करके, UnpackOptions क्लास और Box क्लास के तरीके, दोनों को एक ही नियम के साथ सुरक्षित रखा जा सकता है. इसके लिए, यहां दिया गया तरीका अपनाएं:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
अगर आपको ऑब्जेक्ट की सूची को प्रोसेस करने वाले किसी फ़ंक्शन को बनाए रखना है, तो एक ऐसा नियम लिखना होगा जो फ़ंक्शन के सिग्नेचर से पूरी तरह मेल खाता हो. ध्यान दें कि जेनरिक टाइप मिटा दिए जाते हैं. इसलिए, List<Product> जैसे पैरामीटर को java.util.List के तौर पर देखा जाता है.
उदाहरण के लिए, अगर आपके पास एक यूटिलिटी क्लास है, जिसमें एक ऐसा फ़ंक्शन है जो Product ऑब्जेक्ट की सूची को इस तरह प्रोसेस करता है:
package com.myapp.utils
import com.myapp.data.Product
import android.util.Log
class DataProcessor {
// This is the function we want to keep
fun processProducts(products: List<Product>) {
Log.d("DataProcessor", "Processing ${products.size} products.")
// Business logic ...
}
}
// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)
सिर्फ़ processProductsफ़ंक्शन को सुरक्षित रखने के लिए, इस कीप रूल का इस्तेमाल किया जा सकता है:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
ऐरे टाइप
ऐरे के हर डाइमेंशन के लिए, कॉम्पोनेंट टाइप में [] जोड़कर ऐरे का टाइप तय करें. यह क्लास टाइप और प्रिमिटिव टाइप, दोनों पर लागू होता है.
- एक डाइमेंशन वाला क्लास ऐरे:
java.lang.String[] - दो डाइमेंशन वाला प्रिमिटिव ऐरे:
int[][]
उदाहरण के लिए, अगर आपके पास यह कोड है:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
यहां दिया गया कीप रूल इस्तेमाल किया जा सकता है:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
उदाहरण
उदाहरण के लिए, किसी क्लास और उसके सभी कोड कॉम्पोनेंट को सुरक्षित रखने के लिए, इसका इस्तेमाल करें:
-keep class com.myapp.MyClass { *; }
सिर्फ़ क्लास और उसके डिफ़ॉल्ट कंस्ट्रक्टर को सुरक्षित रखने के लिए, लेकिन अन्य कोड कॉम्पोनेंट को नहीं, तो इसका इस्तेमाल करें:
-keep class com.myapp.MyClass
हमारा सुझाव है कि आप हमेशा कुछ कोड कॉम्पोनेंट के बारे में बताएं. जैसे, यहां दिए गए उदाहरण में क्लास MyClass में, पब्लिक फ़ील्ड text और पब्लिक मेथड updateText() को रखा गया है.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
सभी सार्वजनिक फ़ील्ड और सार्वजनिक तरीके सुरक्षित रखने के लिए, यह उदाहरण देखें:
-keep public class com.example.api.ApiClient {
public *;
}
कोड कॉम्पोनेंट की जानकारी शामिल न करना
कोड कॉम्पोनेंट के स्पेसिफ़िकेशन को हटाने पर, R8 क्लास के लिए डिफ़ॉल्ट कंस्ट्रक्टर को बनाए रखता है.
उदाहरण के लिए, अगर आपने -keep class com.example.MyClass या -keep class com.example.MyClass {} लिखा है, तो R8 इन्हें इस तरह से प्रोसेस करेगा:
-keep class com.example.MyClass{
void <init>();
}
सदस्य के नाम के पैटर्न को नेगेट करना
Android Gradle प्लगिन (AGP) 9.2.0 और इसके बाद के वर्शन में, कीप रूल में मेंबर के नाम के पैटर्न को हटाया जा सकता है. इसकी मदद से, पैटर्न के आधार पर कोड कॉम्पोनेंट को रखने या हटाने के बारे में बताया जा सकता है. किसी पैटर्न को नकारने के लिए, सदस्य के नाम के पैटर्न के आगे विस्मयादिबोधक चिह्न (!) लगाएं.
इस सुविधा का इस्तेमाल उन स्थितियों में किया जा सकता है जहां आपको ज़्यादातर उन सदस्यों को शामिल करना है जो किसी बड़े पैटर्न से मेल खाते हैं, लेकिन कुछ को बाहर रखना है. जैसे, सिर्फ़ टेस्टिंग के लिए बनाए गए तरीके.
उदाहरण:
com.example.MyClass में मौजूद सभी सार्वजनिक तरीकों को सुरक्षित रखने के लिए, यहां दिया गया नियम इस्तेमाल करें. हालांकि, "ForTesting" पर खत्म होने वाले तरीकों को सुरक्षित नहीं रखा जा सकता:
-keepclassmembers class com.example.MyClass {
public *** !*ForTesting(...);
}
पैकेज-लेवल के फ़ंक्शन
किसी क्लास के बाहर तय किए गए Kotlin फ़ंक्शन (आम तौर पर, टॉप लेवल फ़ंक्शन कहा जाता है) को रेफ़रंस के तौर पर इस्तेमाल करने के लिए, Kotlin कंपाइलर से जोड़ी गई क्लास के लिए, जनरेट किए गए Java नाम का इस्तेमाल करना न भूलें. क्लास का नाम, Kotlin फ़ाइल नाम होता है. इसमें Kt जोड़ा जाता है. उदाहरण के लिए, अगर आपके पास MyClass.kt नाम की Kotlin फ़ाइल है, जिसे इस तरह से तय किया गया है:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
isEmailValid फ़ंक्शन के लिए निजी डेटा के रखरखाव का नियम लिखने के लिए, क्लास स्पेसिफ़िकेशन को जनरेट की गई क्लास MyClassKt को टारगेट करना होगा:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
वाइल्डकार्ड
यहां दी गई टेबल में बताया गया है कि वाइल्डकार्ड का इस्तेमाल करके, एक जैसे पैटर्न वाली कई क्लास या कोड कॉम्पोनेंट पर, सुरक्षित रखने के नियम कैसे लागू किए जाते हैं.
| वाइल्डकार्ड | क्लास या कोड कॉम्पोनेंट पर लागू होता है | जानकारी |
|---|---|---|
| ** | दोनों | इसका इस्तेमाल सबसे ज़्यादा किया जाता है. यह किसी भी टाइप के नाम से मेल खाता है. इसमें पैकेज सेपरेटर की कोई भी संख्या शामिल हो सकती है. यह किसी पैकेज और उसके सब-पैकेज में मौजूद सभी क्लास का मिलान करने के काम आता है. |
| * | दोनों | क्लास स्पेसिफ़िकेशन के लिए, यह टाइप के नाम के किसी भी ऐसे हिस्से से मेल खाता है जिसमें पैकेज सेपरेटर (.) शामिल नहीं होते हैं कोड कॉम्पोनेंट स्पेसिफ़िकेशन के लिए, यह किसी भी तरीके या फ़ील्ड के नाम से मेल खाता है. इसका इस्तेमाल अकेले करने पर, यह ** का उपनाम भी होता है. |
| ? | दोनों | क्लास या कोड कॉम्पोनेंट के नाम में मौजूद किसी भी वर्ण से मेल खाता है. |
| *** | कोड कॉम्पोनेंट | यह किसी भी टाइप से मेल खाता है. इसमें प्रिमिटिव टाइप (जैसे, int), क्लास टाइप (जैसे, java.lang.String), और किसी भी डाइमेंशन के अरे टाइप (जैसे, byte[][]) शामिल हैं. |
| ... | कोड कॉम्पोनेंट | यह किसी तरीके के लिए, पैरामीटर की किसी भी सूची से मैच करता है. |
| % | सदस्य | यह किसी भी प्रिमिटिव टाइप से मैच करता है. जैसे, int, float, boolean या अन्य. |
यहां खास वाइल्डकार्ड इस्तेमाल करने के कुछ उदाहरण दिए गए हैं:
अगर आपके पास एक ही नाम वाले कई ऐसे तरीके हैं जो अलग-अलग प्रिमिटिव टाइप को इनपुट के तौर पर लेते हैं, तो
%का इस्तेमाल करके, एक ऐसा नियम लिखा जा सकता है जो उन सभी को सुरक्षित रखता है. उदाहरण के लिए, इसDataStoreक्लास में कईsetValueतरीके हैं:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }कीप रूल के तहत, सभी तरीकों को सुरक्षित रखा जाता है:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }अगर आपके पास ऐसी कई क्लास हैं जिनके नाम में सिर्फ़ एक वर्ण का अंतर है, तो उन सभी को सुरक्षित रखने के लिए,
?का इस्तेमाल करके एक नियम लिखें. उदाहरण के लिए, अगर आपके पास ये क्लास हैं:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}यहां दिया गया कीप रूल, सभी क्लास को सुरक्षित रखता है:
-keep class com.example.models.UserV?अगर आपको
ExampleऔरAnotherExampleक्लास (अगर वे रूट-लेवल की क्लास थीं) को मैच करना है, लेकिनcom.foo.Exampleको नहीं, तो इस कीप रूल का इस्तेमाल करें:-keep class *Exampleसिर्फ़ * का इस्तेमाल करने पर, यह ** के लिए उपनाम के तौर पर काम करता है. उदाहरण के लिए, यहां दिए गए कीप रूल एक जैसे हैं:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
शर्त के साथ डेटा सेव रखने के नियम
स्टैंडर्ड कीप रूल के अलावा, शर्त के साथ कीप रूल का इस्तेमाल किया जा सकता है. ये रूल सिर्फ़ तब लागू होते हैं, जब कोई खास शर्त पूरी होती है. -if फ़्लैग का इस्तेमाल करके, शर्तों वाले नियम तय किए जा सकते हैं. -if फ़्लैग के बाद आने वाला कीप रूल सिर्फ़ तब चालू होता है, जब -if फ़्लैग में मौजूद क्लास की जानकारी मैच होती है.
सुरक्षित रखने के नियम, खास तौर पर उन लाइब्रेरी या कोड पैटर्न के लिए फ़ायदेमंद होते हैं जिनमें रिफ़्लेक्शन का इस्तेमाल किया जाता है. रिफ़्लेक्शन में, सुरक्षित रखने के नियम की ज़रूरत सिर्फ़ तब होती है, जब कोई क्लास या कोड कॉम्पोनेंट मौजूद हो या किसी पैटर्न से मेल खाता हो. शर्त के हिसाब से नियमों का इस्तेमाल करने से, आपके ऐप्लिकेशन का साइज़ कम करने में मदद मिलती है. ऐसा इसलिए होता है, क्योंकि इससे गैर-ज़रूरी कोड को बनाए रखने से रोका जा सकता है.
शर्त के साथ लागू होने वाले कीप रूल का सामान्य सिंटैक्स इस तरह होता है:
-if <class_specification_if> <keep_rule>
अगर -if शर्त में क्लास स्पेसिफ़िकेशन में वाइल्डकार्ड (जैसे, * या **) शामिल हैं, तो वाइल्डकार्ड से मैच करने वाले वर्णों के क्रम को कैप्चर किया जाता है.
बैकरेफ़रंस का इस्तेमाल करके, कैप्चर की गई इन स्ट्रिंग का रेफ़रंस अगले कीप रूल में दिया जा सकता है: <1> का मतलब है कि पहले वाइल्डकार्ड से कैप्चर की गई स्ट्रिंग, <2> का मतलब है कि दूसरे वाइल्डकार्ड से कैप्चर की गई स्ट्रिंग वगैरह.
उदाहरण के लिए, Jetpack Navigation कॉम्पोनेंट, डेस्टिनेशन के बीच टाइप-सेफ़ आर्ग्युमेंट पास करने के लिए, NavArgs क्लास जनरेट करता है. NavArgsLazy डेलिगेट का इस्तेमाल करने पर, यह रिफ़्लेक्शन का इस्तेमाल करके जनरेट की गई NavArgs क्लास पर स्टैटिक fromBundle तरीके को ढूंढता है और उसे लागू करता है, ताकि आर्ग्युमेंट को डिसिरियलाइज़ किया जा सके. अगर आपका ऐप्लिकेशन NavArgs का इस्तेमाल करता है, तो आपको सिर्फ़ उन क्लास के लिए fromBundle तरीका बनाए रखना होगा जो NavArgs इंटरफ़ेस को लागू करती हैं.
शर्त के साथ कीप रूल का इस्तेमाल करके, यह तय किया जा सकता है कि अगर कोई क्लास androidx.navigation.NavArgs को लागू करती है, तो R8 को उस क्लास के लिए fromBundle तरीके को सुरक्षित रखना चाहिए:
# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
public static ** fromBundle(android.os.Bundle);
}
इस उदाहरण में, **, -if शर्त में पहला और इकलौता वाइल्डकार्ड है. यह androidx.navigation.NavArgs को लागू करने वाली किसी भी क्लास के नाम से मेल खाता है. ** से मैच करने वाली स्ट्रिंग (इस मामले में, क्लास का नाम) को कैप्चर किया जाता है. इसके बाद, <1> का इस्तेमाल करके, अगले नियम में इसका रेफ़रंस दिया जा सकता है. इसलिए, -keepclassmembers नियम, androidx.navigation.NavArgs को लागू करने वाली किसी भी क्लास पर लागू होता है. यह क्लास, -if शर्त से मैच करती है. अगर R8 को NavArgs को लागू करने वाली कोई क्लास नहीं मिलती है, तो वह इस कीप रूल को अनदेखा कर देता है.
इस्तेमाल का एक और सामान्य उदाहरण, Gson जैसी JSON सीरियलाइज़ेशन लाइब्रेरी के साथ है. अगर आपकी डेटा मॉडल क्लास, किसी फ़ील्ड पर Gson के @SerializedName एनोटेशन का इस्तेमाल करती हैं, तो ऐसी किसी भी क्लास और उसके कोड कॉम्पोनेंट को सुरक्षित रखने के लिए, शर्त वाले नियम का इस्तेमाल किया जा सकता है. Gson को रिफ़्लेक्शन के लिए इसकी ज़रूरत होती है:
# If a class has fields annotated with @SerializedName...
-if class ** { @com.google.gson.annotations.SerializedName <fields>; }
# ...then keep that class (<1>), its @SerializedName fields,
# and its constructors for Gson.
-keep class <1> {
@com.google.gson.annotations.SerializedName <fields>;
<init>(...);
}
बैकरेफ़रंस, स्ट्रिंग कैप्चर करते हैं. अगर वाइल्डकार्ड, नाम के सिर्फ़ किसी हिस्से से मैच करता है, तो ये स्ट्रिंग, क्लास के नामों की सबस्ट्रिंग हो सकती हैं. उदाहरण के लिए, अगर -if class com.example.*X* का इस्तेमाल किया जाता है, तो R8, X से पहले की सबस्ट्रिंग को <1> के तौर पर और X के बाद की सबस्ट्रिंग को <2> के तौर पर कैप्चर करता है. यहां दिया गया नियम, इस तरीके का इस्तेमाल करके ऐसी क्लास का नाम ढूंढता है जिसमें X शामिल हो. साथ ही, उससे मिलती-जुलती क्लास को सुरक्षित रखता है, जिसमें X को Y से बदल दिया गया हो:
# If a class like com.example.PrefixXPostfix exists...
-if class com.example.*X*
# ...keep com.example.PrefixYPostfix.
-keep class com.example.<1>Y<2>
रिफ़्लेक्शन के लिए, शर्त के साथ डेटा सेव रखने के नियम
शर्त के साथ लागू होने वाले कीप रूल का इस्तेमाल, रिफ़्लेक्शन को मैनेज करने के लिए किया जाता है. इसमें रनटाइम के दौरान, खास तरीकों या क्लास को डाइनैमिक तौर पर ऐक्सेस किया जाता है. उदाहरण के लिए, अगर कोई लाइब्रेरी आपके कोड के साथ इंटरैक्ट करने के लिए रिफ़्लेक्शन का इस्तेमाल करती है, तो हो सकता है कि आपको सिर्फ़ कुछ सदस्यों को बनाए रखने की ज़रूरत हो. ऐसा तब होता है, जब आपको उस लाइब्रेरी की किसी खास सुविधा का इस्तेमाल करना हो.
Jetpack Navigation लाइब्रेरी, जनरेट की गई NavArgs क्लास पर स्टैटिक fromBundle तरीके को कॉल करने के लिए, NavArgsLazy डेलिगेट का इस्तेमाल करके रिफ़्लेक्शन का इस्तेमाल करती है. इससे टाइप-सेफ़ आर्ग्युमेंट पास किया जा सकता है. यह पक्का करने के लिए कि यह तरीका सिर्फ़ NavArgs को लागू करने के लिए इस्तेमाल किया जाए, न कि हर क्लास के लिए, Jetpack Navigation में शर्त के साथ लागू होने वाला यह कीप रूल शामिल है:
# If a class implements NavArgs...
-if public class ** implements androidx.navigation.NavArgs
# ...then keep the fromBundle method of that matched class (<1>).
-keepclassmembers public class <1> {
public static ** fromBundle(android.os.Bundle);
}
यह नियम, fromBundle को सिर्फ़ उन क्लास के लिए सेव रखता है जिनमें इसकी ज़रूरत होती है. इससे, इसे सभी क्लास में सेव रखने या मैन्युअल तरीके से यह बताने की ज़रूरत नहीं पड़ती कि किन क्लास में इसकी ज़रूरत है.
जनरेट किए गए Java नामों की जांच करना
कीप रूल लिखते समय, आपको क्लास और अन्य रेफ़रंस टाइप के नाम बताने होंगे. ऐसा तब करना होगा, जब उन्हें Java बाइटकोड में कंपाइल कर लिया गया हो. उदाहरण के लिए, क्लास स्पेसिफ़िकेशन और टाइप देखें. अपने कोड के लिए जनरेट किए गए Java नाम देखने के लिए, Android Studio में इनमें से किसी एक टूल का इस्तेमाल करें:
- APK ऐनालाइज़र
- Kotlin सोर्स फ़ाइल खोलें. इसके बाद, Tools > Kotlin > Show Kotlin Bytecode > Decompile पर जाकर, बाइट कोड की जांच करें.