R8 की मदद से, प्लैटफ़ॉर्म कोड और संसाधनों को ऑप्टिमाइज़ करना

Android प्लैटफ़ॉर्म का बिल्ड सिस्टम (Soong), DEX बाइट कोड को कंपाइल करने वाले टारगेट पर R8 कंपाइलर को चलाता है. इनमें Android ऐप्लिकेशन (android_app), टेस्ट (android_test), और compile_dex: true के साथ इंस्टॉल की जा सकने वाली Java लाइब्रेरी (जैसे, services.jar) शामिल हैं. ऐसा, इस्तेमाल न किए गए कोड और संसाधनों को छोटा करने, ऑप्टिमाइज़ करने, और हटाने के लिए किया जाता है. स्टैटिक लाइब्रेरी (android_library और स्टैटिक java_library) पर, Soong सीधे तौर पर R8 को नहीं चलाता. इसके बजाय, वह optimize प्रॉपर्टी ब्लॉक का इस्तेमाल करके, उपभोक्ता के कीप रूल (export_proguard_flags_files: true) को डाउनस्ट्रीम DEX टारगेट से अटैच और प्रोपेगेट करता है. ये टारगेट, उन्हें स्टैटिक तौर पर लिंक करते हैं.

सिस्टम इमेज या वेंडर पैकेज बनाने वाले प्लैटफ़ॉर्म इंजीनियर के लिए, बहुत ज़्यादा ब्रॉड कीप रूल को हटाने से सिस्टम की परफ़ॉर्मेंस को बेहतर बनाने में मदद मिलती है:

  • सिस्टम पार्टिशन का साइज़ छोटा होना: R8, /system, /system_ext, /product, और /vendor पर APK और JAR फ़ाइलों को पैकेज करने से पहले, इस्तेमाल न की जाने वाली क्लास, तरीकों, और संसाधनों को हटा देता है.
  • छोटे कंपाइल किए गए आर्टफ़ैक्ट: कम DEX मेथड का मतलब है कि बिल्ड-टाइम या डिवाइस पर कंपाइलेशन के दौरान, dex2oat से जनरेट की गई .odex और .vdex फ़ाइलें छोटी होती हैं.
  • रनटाइम के दौरान मेमोरी पर कम दबाव: Android, .odex, .vdex, और APK रिसॉर्स टेबल को प्रोसेस मेमोरी में मैप करता है. इसके लिए, वह मांग के हिसाब से पेज लोड करने वाले mmap कॉल का इस्तेमाल करता है. इसलिए, छोटी बाइनरी फ़ाइलें, ऐप्लिकेशन के स्टार्टअप के दौरान पेज फ़ॉल्ट को कम करती हैं. साथ ही, प्रोसेस के दौरान रेज़िडेंट कोड और रिसॉर्स मेमोरी को कम करती हैं. कंपाइल किया गया ऐप्लिकेशन कोड, डिवाइस की मेमोरी पर कैसे असर डालता है, इस बारे में ज़्यादा जानने के लिए, ऐप्लिकेशन कोड मेमोरी है देखें.
  • पूरे प्रोग्राम को ज़्यादा असरदार तरीके से ऑप्टिमाइज़ करना: Narrow keep constraints की मदद से, R8 इनलाइन मेथड, इंटरफ़ेस और वर्चुअल कॉल को डीवर्चुअलाइज़ कर सकता है. साथ ही, इस्तेमाल न किए गए फ़ील्ड को हटा सकता है और क्लास में कॉन्स्टेंट को लागू कर सकता है.

Soong में R8 ऑप्टिमाइज़ेशन को कॉन्फ़िगर करना

Android.bp फ़ाइलों में, optimize प्रॉपर्टी ब्लॉक का इस्तेमाल करके R8 ऑप्टिमाइज़ेशन को कॉन्फ़िगर करें. इसके लिए, android_app और इंस्टॉल किए जा सकने वाले java_library टारगेट (या उपभोक्ता के लिए 'कीप रूल' एक्सपोर्ट करने के लिए, android_library और स्टैटिक java_library मॉड्यूल) पर जाएं:

android_app {
    name: "MySystemApp",
    srcs: ["src/**/*.java"],
    optimize: {
        obfuscate: true,
        shrink_resources: true,
    },
}

यहां दी गई टेबल में, Soong में सबसे ज़्यादा इस्तेमाल होने वाली optimize प्रॉपर्टी के बारे में खास जानकारी दी गई है. इन्हें build/soong/java/dex.go में बताया गया है:

प्रॉपर्टी ब्यौरा
enabled इससे यह कंट्रोल किया जाता है कि टारगेट पर R8 चलेगा या नहीं. सभी android_app टारगेट के लिए, डिफ़ॉल्ट रूप से true पर सेट होता है.
shrink यह विकल्प, ट्री शेकिंग को कंट्रोल करता है, ताकि ऐसी क्लास, फ़ील्ड, और तरीकों को हटाया जा सके जिन्हें ऐक्सेस नहीं किया जा सकता. android_app टारगेट के लिए, डिफ़ॉल्ट रूप से true पर सेट होता है. वहीं, स्टैंडअलोन java_library और टेस्ट मॉड्यूल के लिए, डिफ़ॉल्ट रूप से false पर सेट होता है. ये मॉड्यूल, -dontshrink को पास करते हैं. हालांकि, ऐसा तब होता है, जब इन्हें साफ़ तौर पर सेट न किया गया हो.
optimize यह बाइट कोड ऑप्टिमाइज़ेशन को कंट्रोल करता है. जैसे, मेथड इनलाइनिंग, क्लास मर्जिंग, कॉन्स्टेंट प्रोपेगेशन, और डेड ब्रांच को हटाना. android_app टारगेट के लिए, डिफ़ॉल्ट रूप से true पर सेट होता है. इसे RELEASE_R8_OPTIMIZE_BY_DEFAULT रिलीज़ के लिए तैयार बिल्ड फ़्लैग से कंट्रोल किया जाता है.
obfuscate यह आइडेंटिफ़ायर की काट-छांट और नाम बदलने की प्रोसेस को कंट्रोल करता है. यह डिफ़ॉल्ट रूप से false पर सेट होता है, ताकि पुराने वर्शन के साथ काम कर सके. हालांकि, DEX के साइज़ को कम करने और ज़्यादा ऑप्टिमाइज़ेशन अनलॉक करने के लिए, इसे जहां भी संभव हो true पर सेट करें. ज़्यादा जानकारी के लिए, जहां भी संभव हो वहां कोड के नाम को अस्पष्ट बनाने की सुविधा चालू करना लेख पढ़ें.
shrink_resources कोड छोटा करने के बाद, पैकेज किए गए APK से इस्तेमाल न किए गए रिसॉर्स (res/ एंट्री) हटाता है. डिफ़ॉल्ट रूप से, यह false पर सेट होता है.
optimized_shrink_resources यह इंटिग्रेट की गई R8 कोड और रिसॉर्स श्रिंकर पाइपलाइन को चलाता है, ताकि R8 एक ही बार में कोड और रिसॉर्स के रेफ़रंस को एक साथ ट्रेस कर सके. shrink_resources: true सेट होने पर, डिफ़ॉल्ट रूप से RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT पर सेट होता है. स्टैंडर्ड प्लैटफ़ॉर्म बिल्ड में, यह true पर सेट होता है.
proguard_flags_files इसमें मॉड्यूल के हिसाब से .flags या .pro फ़ाइलें शामिल होती हैं. इनमें, कस्टम कीप रूल होते हैं. जब स्टैंडर्ड एनोटेशन या डिफ़ॉल्ट एनोटेशन से काम चल जाए, तो कस्टम फ़ाइलें जोड़ने से बचें.
export_proguard_flags_files इस लाइब्रेरी के proguard_flags_files को उन डाउनस्ट्रीम मॉड्यूल में भेजता है जो इस पर स्टैटिक तौर पर निर्भर करते हैं.
trace_references_from यह उन डायरेक्ट DEX-कंपाइलिंग डिपेंडेंट टारगेट की सूची बनाता है जो instrumentation_for या libs से कनेक्ट होते हैं. साथ ही, जिनके बाइट कोड रेफ़रंस को इस टारगेट R8 में अपने-आप ट्रेस और सेव किया जाता है. इसके अलावा, इन्हें override_android_app और override_android_test वैरिएंट में भी लागू किया जाता है. साथ ही, .impl स्प्लिट के साथ जोड़े जाने पर, ये डिपेंडेंट न होने वाली कंपैनियन Java लाइब्रेरी के लिए भी काम करते हैं.
allow_traced_references यह instrumentation_for या libs के ज़रिए कनेक्ट किए गए, सीधे तौर पर DEX-कंपाइलिंग पर निर्भर टारगेट को अनुमति देता है. जैसे, क्रॉस-डेटा स्टोर करने की जगह या वेंडर टेस्ट, जिन्हें trace_references_from में शामिल नहीं किया जा सकता. इससे, trace_references: true को सेट करके, इस टारगेट में उनके बाइट कोड रेफ़रंस को ट्रेस करने की सुविधा चालू की जा सकती है.
trace_references allow_traced_references: true के साथ टारगेट पर सीधे DEX-कंपाइलिंग डिपेंडेंट पर true पर सेट होने पर, इस मॉड्यूल पर R8 tracereferences चलाता है और जनरेट किए गए कीप रूल को टारगेट मॉड्यूल पर पास करता है.
protect_api_surface --protect-api-surface को R8 पर पास करता है. java_sdk_library पर सेट होने पर, यह Metalava API के कीप रूल को अपने-आप जनरेट करता है और उन्हें चालू किए गए सभी एपीआई स्कोप (सार्वजनिक, सिस्टम, मॉड्यूल-लिब, सिस्टम-सर्वर, और टेस्ट) में, लागू करने वाली लाइब्रेरी से अटैच करता है.

स्टैटिक लाइब्रेरी (android_library, java_library, और एएआर इंपोर्ट) भी Android.bp में required_downstream_optimization: { enabled: true, optimize: true } सेट कर सकती हैं. इससे, डाउनस्ट्रीम DEX-कंपाइलिंग टारगेट को स्टैटिक तौर पर लिंक किया जा सकता है (सीधे तौर पर या ट्रांज़िटिव तौर पर), ताकि R8 की मदद से साइज़ छोटा करने और बाइट कोड को ऑप्टिमाइज़ करने की सुविधा चालू की जा सके. Soong, androidx.compose.runtime_runtime पर इसे अपने-आप चालू कर देता है, ताकि Jetpack Compose को लिंक करने वाले टारगेट, R8 के साथ ऑप्टिमाइज़ किए जा सकें.

जहां भी संभव हो, कोड को अस्पष्ट बनाने की सुविधा चालू करें

Soong में, obfuscate को डिफ़ॉल्ट रूप से false पर सेट किया जाता है, ताकि पुराने वर्शन के साथ काम किया जा सके. हालांकि, आपको जहां भी हो सके वहां android_app और स्टैंडअलोन DEX टारगेट पर, obfuscate: true को साफ़ तौर पर सेट करना चाहिए:

  • DEX और .vdex का छोटा फ़ुटप्रिंट: पैकेज, क्लास, फ़ील्ड, और मेथड का नाम बदलकर छोटे आइडेंटिफ़ायर (a, b) करने से, DEX स्ट्रिंग पूल (string_ids और string_data_item) और टाइप डिस्क्रिप्टर छोटे हो जाते हैं. Android, .vdex और .odex फ़ाइलों को प्रोसेस मेमोरी में मैप करता है. इसलिए, छोटे सिंबल टेबल से सिस्टम पार्टिशन का साइज़ और रनटाइम मेमोरी फ़ुटप्रिंट, दोनों सीधे तौर पर कम हो जाते हैं.
  • पूरे प्रोग्राम को बेहतर तरीके से ऑप्टिमाइज़ करना: R8 को आइडेंटिफ़ायर का नाम बदलने की अनुमति देने से, क्लास मर्ज करने, पैकेज को फ़्लैट करने, और सिंथेटिक क्लास की डुप्लीकेट कॉपी हटाने की सुविधा चालू हो जाती है. ऐसा उन पैकेज के लिए किया जाता है जिन्हें R8 को नाम के टकराव से बचने के लिए छोड़ना पड़ता है.
  • पूरे स्टैक ट्रेस को सिंबल में बदलना: Soong में कोड को छिपाने की सुविधा चालू करने से, डीबग करने की क्षमता कम नहीं होती. R8 से कंपाइल किए गए हर टारगेट के लिए, Soong मॉड्यूल की इंटरमीडिएट डायरेक्ट्री में proguard_dictionary मैपिंग फ़ाइल जनरेट करता है. साथ ही, सभी मॉड्यूल डिक्शनरी को बिल्ड के proguard-dict.zip आर्टफ़ैक्ट में बंडल करता है. इसके अलावा, DEX हेडर में मैपिंग हैश (--map-id-template) को एम्बेड करता है और क्लास SourceFile एट्रिब्यूट (--source-file-template) को फिर से लिखता है, ताकि retrace स्टैक ट्रेस को अपने-आप सिंबॉलिकेट कर सके.

obfuscate: false (या साफ़ तौर पर सार्वजनिक एपीआई के नाम सुरक्षित रखें) को सिर्फ़ इनके लिए रखें:

  • bootclasspath या system_server क्लासपाथ पर शेयर की गई लाइब्रेरी: बाहरी एपीआई को एक्सपोज़ करने वाले मॉड्यूल (java_library या java_sdk_library) रन टाइम के दौरान, अन्य मॉड्यूल से डाइनैमिक तौर पर लिंक की गई सतह (जैसे कि framework.jar, services.jar या <uses-library> टारगेट) को obfuscate: false रखना चाहिए या protect_api_surface: true, @KeepForApi या -keep नियमों का इस्तेमाल करके, अपने एपीआई की सतह को साफ़ तौर पर सुरक्षित रखना चाहिए, ताकि उनके स्टब के ख़िलाफ़ कंपाइल किए गए कॉलर, रन टाइम के दौरान क्लास और सदस्य के नामों को रिज़ॉल्व कर सकें. java_sdk_library मॉड्यूल पर, protect_api_surface: true को सेट करने से, Metalava ProGuard के नियम अपने-आप जनरेट हो जाते हैं और लागू करने वाली लाइब्रेरी में अटैच हो जाते हैं. ये नियम, चालू किए गए सभी एपीआई स्कोप (सार्वजनिक, सिस्टम, मॉड्यूल-लाइब्रेरी, सिस्टम-सर्वर, और टेस्ट) पर लागू होते हैं. जब अपडेट न किए जा सकने वाले bootclasspath टारगेट (framework-minus-apex, ext, और framework-location) पर सामान्य R8 ऑप्टिमाइज़ेशन चालू किया जाता है, तो उनमें framework-hiddenapi-proguard-rules (hiddenapi_to_proguard से जनरेट किया गया) भी शामिल होता है. इससे, चालू boot/hiddenapi/*.txt फ़ाइलों में मौजूद सदस्यों को बनाए रखा जा सकता है. हालांकि, इसमें hiddenapi-max-target-o.txt को शामिल नहीं किया जाता.

किसी ऐप्लिकेशन पर obfuscate: true को चालू करने से पहले, माइग्रेशन की इन ज़रूरी शर्तों की पुष्टि करें:

  • बाहरी टेस्ट APK डिपेंडेंसी: अगर कोई बाहरी android_test APK, instrumentation_for: "MyApp" सेट करता है और MyApp की इंटरनल क्लास या तरीकों को सीधे तौर पर शुरू करता है, तो उन सिंबल का नाम बदलने से, टेस्ट रन टाइम के दौरान NoSuchMethodError या NoClassDefFoundError की गड़बड़ी होती है. trace_references_from को कॉन्फ़िगर करें (या trace_references: true के साथ allow_traced_references), टेस्ट को सेल्फ़-इंस्ट्रुमेंटिंग APK के तौर पर स्ट्रक्चर करें, जो MyApp.impl को स्टैटिक तौर पर लिंक करता हो या टेस्ट हुक को @VisibleForTesting के साथ एनोटेट करें (दूसरा चरण: टेस्ट-ड्रिवन कीप रूल माइग्रेट करें देखें), ताकि टेस्ट के लिए ज़रूरी इंटरनल सिंबल सुरक्षित रहें और ऐप्लिकेशन के बाकी हिस्से को छिपाया जा सके.
  • स्ट्रिंग-आधारित रिफ़्लेक्शन और जेएनआई: अगर कोई मॉड्यूल, लिटरल स्ट्रिंग के नामों (Class.forName, getDeclaredMethod या जेएनआई FindClass और GetMethodID) के हिसाब से क्लास, मेथड या फ़ील्ड को खोजता है, तो कोड को छिपाने की प्रोसेस उन टारगेट का नाम बदल देती है और रनटाइम लुकअप को रोक देती है. ऐसा तब होता है, जब कीप रूल या एनोटेशन का इस्तेमाल न किया गया हो. (ध्यान दें कि बिना एनोटेशन वाले रिफ़्लेक्शन, shrink: true और optimize: true के तहत भी काम नहीं करते.) उन एंट्री पॉइंट को एनोटेशन बनाए रखने की गाइड (@UsesReflection, @UsedByReflection या @UsedByNative) के साथ एनोटेट करें, ताकि R8 उनके नाम सुरक्षित रखे और मॉड्यूल के बाकी हिस्से को छिपा दे.

'कोई कस्टम फ़्लैग नहीं' सिद्धांत का पालन करना

Android प्लैटफ़ॉर्म के सबसे सही मॉड्यूल में, कोई कस्टम proguard.flags या keep.xml फ़ाइल नहीं होती. Android प्लैटफ़ॉर्म के बिल्ड में, कस्टम कीप रूल का ज़्यादातर हिस्सा ग़ैर-ज़रूरी होता है:

  • प्लैटफ़ॉर्म बेसलाइन और AAPT2: ज़्यादातर एंट्री पॉइंट, ग्लोबल प्लैटफ़ॉर्म बेसलाइन (जैसे, @Keep, JNI native मेथड, और @VisibleForTesting) की मदद से अपने-आप सेव हो जाते हैं या AAPT2 की मदद से AndroidManifest.xml और लेआउट रिसॉर्स से जनरेट होते हैं. ज़्यादा जानकारी के लिए, डिफ़ॉल्ट प्लैटफ़ॉर्म के लिए, सेव रखने के नियमों को समझें लेख पढ़ें.
  • टारगेट किए गए विकल्प: जहां एंट्री पॉइंट को सुरक्षित रखना ज़रूरी है वहां अलग की गई .flags फ़ाइलों के बजाय, डिक्लेरेशन-साइट एनोटेशन (keepanno, @VisibleForTesting), एक्सपोर्ट किए गए लाइब्रेरी नियम (export_proguard_flags_files: true) या स्टैटिक टेस्ट लिंकिंग को प्राथमिकता दें. ज़्यादा जानकारी के लिए, मौजूदा कीप नियमों की जांच करना और उन्हें माइग्रेट करना लेख पढ़ें.

कस्टम proguard.flags या keep.xml फ़ाइल को जोड़ने या बनाए रखने से पहले, पुष्टि करें कि नियम को डिफ़ॉल्ट बेसलाइन से पहले ही मैनेज किया जा रहा है या उसे कोड एनोटेशन पर माइग्रेट किया जा सकता है.

प्लैटफ़ॉर्म पर डेटा सेव रखने के डिफ़ॉल्ट नियमों को समझना

Soong, DEX-कंपाइलिंग टारगेट (android_app, android_test, और इंस्टॉल किए जा सकने वाले java_library मॉड्यूल) पर हर R8 को कॉल करने के लिए, ये ग्लोबल बेसलाइन नियम अपने-आप पास करता है. इन्हें build/soong/java/dex.go में कॉन्फ़िगर किया जाता है:

  • build/make/core/proguard.flags: यह सभी पैकेज में @com.android.internal.annotations.VisibleForTesting के साथ एनोटेट की गई क्लास और सदस्यों को सुरक्षित रखता है. साथ ही, android.**, com.android.**, और com.google.android.** पैकेज में @VisibleForTesting (androidx.annotation.VisibleForTesting या com.google.common.annotations.VisibleForTesting) के साथ एनोटेट की गई क्लास और सदस्यों को सुरक्षित रखता है. यह @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback, और @dalvik.annotation.optimization.** को भी सुरक्षित रखता है.
  • build/make/core/proguard_basic_keeps.flags: यह स्टैक ट्रेस के लिए SourceFile एट्रिब्यूट, रनटाइम विज़िबिलिटी एनोटेशन (RuntimeVisible*Annotations), Exceptions और AnnotationDefault एट्रिब्यूट, native मेथड, Serializable मेंबर, @JavascriptInterface मेथड, Throwable(String) कंस्ट्रक्टर, Parcelable$CREATOR फ़ील्ड, और protobuf MessageLite फ़ील्ड को सुरक्षित रखता है.
  • build/make/core/proguard/kotlin.flags: यह Kotlin के खास मेटा-एनोटेशन (kotlin.Metadata और kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) के लिए, नुकसान न पहुंचाने वाली चेतावनियों को बंद कर देता है. साथ ही, रिलीज़ के लिए तैयार बिल्ड में Kotlin DebugMetadata एनोटेशन हटा देता है.
  • build/make/core/proguard/checknotnull.flags: यह सामान्य null चेक हेल्पर कॉल (com.google.common.base.Preconditions.checkNotNull और dagger.internal.Preconditions.checkNotNull*) को छोटे बाइट कोड null चेक से बदल देता है. इस फ़ाइल में जान-बूझकर Objects.requireNonNull को शामिल नहीं किया गया है, ताकि फ़्रेमवर्क एपीआई की सीमाओं के पार, साफ़ तौर पर अपवाद वाले मैसेज को सुरक्षित रखा जा सके.
  • build/make/core/proguard/enumvalues.flags: यह enum टाइप पर values और valueOf तरीकों को बनाए रखता है. हालांकि, ऐसा तब तक होता है, जब तक बिल्ड कॉन्फ़िगरेशन से इन्हें बंद नहीं किया जाता.
  • AAPT2 के अपने-आप जनरेट होने वाले नियम: AAPT2, मर्ज किए गए AndroidManifest.xml, लेआउट एक्सएमएल, और प्राथमिकता वाली एक्सएमएल फ़ाइलों की जांच करता है. इससे, रजिस्टर किए गए हर Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, कस्टम View सबक्लास, Preference सबक्लास, और एक्सएमएल android:onClick तरीके के लिए, सटीक नियम जनरेट किए जा सकते हैं.

डेटा को सेव रखने के मौजूदा नियमों की समीक्षा करना और उन्हें माइग्रेट करना

प्लैटफ़ॉर्म रिपॉज़िटरी में मौजूद proguard.flags या keep.xml फ़ाइलों का ऑडिट करते समय, हर नियम का आकलन इस चार चरणों वाले क्रम के हिसाब से करें:

पहला चरण: ग़ैर-ज़रूरी या पुराने नियम मिटाना

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

अगर .flags फ़ाइल में मौजूद सभी नियम ग़ैर-ज़रूरी हैं, तो फ़ाइल को मिटाएं और Android.bp से proguard_flags_files को हटाएं. अगर बाकी बचे optimize ब्लॉक में सिर्फ़ डिफ़ॉल्ट सेटिंग को फिर से बताया गया है, तो ग़ैर-ज़रूरी ब्लॉक को हटाएं और bpfmt -w Android.bp का इस्तेमाल करके बिल्ड फ़ाइल को फ़ॉर्मैट करें. किसी पैकेज को क्लीन अप करते समय, देखें कि सबडायरेक्ट्री में मौजूद कंपैनियन मॉड्यूल (जैसे, Kotlin टारगेट वैरिएंट), एक ही फ़्लैग फ़ाइल को रेफ़रंस करते हैं या नहीं. साथ ही, उन्हें एक साथ अपडेट करें.

दूसरा चरण: टेस्ट-ड्रिवन कीप रूल माइग्रेट करना

टेस्ट-ड्रिवन कीप रूल को कस्टम प्रोडक्शन .flags फ़ाइलों से बाहर ले जाने के लिए, इनमें से किसी एक पैटर्न का इस्तेमाल करें:

  • टेस्ट या कंपैनियन टारगेट (trace_references_from या allow_traced_references) से रेफ़रंस ट्रेस करना: जब टारगेट और सीधे तौर पर डिपेंडेंट मॉड्यूल, दोनों अपने DEX (android_app, android_test या dexed java_library) को कंपाइल करते हैं और instrumentation_for या libs के ज़रिए कनेक्ट होते हैं, तो Soong, डिपेंडेंट मॉड्यूल पर R8 tracereferences चला सकता है. साथ ही, जनरेट किए गए कीप रूल को टारगेट के R8 कंपाइलेशन में फ़ीड कर सकता है. इसके लिए, डिपेंडेंसी साइकल बनाने या मैन्युअल तरीके से .impl लाइब्रेरी को स्प्लिट करने की ज़रूरत नहीं होती:

    • एक ही रिपॉज़िटरी में मौजूद टेस्ट (trace_references_from): टारगेट मॉड्यूल पर सीधे तौर पर निर्भर instrumentation_for या libs डिपेंडेंसी की सूची बनाएं. यह override_android_app और override_android_test वैरिएंट में भी अपने-आप लागू हो जाती है. साथ ही, .impl स्प्लिट के साथ जोड़े जाने पर, यह उन कंपैनियन Java लाइब्रेरी के साथ काम करती है जो डिपेंडेंट नहीं हैं:

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              obfuscate: true,
              shrink_resources: true,
              trace_references_from: ["MyAppTests"],
          },
      }
      
      android_test {
          name: "MyAppTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
      }
      
    • क्रॉस-डेटा स्टोर या वेंडर टेस्ट (allow_traced_references और trace_references): जब टारगेट मॉड्यूल, trace_references_from में डिपेंडेंट को सीधे तौर पर लिस्ट नहीं कर पाता (जैसे, किसी दूसरे डेटा स्टोर या वेंडर ट्री में टेस्ट), तो टारगेट मॉड्यूल पर allow_traced_references: true सेट करें और हर डिपेंडेंट से trace_references: true के साथ शामिल हों:

      android_app {
          name: "MyApp",
          srcs: ["src/**/*.java"],
          optimize: {
              allow_traced_references: true,
          },
      }
      
      android_test {
          name: "MyAppVendorTests",
          instrumentation_for: "MyApp",
          srcs: ["tests/**/*.java"],
          optimize: {
              trace_references: true,
          },
      }
      
    • टेस्ट में स्ट्रिंग-आधारित रिफ़्लेक्शन: tracereferences, बाइट कोड के कॉन्स्टेंट-पूल रेफ़रंस की जांच करता है. साथ ही, टेस्ट में स्ट्रिंग-आधारित रिफ़्लेक्शन (Class.forName, getDeclaredField, getDeclaredMethod या रिफ़्लेक्टिव फ़ील्ड इंजेक्शन हेल्पर) का पता नहीं लगा सकता. रिफ़्लेक्टिव तरीके से ऐक्सेस किए गए प्रोडक्शन फ़ील्ड और तरीकों को @VisibleForTesting (या @UsedByReflection) के साथ एनोटेट करें.

  • टेस्ट (static_libs) में लागू करने वाली लाइब्रेरी को लिंक करें: अगर आपको प्रोडक्शन APK में सिर्फ़ टेस्ट के लिए इस्तेमाल होने वाले एंट्री पॉइंट नहीं चाहिए और आपको प्रोडक्शन टारगेट पर R8 को फिर से ट्रिगर करने के लिए टेस्ट में बदलाव नहीं करना है, तो ऐप्लिकेशन की सोर्स फ़ाइलों को android_library (MyApp.impl) में रखें. साथ ही, MyApp.impl को MyApp और MyAppTests, दोनों में स्टैटिक तौर पर लिंक करें:

    android_library {
        name: "MyApp.impl",
        srcs: ["src/**/*.java"],
        manifest: "AndroidManifest.xml",
    }
    
    android_app {
        name: "MyApp",
        static_libs: ["MyApp.impl"],
        optimize: {
            obfuscate: true,
            shrink_resources: true,
        },
    }
    
    android_test {
        name: "MyAppTests",
        srcs: ["tests/**/*.java"],
        static_libs: ["MyApp.impl"],
    }
    

    तीन मॉड्यूल वाले इस पैटर्न की मदद से, MyAppTests को सेल्फ़-इंस्ट्रुमेंटिंग टेस्ट के तौर पर चलाया जा सकता है. इसमें इंटरनल क्लास का पूरा ऐक्सेस होता है. साथ ही, MyApp को प्रोडक्शन एंट्री पॉइंट के हिसाब से छोटा और अस्पष्ट बनाया जा सकता है.

  • टेस्ट हुक को @VisibleForTesting के साथ एनोटेट करें: अगर कोई बाहरी टेस्ट APK, android_app टारगेट पर सिर्फ़ कुछ इंटरनल मेथड या कंस्ट्रक्टर को कॉल करता है, तो उन एलान को @VisibleForTesting के साथ एनोटेट किया जा सकता है. ग्लोबल build/make/core/proguard.flags बेसलाइन, android.**, com.android.**, और com.google.android.** पैकेज में मौजूद @VisibleForTesting आइटम को अपने-आप सेव रखती है. इसके लिए, कस्टम .flags फ़ाइलों की ज़रूरत नहीं होती. (इन नेमस्पेस से बाहर के वेंडर मॉड्यूल के लिए, trace_references_from, @UsedByReflection या एक्सपोर्ट की गई लाइब्रेरी के नियमों का इस्तेमाल करें.) trace_references_from या trace_references के बजाय @VisibleForTesting का इस्तेमाल करके मैन्युअल तरीके से एनोटेट करते समय, Mockito mock(...) या spy(...) को पास की गई क्लास को भी एनोटेट करें. सिर्फ़ स्टब किए गए तरीकों को एनोटेट न करें. ऐसा इसलिए, क्योंकि उपयोगकर्ता के डिवाइस पर मौजूद mockito-target (dexmaker-mockito) रनटाइम सबक्लास जनरेट करता है और R8, सबक्लास नहीं की गई क्लास final (MockitoException: Can't mock/spy class ... - final class) को मार्क कर सकता है.

  • ओवरलैप होने वाली स्टैटिक लाइब्रेरी (jarjar_rules) से बचें या उन्हें फिर से पैकेज करें: जब android_app और उसका instrumentation_for टेस्ट APK, दोनों एक ही लाइब्रेरी को स्टैटिक तौर पर लिंक करते हैं (सीधे तौर पर, जैसे कि guava या ट्रांज़िटिव तौर पर), तो tracereferences टेस्ट में तय की गई उन क्लास को देखता है और ऐप्लिकेशन के लिए कोई कीप रूल नहीं बनाता. साथ ही, रन टाइम के दौरान ऐप्लिकेशन का पैरंट क्लासलोडर, R8-shrunk क्लास के साथ टेस्ट की कॉपी को शैडो करता है. इससे IllegalAccessError या NoSuchMethodError होता है. अगर टेस्ट को बंडल की गई अपनी कॉपी की ज़रूरत नहीं है, तो टेस्ट के static_libs से डुप्लीकेट कॉपी को हटाएं या टकराव से बचने के लिए, इसे libs की मदद से लिंक करें. इसके अलावा, jarjar_rules का इस्तेमाल करके, ऐप्लिकेशन की ओवरलैप होने वाली स्टैटिक लाइब्रेरी के पैकेज को मॉड्यूल के हिसाब से प्रीफ़िक्स के तहत फिर से पैकेज करें.

तीसरा चरण: मालिकाना हक वाली लाइब्रेरी से नियम एक्सपोर्ट करना

अगर शेयर की गई किसी java_library या android_library को अपने इंटरनल रिफ़्लेक्शन या जेएनआई कॉलबैक के लिए, नियम बनाए रखने की ज़रूरत है, तो लाइब्रेरी टारगेट पर नियम तय करें और export_proguard_flags_files: true सेट करें:

java_library {
    name: "my-shared-library",
    srcs: ["src/**/*.java"],
    optimize: {
        proguard_flags_files: ["proguard.flags"],
        export_proguard_flags_files: true,
    },
}

सभी डाउनस्ट्रीम android_app और लाइब्रेरी टारगेट, जो स्टैटिक तौर पर my-shared-library को लिंक करते हैं वे इन नियमों को अपने-आप इनहेरिट कर लेते हैं. इसलिए, डाउनस्ट्रीम ऐप्लिकेशन को उन्हें डुप्लीकेट करने की ज़रूरत नहीं होती. जब अलग-अलग डायरेक्ट्री के कई मॉड्यूल को नियमों का ऐसा सेट शेयर करना हो जो किसी एक कोड लाइब्रेरी से जुड़ा न हो, तो .flags फ़ाइल को Android.bp में साफ़ तौर पर filegroup के ज़रिए पब्लिश करें. इससे मॉड्यूल, पैकेज की सीमाओं के पार :my-shared-flags को साफ़ तौर पर रेफ़र कर सकते हैं. डाउनस्ट्रीम ऐप्लिकेशन के ऑप्टिमाइज़ेशन को सीमित किए बिना, लाइब्रेरी के उपभोक्ता के लिए डेटा सुरक्षित रखने वाले कीप नियमों को बनाने के बारे में सामान्य दिशा-निर्देश पाने के लिए, लाइब्रेरी तैयार करने वालों के लिए ऑप्टिमाइज़ेशन लेख पढ़ें.

चौथा चरण: सोर्स कोड में एलान एनोटेट करना

जब किसी क्लास, तरीके, कंस्ट्रक्टर या फ़ील्ड को रिफ़्लेक्शन या जेएनआई के ज़रिए ऐक्सेस किया जाता है और AAPT2 या ग्लोबल बेसलाइन में शामिल नहीं किया जाता है, तो .flags फ़ाइलों में मौजूद अलग किए गए -keep नियमों को एनोटेशन बनाए रखने की गाइड से सोर्स एनोटेशन से बदलें (keepanno Javadoc रेफ़रंस देखें):

  • @UsesReflection: इस एनोटेशन का इस्तेमाल तब करें, जब आपके पास रिफ़्लेक्शन करने वाले कोड का कंट्रोल हो. इसे रिफ़्लेक्शन कॉल साइट पर रखें, ताकि यह एलान किया जा सके कि किन टारगेट क्लास, तरीकों या फ़ील्ड को डाइनैमिक तौर पर ऐक्सेस किया जाता है. @UsesReflection, एनोटेट की गई कॉल साइट तक पहुंचने की ज़रूरी शर्त को अपने-आप एन्कोड करता है. इसलिए, अगर कॉल साइट का इस्तेमाल नहीं किया जाता है, तो R8, कॉलर और रिफ़्लेक्टिव टारगेट, दोनों को हटा देता है.
  • @UsedByReflection: इसे उन क्लास, मेथड, फ़ील्ड या कंस्ट्रक्टर पर लागू करें जिन्हें बाहरी कोड या लाइब्रेरी, रिफ़्लेक्टिव तरीके से इंस्टैंशिएट या शुरू करती हैं. जैसे, Bundle या Settings कुंजियों से Class.forName के साथ लोड की गई क्लास या डाइनैमिक क्लासलोडर में लोड की गई प्लगिन क्लास. kind (जैसे, KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition), और पैरामीटर की पाबंदियां तय करें, ताकि R8 सिर्फ़ सटीक रिफ़्लेक्टिव कॉन्ट्रैक्ट को बनाए रखे. साथ ही, क्लास के इस्तेमाल न किए गए कॉम्पोनेंट को ऑप्टिमाइज़ या कम कर सके. @UsedByReflection को मेनिफ़ेस्ट में रजिस्टर किए गए कॉम्पोनेंट, जैसे कि JobService या BroadcastReceiver में न जोड़ें. ऐसा इसलिए, क्योंकि AAPT2 उन्हें पहले से ही सेव रखता है.
  • @UsedByNative: GetMethodID, GetStaticMethodID या GetFieldID का इस्तेमाल करके, C या C++ जेएनआई कोड से ऐक्सेस किए गए तरीकों या फ़ील्ड पर रखें.
  • @KeepForApi: इसे लाइब्रेरी एपीआई क्लास या सदस्यों पर रखें. इन्हें तब भी बरकरार रखना ज़रूरी है, जब डिस्ट्रिब्यूशन से पहले लाइब्रेरी का साइज़ छोटा किया जाता है.
  • @Keep (androidx.annotation.Keep): keepanno लागू न होने पर, इसे फ़ॉलबैक के तौर पर इस्तेमाल करें. किसी क्लास पर @Keep लागू करने से, क्लास और उसके सभी कोड कॉम्पोनेंट बिना किसी शर्त के सुरक्षित रहते हैं. किसी तरीके या फ़ील्ड पर @Keep लागू करने से, वह बिना किसी शर्त के एंट्री पॉइंट के तौर पर काम करता है. इससे मेंबर और उसमें मौजूद क्लास को बनाए रखा जाता है. भले ही, क्लास को कभी इंस्टैंशिएट न किया गया हो. वहीं, keepanno से पता चलता है कि शर्त के हिसाब से ऐक्सेस किया जा सकता है या नहीं.

Soong मॉड्यूल में R8 keepanno एनोटेशन इस्तेमाल करने के लिए:

  1. Android.bp में libs में "keepanno-annotations" जोड़ें:

    libs: [
        "keepanno-annotations",
    ],
    
  2. com.android.tools.r8.keepanno.annotations.* को इंपोर्ट करें और Java या Kotlin सोर्स कोड में, एलान या कॉल साइट को एनोटेट करें:

    import com.android.tools.r8.keepanno.annotations.KeepItemKind;
    import com.android.tools.r8.keepanno.annotations.UsedByReflection;
    
    public final class CustomPluginController {
        @UsedByReflection(
            description = "Instantiated via Class.forName from plugin config",
            kind = KeepItemKind.CLASS_AND_METHODS)
        public CustomPluginController(Context context) {
            // ...
        }
    }
    

    R8, keepanno एनोटेशन को सीधे अपने इंटरनल कीप रूल मॉडल में ट्रांसलेट करता है. साथ ही, फ़ाइनल DEX आउटपुट से एनोटेशन हटा देता है. इससे रनटाइम बाइट कोड ओवरहेड नहीं जुड़ता.

आम गलतियों से बचना

यहां दिए गए सेक्शन में, प्लैटफ़ॉर्म कोड में अक्सर होने वाली proguard.flags और keep.xml गड़बड़ियों के बारे में बताया गया है. साथ ही, उन्हें ठीक करने का तरीका भी बताया गया है.

ब्रॉड कॉम्पोनेंट के रखरखाव के नियम

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • इससे परफ़ॉर्मेंस पर क्यों असर पड़ता है: AAPT2 के साथ इस नियम की कोई ज़रूरत नहीं है. यह वाइल्डकार्ड का इस्तेमाल करता है. हालांकि, यह नहीं देखता कि कॉम्पोनेंट, मर्ज किए गए फ़ाइनल AndroidManifest.xml में रजिस्टर है या नहीं. इसलिए, यह R8 को क्लासपाथ पर मौजूद हर Activity, Service या BroadcastReceiver सबक्लास को बनाए रखने के लिए मजबूर करता है. इसमें लाइब्रेरी के ऐसे कॉम्पोनेंट भी शामिल हैं जिनका इस्तेमाल नहीं किया गया है और डीबग करने की ऐसी गतिविधियां भी शामिल हैं जिन्हें बंद कर दिया गया है.
  • सुझाया गया तरीका: नियम को मिटाएं. AAPT2, आपस में मिले हुए मेनिफ़ेस्ट की जांच करता है और रजिस्टर किए गए कॉम्पोनेंट के लिए, सटीक -keep नियम जनरेट करता है.

ब्रॉड एक्सएमएल क्लिक हैंडलर के लिए नियम बनाए रखना

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • इससे परफ़ॉर्मेंस पर क्यों असर पड़ता है: मॉड्यूल की हर क्लास में हर public void *(View) तरीके को बनाए रखने से, R8 किसी भी ऐसे तरीके को हटाने या इनलाइन करने से रोकता है जो एक View पैरामीटर को स्वीकार करता है.
  • सुझाया गया तरीका: नियम को मिटाएं. AAPT2, लेआउट एक्सएमएल फ़ाइलों को स्कैन करता है और android:onClick एट्रिब्यूट से रेफ़रंस किए गए तरीकों के लिए, टारगेट किए गए कीप नियम जनरेट करता है.

ब्रॉड व्यू गेटर और सेटर के लिए, कीप रूल

# Don't do this:
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    public *** get*();
}
  • इससे परफ़ॉर्मेंस पर क्यों असर पड़ता है: इस नियम की वजह से, R8 को ऐप्लिकेशन में मौजूद हर View सबक्लास और लिंक की गई सभी लाइब्रेरी (इनमें AndroidX और Material लाइब्रेरी शामिल हैं) में हर गेटर और सेटर को बनाए रखना पड़ता है. इससे, इस्तेमाल न किए जा रहे तरीके को हटाने और यूज़र इंटरफ़ेस (यूआई) कोड में इनलाइन करने की प्रोसेस ब्लॉक हो जाती है.
  • सुझाया गया तरीका: नियम को मिटाएं. AAPT2, लेआउट एक्सएमएल फ़ाइलों से इनफ़्लेट की गई कस्टम View क्लास के लिए, कंस्ट्रक्टर को पहले से ही सुरक्षित रखता है. अगर कोड, ObjectAnimator.ofFloat(view, "translationZ", ...) जैसे रिफ़्लेक्टिव स्ट्रिंग नेम का इस्तेमाल करके व्यू प्रॉपर्टी को ऐनिमेट करता है, तो स्ट्रिंग नेम को टाइप की गई प्रॉपर्टी रेफ़रंस (View.TRANSLATION_Z या कस्टम FloatProperty या IntProperty लागू करने का तरीका) से बदलें, ताकि रिफ़्लेक्शन से पूरी तरह बचा जा सके. अगर रिफ़्लेक्टिव प्रॉपर्टी ऐक्सेस से बचना मुमकिन नहीं है, तो खास गेटर या सेटर को @UsedByReflection के साथ एनोटेट करें.

ब्लैंकेट ऑप्टिमाइज़ेशन या कोड को छिपाने की सुविधा बंद करना

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • इससे परफ़ॉर्मेंस पर क्यों असर पड़ता है: .flags फ़ाइल में -dontoptimize या -dontshrink डालने से, मॉड्यूल की Android.bp सेटिंग चुपचाप बदल जाती हैं. साथ ही, पूरे टारगेट के लिए ऑप्टिमाइज़ेशन पास बंद हो जाते हैं. इससे भी खराब बात यह है कि अगर कोई लाइब्रेरी -dontoptimize वाली .flags फ़ाइल एक्सपोर्ट करती है, तो वह लाइब्रेरी से लिंक करने वाले हर डाउनस्ट्रीम android_app के लिए, R8 ऑप्टिमाइज़ेशन को बंद कर देती है.
  • सुझाया गया समाधान: .flags फ़ाइलों से -dontoptimize, -dontshrink, और -dontobfuscate को हटाएं. लीफ टारगेट पर optimize ब्लॉक (shrink, optimize, obfuscate) का इस्तेमाल करके, Android.bp में ऑप्टिमाइज़ेशन के तरीके को साफ़ तौर पर कंट्रोल करें.

कमिट किए गए नियमों में डाइग्नोस्टिक फ़्लैग

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • इससे बिल्ड पर बुरा असर क्यों पड़ता है: सैंडबॉक्स किए गए Soong बिल्ड के दौरान, डाइग्नोस्टिक फ़्लैग, बिल्ड लॉग को भर देते हैं या आउटपुट फ़ाइलों को लोकल पाथ में लिखने की कोशिश करते हैं.
  • सुझाया गया तरीका: कमिट की गई .flags फ़ाइलों से इन फ़्लैग को मिटाएं. Soong, R8 मैपिंग और इस्तेमाल के आउटपुट, जैसे कि proguard_dictionary और proguard_usage.zip को out/soong/.intermediates/ में मौजूद मॉड्यूल की इंटरमीडिएट डायरेक्ट्री में अपने-आप लिखता है.

टेस्ट कोड के लिए प्रोडक्शन कीप नियम

  • इससे परफ़ॉर्मेंस पर असर क्यों पड़ता है: सिर्फ़ टेस्ट ऐक्सेस के लिए कस्टम -keep नियम जोड़ने से, कोड को बिना अस्पष्टता के रखना पड़ता है और प्रोडक्शन बिल्ड में बनाए रखना पड़ता है. trace_references_from (या trace_references: true के साथ allow_traced_references) को कॉन्फ़िगर करते समय या @VisibleForTesting के साथ एनोटेट करने के तरीकों से, मैन्युअल -keep नियमों को बनाए रखने से बचा जा सकता है. साथ ही, सिर्फ़ उन चीज़ों को बनाए रखा जा सकता है जिन्हें टेस्ट रेफ़रंस के तौर पर इस्तेमाल करते हैं. हालांकि, ये सिंबल अब भी प्रोडक्शन बाइनरी में शिप किए जाते हैं.
  • सुझाया गया तरीका: -keep के लिए कस्टम टेस्ट के नियमों को हटाएं. इसके लिए, trace_references_from (या allow_traced_references और trace_references: true) को चालू करें. इसके अलावा, ऐप्लिकेशन को .impl लाइब्रेरी में स्ट्रक्चर करें. इस लाइब्रेरी को static_libs का इस्तेमाल करके, खुद से इंस्ट्रुमेंट करने वाले टेस्ट APK से लिंक करें. इससे, सिर्फ़ टेस्ट के लिए इस्तेमाल होने वाले एंट्री पॉइंट, प्रोडक्शन APK से पूरी तरह से बाहर रहेंगे. इसके बारे में दूसरा चरण: टेस्ट-ड्रिवन कीप नियमों को माइग्रेट करना में बताया गया है.

keep.xml में ब्लैंकेट रिसॉर्स वाइल्डकार्ड

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • इससे परफ़ॉर्मेंस पर असर क्यों पड़ता है: ब्लैंकेट वाइल्डकार्ड, मॉड्यूल और उसकी डिपेंडेंसी में मैचिंग टाइप के हर रिसॉर्स को बनाए रखते हैं. इससे, इस्तेमाल नहीं किए जाने वाले रिसॉर्स को हटाने की प्रोसेस (shrink_resources: true) पर असर पड़ता है. साथ ही, APK और मैप की गई resources.arsc टेबल का साइज़ बढ़ जाता है.
  • समस्या को ठीक करने का सुझाया गया तरीका:
    • keep.xml के बजाय स्टैटिक रिसॉर्स रेफ़रंस को प्राथमिकता दें: Resources.getIdentifier तरीके का इस्तेमाल करके, रिसॉर्स के सीमित सेट को डाइनैमिक तौर पर खोजने से बचें. जैसे, नंबर वाली एक्सपेरिमेंट स्ट्रिंग या थीम वाले ड्रॉएबल. इसके बजाय, कंपाइल के समय switch स्टेटमेंट या स्टैटिक R.id, R.string या R.drawable कॉन्सटैंट पर मैप का इस्तेमाल करें. स्टैटिक रेफ़रंस की मदद से, R8 और AAPT2 सटीक लाइव रिसॉर्स का पता लगा सकते हैं. साथ ही, रनटाइम स्ट्रिंग लुकअप ओवरहेड को खत्म कर सकते हैं. इसके अलावा, keep.xml की ज़रूरत को पूरी तरह से खत्म कर सकते हैं.
    • संसाधनों के नाम की सूची बनाएं: अगर डाइनैमिक लुकअप की ज़रूरत है, जैसे कि तीसरे पक्ष के लाइसेंस रीडर के लिए, तो tools:keep में संसाधन के सटीक आइडेंटिफ़ायर की सूची बनाएं (उदाहरण के लिए, tools:keep="@raw/third_party_licenses").
    • जिन keep.xml फ़ाइलों की ज़रूरत नहीं है उन्हें मिटाएं: अगर सूची में दिए गए रिसॉर्स को कोड (R.raw.foo) या एक्सएमएल (@raw/foo) में पहले से ही स्टैटिक तौर पर रेफ़र किया गया है, तो श्रिंकर उन्हें अपने-आप सेव रखता है. इसलिए, res/raw/keep.xml को मिटाया जा सकता है.

नियम में किए गए बदलावों की पुष्टि और जांच करना

जब भी proguard.flags या keep.xml में, कीप रूल को हटाया या कम किया जाए, तो पुष्टि करें कि मॉड्यूल सही तरीके से बिल्ड हो रहा है, यूनिट और इंस्ट्रुमेंटेशन टेस्ट पास कर रहा है, और सभी ज़रूरी एंट्री पॉइंट को बनाए रख रहा है.

मॉड्यूल बनाना और उनकी जांच करना

  1. मॉड्यूल को साफ़ तौर पर बनाएं, ताकि यह पुष्टि की जा सके कि R8 और AAPT2, रेफ़रंस न मिलने की चेतावनियों के बिना पूरे हो गए हैं:

    m <MODULE_NAME>
    
  2. atest का इस्तेमाल करके, मॉड्यूल के लिए यूनिट और इंस्ट्रुमेंटेशन टेस्ट चलाएं:

    atest <TEST_MODULE_NAME>
    
  3. सिस्टम ऐप्लिकेशन, खास सेवाओं या हार्डवेयर पर निर्भर मॉड्यूल के लिए, टारगेट किए गए फ़िज़िकल डिवाइसों या डिवाइस की टेस्ट लैब में इंस्ट्रुमेंटेशन और यूज़र इंटरफ़ेस (यूआई) टेस्ट चलाएं. इससे रनटाइम रिफ़्लेक्शन पाथ, आईपीसी बाइंडिंग, और रिसॉर्स इन्फ़्लेशन को टेस्ट किया जा सकता है.

DEX और रिसॉर्स के अंतर की जांच करना

नियमों में बदलाव करने से पहले और बाद में, कंपाइल किए गए APK या JAR की तुलना करें. इससे यह पुष्टि की जा सकेगी कि R8, ज़रूरी एंट्री पॉइंट को हटाए बिना, काम न करने वाले कोड और संसाधनों को हटा देता है:

  • आउटपुट APK या DEX फ़ाइलों में सेव की गई क्लास, तरीकों, और फ़ील्ड की जांच करने के लिए, apkanalyzer या dexdump का इस्तेमाल करें:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • keep.xml में बदलाव करते समय या shrink_resources को चालू करते समय, aapt2 dump resources का इस्तेमाल करके पुष्टि करें कि बिल्ड, इस्तेमाल न किए गए संसाधनों को हटाता है और ज़रूरी संसाधनों को बनाए रखता है:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • prebuilts/r8/r8.jar को सीधे किसी मॉड्यूल के प्री-R8 classes.jar पर चलाते समय, फ़्लैग में हुए बदलावों की पुष्टि ऑफ़लाइन की जा सकती है. इसके लिए, पूरे प्लैटफ़ॉर्म को बिल्ड करने की ज़रूरत नहीं होती. इसके लिए, मॉड्यूल के --lib क्लासपाथ JAR (android.jar, framework.jar, और शेयर किए गए libs) और AAPT2 के जनरेट किए गए proguard.options नियम, दोनों को AndroidManifest.xml और लेआउट एक्सएमएल से पास करें. इन इनपुट के बिना, R8 फ़्रेमवर्क के सुपरटाइप (Activity, Service, Phone, Handler) को रिज़ॉल्व नहीं कर सकता. साथ ही, हैरारकी के तरीकों को कम बनाए रखता है.

R8 की मदद से, कीप रेडियस और नियम के सबसप्शन का विश्लेषण करना

ओपन-सोर्स R8 कंपाइलर में, Keep Radius ऐनलाइज़र शामिल होता है. इसे Android Studio और Gradle में R8 कॉन्फ़िगरेशन ऐनलाइज़र के तौर पर भी दिखाया जाता है. यह कंपाइलेशन के दौरान, हर 'कीप रूल' के सटीक असर को मेज़र करता है. Soong, इस ऐनलाइज़र को सीधे Android प्लैटफ़ॉर्म बिल्ड में इंटिग्रेट करता है. इसे build/soong/java/dex.go में कॉन्फ़िगर किया गया है.

किसी प्लैटफ़ॉर्म मॉड्यूल या पूरे बिल्ड के लिए, कीप रूल का विश्लेषण करने के लिए:

  1. R8_DUMP_KEEP_RADIUS=true एनवायरमेंट वैरिएबल के साथ बिल्ड चलाएं:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    R8_DUMP_KEEP_RADIUS=true सेट होने पर, Soong, R8 को निर्देश देता है कि वह out/soong/.intermediates/ में मौजूद हर कंपाइल किए गए मॉड्यूल के लिए, इंटरमीडिएट r8keepradius.pb फ़ाइल में कीप रूल की मेट्रिक रिकॉर्ड करे. हर कीप रूल और keepanno एनोटेशन के लिए, R8 ये चीज़ें रिकॉर्ड करता है:

    • Immediate keep radius: नियम के तहत रखी गई सटीक क्लास, फ़ील्ड, और तरीके. साथ ही, इसमें उन पाबंदियों की जानकारी भी शामिल होती है जो नियम के तहत, कोड को छोटा करने, ऑप्टिमाइज़ करने या छिपाने के लिए लागू की जाती हैं.
    • नियमों का ओवरलैप होना: ऐसे कौनसे अन्य कीप रूल या एनोटेशन हैं जो पहले से ही उन आइटम को सेव रखते हैं. अगर कोई कस्टम नियम, AAPT2 नियम या ग्लोबल बेसलाइन नियम में पूरी तरह से शामिल है, तो उसे सुरक्षित तरीके से मिटाया जा सकता है.
    • पूरे पैकेज और ग्लोबल लेवल पर लागू होने वाले नियम: ऐसे नियम जो पूरे पैकेज के लिए वाइल्डकार्ड का इस्तेमाल करते हैं या ग्लोबल कॉन्फ़िगरेशन डायरेक्टिव लागू करते हैं.
  2. KeepRadiusHtmlReportGenerator (prebuilts/r8/r8.jar में बंडल किया गया) का इस्तेमाल करके, r8keepradius.pb आउटपुट को इंटरैक्टिव एचटीएमएल रिपोर्ट में बदलें:

    # Generate an HTML report for a single module
    INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME>
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        $INTERMEDIATES/android_common/r8keepradius.pb \
        keep_radius_report.html
    
    # Scan all built modules under out/soong/.intermediates and generate
    # per-module HTML reports plus an aggregate keepradius.html summary
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        out/soong/.intermediates \
        out/keep_radius_reports
    

    डायरेक्ट्री देने पर, KeepRadiusHtmlReportGenerator सभी *keepradius*.pb फ़ाइलों को स्कैन करता है और हर मॉड्यूल के लिए एक एचटीएमएल रिपोर्ट जनरेट करता है. साथ ही, यह out/keep_radius_reports/keepradius.html जनरेट करता है, जिसमें लाइव आइटम की संख्या, सेव किए गए आइटम की संख्या, और बिल्ड में सबसे ज़्यादा रेडियस वाले सेव करने के नियमों की जानकारी होती है. जनरेट की गई रिपोर्ट में, कोड को छोटा करने, ऑप्टिमाइज़ करने, और उसे छिपाने के स्कोर को समझने के बारे में ज़्यादा जानने के लिए, R8 कॉन्फ़िगरेशन ऐनलाइज़र का इस्तेमाल करना लेख पढ़ें.

Soong, build/soong/java/dex.go में R8 के अन्य डाइग्नोस्टिक्स और इनर-लूप एनवायरमेंट वैरिएबल के साथ भी काम करता है:

  • R8_DUMP_INPUT=true: यह मॉड्यूल की इंटरमीडिएट डायरेक्ट्री में r8inputs.zip लिखता है. इस डायरेक्ट्री में, सभी इनपुट JAR, लाइब्रेरी JAR, और मर्ज किए गए ProGuard कॉन्फ़िगरेशन शामिल होते हैं. इनका इस्तेमाल, स्टैंडअलोन R8 रीप्रोडक्शन के लिए किया जाता है.
  • R8_DUMP_PERFETTO_TRACE=true: Perfetto में R8 कंपाइलेशन पास की जांच करने के लिए, मॉड्यूल की इंटरमीडिएट डायरेक्ट्री में r8trace.ptrace लिखता है.
  • SOONG_SKIP_TRACE_REFERENCES=true: यह trace_references_from या trace_references से कॉन्फ़िगर किए गए tracereferences नियमों को चलाने से रोकता है. इससे टेस्ट कोड को दोहराते समय, लोकल इनर-लूप बिल्ड की स्पीड बढ़ जाती है.

लाइब्रेरी के उपभोक्ता नियमों की पुष्टि करना

जब कोई java_library या android_library, डाउनस्ट्रीम उपभोक्ताओं (export_proguard_flags_files: true) के लिए कीप नियमों को एक्सपोर्ट करता है, तो उन नियमों में ऐसे ग्लोबल फ़्लैग शामिल नहीं होने चाहिए जो उपभोक्ता ऐप्लिकेशन के लिए ऑप्टिमाइज़ेशन को बदलते या बंद करते हैं.

R8, ओपन-सोर्स कीप रूल ऐनलाइज़र (ProcessKeepRules) उपलब्ध कराता है. इसे Android प्लैटफ़ॉर्म बिल्ड में process-keep-rules होस्ट टूल के तौर पर दिखाया जाता है. इसे prebuilts/r8/Android.bp में तय किया गया है:

# Build the R8 keep rules validator host binary
m process-keep-rules

# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>

process-keep-rules टूल, हर कॉन्फ़िगरेशन फ़ाइल को पार्स करता है. अगर उसे ऐसे डायरेक्टिव मिलते हैं जिनकी अनुमति लाइब्रेरी के उपभोक्ता नियमों में नहीं है, तो वह फ़ाइल और लाइन डाइग्नोस्टिक्स के साथ काम करना बंद कर देता है. अनुमति न दिए गए डायरेक्टिव में ये शामिल हैं: ग्लोबल ऑप्टिमाइज़ेशन, श्रिंकिंग या कोड को छिपाने की सुविधा को बंद करना (जैसे, -dontoptimize या -dontshrink), पैकेज को फिर से पैकेज करना, और ऐक्सेस में बदलाव करने वाले फ़्लैग, गड़बड़ी की जानकारी देने वाले या मैपिंग वाले फ़्लैग, और ऐप्लिकेशन-लेवल -keepattributes.

pgaudit.py की मदद से सोर्स ट्री का ऑडिट करना

R8 के बिल्ड-टाइम ऐनलाइज़र के साथ-साथ, अनबिल्ट सोर्स डायरेक्ट्री का ऑडिट करने के लिए, Android प्लैटफ़ॉर्म ट्री में build/make/core/proguard/tools/ के तहत pgaudit.py स्क्रिप्ट शामिल होती है. यह स्टैटिक विश्लेषण टूल, रिपॉज़िटरी में मौजूद Android.bp, proguard.flags, और keep.xml फ़ाइलों को स्कैन करता है. इससे उन नियमों को फ़्लैग किया जाता है जो AAPT2 या ग्लोबल प्लैटफ़ॉर्म की बेसलाइन, ब्रॉड वाइल्डकार्ड, -dontoptimize ओवरराइड, और सिर्फ़ टेस्ट के लिए बनाए गए नियमों के तहत पहले से ही शामिल हैं:

# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/

# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json

प्लैटफ़ॉर्म और ओईएम टीमें, सोर्स-ट्री की तेज़ी से जांच करने के लिए pgaudit.py, एक्सपोर्ट किए गए लाइब्रेरी के नियमों की पुष्टि करने के लिए process-keep-rules, और बिल्ड में मौजूद हर नियम के लिए क्लास, फ़ील्ड, और तरीके के सटीक रिटेंशन रेडियस को मेज़र करने के लिए R8_DUMP_KEEP_RADIUS=true के साथ KeepRadiusHtmlReportGenerator को जोड़ सकती हैं.