AGP 9 में R8 Optimized Resource Shrinking: जब Static Analysis और Dynamic Access का सामना होता है
द्वारा Nikolay Vlasov
Android Gradle Plugin (AGP) 9.0 पर संक्रमण ने resource shrinking के काम करने के तरीके में एक महत्वपूर्ण बदलाव पेश किया है। जब आपके बिल्ड टाइप में isShrinkResources = true सक्षम होता है, तो AGP अब optimized resource shrinking का उपयोग करता है। यह तंत्र संसाधन विश्लेषण (resource analysis) को कोड विश्लेषण (code analysis) के साथ कसकर जोड़ता है, और उन सभी संसाधनों को हटा देता है जो केवल अप्रयुक्त कोड (unused code) द्वारा संदर्भित होते हैं। हालांकि इससे APK का आकार छोटा हो जाता है, लेकिन यह उन प्रोजेक्ट्स में समस्याएँ भी पैदा कर सकता है जो डायनेमिक रिसोर्स एक्सेस (dynamic resource access) पर निर्भर हैं—ऐसी समस्याएँ जो पिछले वर्जनों में किसी का ध्यान नहीं खींच पाती थीं। (Android Developers)
Dynamic Feature Modules में संसाधन क्यों गायब हो जाते हैं?
आधुनिक resource shrinkers काफी हद तक स्टैटिक विश्लेषण (static analysis) पर निर्भर करते हैं। यदि आपके कोड में किसी संसाधन को सीधे संदर्भित किया गया है (जैसे, R.raw.my_resource), तो shrinker आसानी से इसके उपयोग को ट्रैक कर सकता है। हालांकि, जब आप स्ट्रिंग नामों और रनटाइम लुकअप (runtime lookup) का उपयोग करके संसाधनों को डायनेमिक रूप से एक्सेस करते हैं, तो स्टैटिक विश्लेषण अब विश्वसनीय रूप से यह साबित नहीं कर सकता कि संसाधन की आवश्यकता है। AGP 9 के साथ, यह और भी स्पष्ट हो जाता है क्योंकि optimized resource shrinking अब एकीकृत कोड और संसाधन अनुकूलन पाइपलाइन (pipeline) का हिस्सा है। (Android Developers)
Dynamic Feature Modules (DFM) का उपयोग करने वाले प्रोजेक्ट्स में, बेस मॉड्यूल में अक्सर फीचर की R क्लास पर कंपाइल-टाइम डिपेंडेंसी (dependencies) की कमी होती है, खासकर जब मॉड्यूल Play Feature Delivery के माध्यम से अलग से वितरित किए जाते हैं। हालांकि नया resource shrinker कुछ समय से डायनेमिक फीचर्स का समर्थन कर रहा है, लेकिन यह विशिष्ट आर्किटेक्चर ही है जहां डायनेमिक लुकअप के कारण "संसाधन गायब होने" की समस्याएँ सबसे अधिक होती हैं। (Android Developers)
उदाहरण के लिए, Smart Directory Agent Center प्रोजेक्ट का एक हिस्सा लें। फुल-टेक्स्ट सर्च इंडेक्स को लोड करना इस प्रकार लागू किया गया है:
private fun getFtsId(): Int {
return dynamicContextWrapper.getContextForFts().resources.getIdentifier(
"fts",
"raw",
featuresManager.ftsModulePackage,
)
}
Resource shrinker के लिए, यह कॉल एक "ब्लैक बॉक्स" है: संसाधन का नाम स्टैटिक रेफरेंस के बजाय स्ट्रिंग के रूप में पारित किया जाता है। परिणामस्वरूप, बिल्ड टूल्स गलत तरीके से मान सकते हैं कि संसाधन अप्रयुक्त है और इसे रिलीज़ बिल्ड के दौरान हटा सकते हैं, जिससे रनटाइम पर Resources$NotFoundException हो सकती है। (Android Developers)
AGP में Resource Shrinking का विकास
यह ध्यान रखना महत्वपूर्ण है कि AGP 9.0 ने शून्य से resource shrinking का आविष्कार नहीं किया है; बल्कि, इसने ऑप्टिमाइज्ड पाइपलाइन के भीतर इसके व्यवहार को अधिक कठोर और पूर्वानुमानित बना दिया है। Precise resource shrinking AGP 8.3 में पहले से ही डिफ़ॉल्ट रूप से सक्षम था। AGP 9.0 में, optimized resource shrinking स्वचालित रूप से तब ट्रिगर होता है जब isShrinkResources = true होता है। 8.6 और 9.0 के बीच के वर्जनों के लिए, इस व्यवहार के लिए आमतौर पर स्पष्ट फ्लैग android.r8.optimizedResourceShrinking=true की आवश्यकता होती थी। (Android Developers)
AGP 8.9 में आए रिग्रेशन (regression) से सावधान रहें: रिलीज़ नोट्स में एक resource shrinking बग दर्ज किया गया था जिसके कारण डायनेमिक फीचर मॉड्यूल में संसाधन गायब हो रहे थे। इसे बाद में वर्जनों 8.9.2 और 8.10 में ठीक किया गया था। यदि आप अपने रिलीज़ बिल्ड में क्रैश का सामना करते हैं, तो यह आपकी आर्किटेक्चर की कमी नहीं हो सकती है—यह shrinker के एक विशिष्ट वर्जन में बग हो सकता है। (Android Developers)
वास्तविक समाधान: Keep फाइलें और स्पष्ट संसाधन प्रतिधारण
अप्रत्यक्ष रूप से उपयोग किए जाने वाले संसाधनों की रक्षा करने का सही तरीका आपके प्रोजेक्ट के संसाधनों में एक keep फाइल को परिभाषित करना है। दस्तावेज़ एक विशिष्ट तंत्र के रूप में "ग्लोबल keep.xml" पर भरोसा करने के बजाय res/raw/ (जैसे, res/raw/my.package.keep.xml) में एक XML फाइल बनाने की सलाह देते हैं। कीप फाइल के लिए एक अद्वितीय फ़ाइल नाम का उपयोग मल्टी-मॉड्यूल प्रोजेक्ट्स में रिसोर्स मर्जिंग के दौरान टकराव (conflicts) को रोकता है। (Android Developers)
हमारे मामले में, समाधान इस प्रकार था:
<!-- Path: app/src/main/res/raw/com.example.app.keep.xml -->
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@style/Theme.StartingScreen_*,@style/AppTheme_*,@raw/fts" />
यह दृष्टिकोण आपके इरादे को स्पष्ट बनाता है: @raw/fts संसाधन को हटाया नहीं जाना चाहिए, भले ही वह केवल डायनेमिक नाम रिज़ॉल्यूशन (resolution) के माध्यम से सुलभ हो। यह Resources.getIdentifier() के माध्यम से पढ़े गए किसी भी संसाधन के लिए महत्वपूर्ण है। (Android Developers)
AGP 9 और R8 के लिए सर्वोत्तम अभ्यास
पहला: डायनेमिक रूप से एक्सेस किए गए संसाधनों के लिए, उन्हें @raw/* जैसे व्यापक वाइल्डकार्ड (wildcards) पर भरोसा करने के बजाय tools:keep में स्पष्ट रूप से सूचीबद्ध करना सबसे अच्छा है। आपके नियम जितने विशिष्ट होंगे, अनावश्यक संसाधनों के रहने की संभावना उतनी ही कम होगी, और आपके बिल्ड का व्यवहार उतना ही बेहतर होगा। (Android Developers)
दूसरा: shrinkMode को ध्यान में रखें। डिफ़ॉल्ट रूप से, safe mode का उपयोग किया जाता है, जो उन संसाधनों को सुरक्षित रखने का प्रयास करता है जिनका उपयोग getIdentifier() के माध्यम से डायनेमिक रूप से किया जा सकता है। यदि आप tools:shrinkMode="strict" पर स्विच करते हैं, तो केवल स्पष्ट रूप से संदर्भित संसाधन ही रखे जाते हैं, जिससे आपके कीप नियमों की सटीकता अत्यंत महत्वपूर्ण हो जाती है। (Android Developers)
तीसरा: कारण को लक्षण (symptom) के साथ भ्रमित न करें। समस्या यह नहीं है कि आपका प्रोजेक्ट DFM का उपयोग करता है; समस्या यह है कि कुछ संसाधन केवल रनटाइम लुकअप के माध्यम से उपलब्ध हैं और स्टैटिक विश्लेषण के लिए अदृश्य रहते हैं। DFM केवल इस परिदृश्य को अधिक सामान्य और अनुकूलन के दौरान अधिक स्पष्ट बनाता है। (Android Developers)
निष्कर्ष
AGP 9 में गायब संसाधन किसी जादू या यादृच्छिक त्रुटि का परिणाम नहीं हैं। वे ऑप्टिमाइज्ड रिसोर्स श्रिंकिंग में सख्त स्टैटिक विश्लेषण का एक तार्किक परिणाम हैं। यदि आपकी आर्किटेक्चर डायनेमिक रिसोर्स एक्सेस पर निर्भर करती है, तो आपको कीप फाइलों का उपयोग करके उन संसाधनों की स्पष्ट रूप से रक्षा करनी चाहिए। यदि AGP 8.9-9.0 पर अपडेट करने के बाद विशेष रूप से क्रैश दिखाई देते हैं, तो अपने प्लगइन वर्जन की दोबारा जांच करें, क्योंकि इस क्षेत्र में ज्ञात रिग्रेशन को बाद के पैच में पहले ही संबोधित किया जा चुका है। (Android Developers)
उपयोगी संसाधन
- 1 Enable app optimization with R8 — R8 को कॉन्फ़िगर करने के लिए आधिकारिक गाइड।
- 2 Android Gradle Plugin 7.1.0 Release Notes — परिवर्तन लॉग और डायनेमिक फीचर्स सपोर्ट।
- 3 Tools attributes reference —
keepऔरshrinkModeएट्रिब्यूट्स का विवरण। - 4 Android Gradle Plugin 8.3.0 Release Notes — Precise Resource Shrinking पर विवरण।
- 5 Android Gradle Plugin 8.9.0 Release Notes — रिसोर्स श्रिंकिंग बग फिक्स के बारे में जानकारी।
- 6 Customize which resources to keep —
keep.xmlके साथ काम करने पर विस्तृत गाइड।