किसी क्लास (या सबक्लास या लागू करने का तरीका) और उस क्लास में मौजूद तरीकों, कंस्ट्रक्टर या फ़ील्ड जैसे कोड कॉम्पोनेंट को ऑप्टिमाइज़ेशन की प्रोसेस से सुरक्षित रखने के लिए, कीप रूल तय किया जाता है.
कीप रूल का सामान्य सिंटैक्स इस तरह होता है:
-<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 |
इससे, तय किए गए एलिमेंट को ऑप्टिमाइज़ करने की अनुमति मिलती है. हालांकि, बताए गए एलिमेंट का नाम नहीं बदला जाता या उन्हें हटाया नहीं जाता. |
allowobfucastion |
इससे, चुने गए एलिमेंट का नाम बदला जा सकता है. हालांकि, एलिमेंट को हटाया नहीं जाता और न ही उन्हें ऑप्टिमाइज़ किया जाता है. |
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कोड कॉम्पोनेंट को कंपाइल किया जाता है, तो कंपाइल किए गए उसके सिग्नेचर का मिलान, कीप रूल में मौजूद सिग्नेचर से करें.उदाहरण के लिए, अगर आपने
fetchUserफ़ंक्शन को इस स्निपेट में दिखाए गए तरीके से तय किया है:suspend fun fetchUser(id: String): Userकंपाइल करने पर, बाइटकोड में इसका सिग्नेचर इस तरह दिखता है:
public final Object fetchUser(String id, Continuation<? super User> 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 एनोटेशन लाइब्रेरी इस तरीके का इस्तेमाल करती है.
तरीके
किसी कीप रूल के लिए, कोड कॉम्पोनेंट की खास जानकारी में किसी तरीके को तय करने का सिंटैक्स इस तरह होता है:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
उदाहरण के लिए, नीचे दिया गया कीप रूल, setLabel() नाम के एक सार्वजनिक तरीके को सुरक्षित रखता है. यह तरीका, कोई वैल्यू नहीं दिखाता और String लेता है.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
किसी क्लास के सभी तरीकों को मैच करने के लिए, <methods> को शॉर्टकट के तौर पर इस तरह इस्तेमाल किया जा सकता है:
-keep class com.example.MyView {
<methods>;
}
रिटर्न टाइप और पैरामीटर टाइप के लिए टाइप तय करने के तरीके के बारे में ज़्यादा जानने के लिए, टाइप देखें.
कंस्ट्रक्टर
कंस्ट्रक्टर के बारे में बताने के लिए, <init> का इस्तेमाल करें. निजी डेटा के रखरखाव के नियम के लिए, कोड कॉम्पोनेंट की खास जानकारी में कंस्ट्रक्टर तय करने का सिंटैक्स इस तरह होता है:
[<access_modifier>] <init>(parameter_types);
उदाहरण के लिए, यहां दिया गया कीप रूल, कस्टम View कंस्ट्रक्टर को सेव रखता है. यह Context और AttributeSet लेता है.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
सभी सार्वजनिक कंस्ट्रक्टर को बनाए रखने के लिए, इस उदाहरण का रेफ़रंस दें:
-keep class com.example.ui.MyCustomView {
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>();
}
पैकेज-लेवल के फ़ंक्शन
अगर आपको किसी क्लास के बाहर तय किए गए 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 को लागू करने वाली कोई क्लास नहीं मिलती है, तो वह इस कीप रूल को अनदेखा कर देता है.
इसका इस्तेमाल, JSON सीरियलाइज़ेशन लाइब्रेरी जैसे कि Gson के साथ भी किया जाता है. अगर आपके डेटा मॉडल क्लास, किसी फ़ील्ड पर 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 पर जाएं.