Android परफ़ॉर्मेंस इंजीनियर
Android ऐप्लिकेशन अक्सर ऑप्टिमाइज़ नहीं होते. इसकी वजह यह है कि तीसरे पक्ष की लाइब्रेरी से मिलने वाले नियम, ज़रूरत से ज़्यादा सख्त होते हैं. इससे APK का साइज़ बढ़ जाता है, कोल्ड स्टार्ट धीमा हो जाता है, और रनटाइम की परफ़ॉर्मेंस खराब हो जाती है.
Google Play ने DEX ऑप्टिमाइज़ेशन के लिए, परफ़ॉर्मेंस के नए थ्रेशोल्ड तय किए हैं. अब Google Play पर आपके ऐप्लिकेशन की विज़िबिलिटी इस बात पर निर्भर करती है कि आपका ऐप्लिकेशन, ऑप्टिमाइज़ेशन के मामले में अन्य ऐप्लिकेशन से कितना आगे है.
इस सेशन में, नियम के ब्लोट को कम करने के लिए, आधुनिक R8 वर्कफ़्लो के बारे में बताया गया है. आपको इन चीज़ों के बारे में जानकारी मिलेगी:
ट्रांसक्रिप्ट
नमस्ते!
मेरा नाम ब्रियाना है और मैं Android DevRel टीम से हूं.
मैं क्रिस क्रैक हूं और मैं Android की परफ़ॉर्मेंस टीम में हूं.
टाइप-सेफ़ कीप रूल और DEX ऑप्टिमाइज़ेशन में आपका स्वागत है.
आज हम बात करेंगे कि DEX ऑप्टिमाइज़ेशन क्या है, यह आपके लिए क्यों ज़रूरी है, और हाल ही में हुए कुछ दिलचस्प अपडेट के बारे में भी जानेंगे.
लंबे समय से, DEX ऑप्टिमाइज़ेशन को एक ऐसी सुविधा के तौर पर देखा जाता रहा है जो ज़रूरी नहीं है, लेकिन इसके होने से फ़ायदा मिलता है.
इसलिए, हो सकता है कि आप इसे एक बार कॉन्फ़िगर करके छोड़ दें या फिर जब आपके पास समय हो, तब इसे कॉन्फ़िगर करें.
हालांकि, मोबाइल इंडस्ट्री को हार्डवेयर की सप्लाई से जुड़ी कई समस्याओं का सामना करना पड़ रहा है. इससे डिवाइस की मेमोरी की उपलब्धता पर असर पड़ रहा है. इसलिए, यह ज़रूरी है कि आपके ऐप्लिकेशन के लिए DEX को अच्छी तरह से ऑप्टिमाइज़ किया गया हो. इससे ऐप्लिकेशन कम मेमोरी का इस्तेमाल करता है, तेज़ी से शुरू होता है, और एएनआर की समस्या को कम करता है. साथ ही, रेंडरिंग और रन-टाइम परफ़ॉर्मेंस को बेहतर बनाता है.
Android नेटवर्क को बेहतर बनाए रखने और यह पक्का करने के लिए कि आपके ऐप्लिकेशन लोगों को बेहतरीन अनुभव देते रहें, Google Play ने ऐप्लिकेशन की क्वालिटी से जुड़ी नई ज़रूरी शर्त का एलान किया है. इसमें परफ़ॉर्मेंस के थ्रेशोल्ड भी शामिल हैं. ये थ्रेशोल्ड, फ़रवरी से लागू होंगे.
इसमें DEX कोड ऑप्टिमाइज़ेशन के लिए थ्रेशोल्ड शामिल हैं.
ऐप्लिकेशन के कोड के साइज़ को छोटा करने, उसे ऑप्टिमाइज़ करने, और उसके नाम को अस्पष्ट बनाने के लिए, ऐप्लिकेशन को 25% से ज़्यादा स्कोर हासिल करना होगा.
Android डेवलपर के तौर पर, आपके लिए इसके क्या मायने हैं?
ऑप्टिमाइज़ न किए गए DEX कोड से, उपयोगकर्ता अनुभव पर बुरा असर पड़ता है. साथ ही, इससे Play Store पर आपके ऐप्लिकेशन के दिखने और उसे पब्लिश करने की सुविधाओं पर भी असर पड़ सकता है.
हालांकि, आप सही जगह पर हैं. इस बातचीत के आखिर तक, आपके पास ऐसे टूल होंगे जो इन ज़रूरी शर्तों को पूरा करने के साथ-साथ, इनसे बेहतर नतीजे पाने में भी आपकी मदद करेंगे.
DEX कोड ऑप्टिमाइज़ेशन, Android के ऐप्लिकेशन ऑप्टिमाइज़र R8 की मदद से किया जा सकता है.
अगर कोई दूसरा ऐप्लिकेशन ऑप्टिमाइज़र आपके ऐप्लिकेशन को इन थ्रेशोल्ड से ऊपर ले जा सकता है, तो R8 का इस्तेमाल करना ज़रूरी नहीं है. हालांकि, हमारा सुझाव है कि आप R8 का इस्तेमाल करें, क्योंकि यह सीधे Android के टूल चेन में पहले से मौजूद होता है.
अच्छी तरह से ऑप्टिमाइज़ किया गया DEX, ऐप्लिकेशन बंडल कम मेमोरी का इस्तेमाल करता है. हालांकि, इसके मुख्य फ़ायदे अब भी परफ़ॉर्मेंस से जुड़े हैं: एएनआर की समस्या कम होना, तेज़ी से शुरू होना, और रन-टाइम परफ़ॉर्मेंस बेहतर होना.
ऐप्लिकेशन को ऑप्टिमाइज़ करने से, आपको और आपके उपयोगकर्ताओं को असल में, बेहतर नतीजे मिलते हैं.
उदाहरण के लिए, Grab ने R8 के फ़ुल मोड को अपनाया और अपने कीप नियमों को बेहतर बनाया. इससे उसे अपने ऐप्लिकेशन के लिए 78% ऑप्टिमाइज़ेशन, 79% श्रिंकिंग, और 82% कोड को छिपाने की सुविधा मिली.
इस स्लाइड को देखकर आपको लग सकता है कि "ये आंकड़े तो बहुत ज़्यादा हैं, लेकिन इनका असल में क्या मतलब है?"
इन सुधारों और बेसलाइन प्रोफ़ाइलों की मदद से, उन्होंने ANR की संख्या में 32% की कमी की. साथ ही, ऐप्लिकेशन के स्टार्ट-अप में 35% का सुधार किया.
जब टॉप ऐप्लिकेशन पहली बार R8 का इस्तेमाल करते हैं, तो उन्हें DEX के साइज़ में औसतन 38% की कमी और यूज़र-पर्सीव्ड एएनआर में 16% की कमी देखने को मिलती है.
अगर पहले से ही R8 का इस्तेमाल किया जा रहा है, तो हमारे पास आपके लिए अच्छी खबर है.
जिन ऐप्लिकेशन में R8 पहले से चालू है और जो ऑप्टिमाइज़ेशन, कोड के साइज़ को छोटा करने, और उसके नाम को अस्पष्ट बनाने के लेवल को 50% से ज़्यादा बेहतर बनाने के लिए कदम उठाते हैं उनके DEX साइज़ में औसतन 25% की कमी आती है. साथ ही, ऐप्लिकेशन के शुरू होने में लगने वाले समय में 4% की कमी आती है. इसके अलावा, यूज़र-पर्सीव्ड एएनआर में 17% की कमी आती है. यह कमी, पिछली स्लाइड में दिखाए गए 16% के अलावा है.
इसलिए, आज हम आपको बताएंगे कि इस तरह के आंकड़े कैसे हासिल किए जा सकते हैं.
हम उन टूल के बारे में बताएंगे जो आपके DEX कोड को ऑप्टिमाइज़ करने में मदद कर सकते हैं. साथ ही, बहुत ज़्यादा ब्रॉड और गलत तरीके से कॉन्फ़िगर किए गए कीप रूल से, खास और सटीक रूल पर स्विच करने में मदद कर सकते हैं.
सबसे पहले, हम यह बताना चाहते हैं कि R8 कई सालों से उपलब्ध है.
हमें पता है कि इससे ऑप्टिमाइज़ेशन पर काफ़ी असर पड़ा है. हालांकि, इसे कॉन्फ़िगर करने में अब भी काफ़ी सावधानी बरतनी पड़ती है.
रिफ़्लेक्टिव कॉल का मतलब है कि इनपुट का अंदाज़ा नहीं लगाया जा सकता. इससे रन-टाइम क्रैश हो सकते हैं.
इस वजह से, ऐप्लिकेशन और लाइब्रेरी की टीमों ने पहले भी कई बार, ज़रूरत से ज़्यादा बड़े कीप रूल जोड़े हैं, ताकि वे सुरक्षित रह सकें.
हमें पता है कि सबसे लोकप्रिय ऐप्लिकेशन में से 41% ऐप्लिकेशन, Google Play के तय किए गए 25% ऑप्टिमाइज़ेशन लेवल से कम हैं. हालांकि, हमें यह भी पता है कि सबसे लोकप्रिय ऐप्लिकेशन में से 13% ऐप्लिकेशन, पहले से ही उस थ्रेशोल्ड से तीन गुना ज़्यादा ऑप्टिमाइज़ेशन लेवल हासिल कर रहे हैं.
इस संख्या को हासिल करने वाले ऐप्लिकेशन, अलग-अलग इंडस्ट्री और उपयोगकर्ता अनुभव से जुड़े हैं.
अगर आपके पास सही टूल हैं और आपको अपने बनाए गए और लाइब्रेरी से इनहेरिट किए गए, दोनों तरह के कीप रूल मैनेज करने की सही जानकारी है, तो आपके ऐप्लिकेशन के लिए ऑप्टिमाइज़ेशन स्कोर को बेहतर बनाया जा सकता है.
इससे पहले कि हम नए टूल और सुधारों के बारे में जानें, हम पिछले साल R8 में किए गए कुछ अन्य सुधारों के बारे में भी बताना चाहते हैं. इनसे भी ऑप्टिमाइज़ेशन थोड़ा आसान हो जाता है.
सबसे पहले, नए ऐप्लिकेशन टेंप्लेट में पैकेज स्कोप शामिल होता है. इससे यह पक्का करने में मदद मिलती है कि शुरुआत से ही ऑप्टिमाइज़ेशन का कम से कम लेवल मौजूद हो.
R8 कॉन्फ़िगरेशन ऐनालाइज़र, अब एजीपी के पुराने वर्शन पर काम करता है. हम इसके बारे में कुछ ही देर में बात करेंगे.
साथ ही, Bazel के लिए भी इसे अनौपचारिक तौर पर इस्तेमाल किया जा सकता है.
AGP 9.2 में, कोरूटीन प्रिमिटिव की स्पीड दोगुनी हो गई है.
एजीपी 9.2 में R8 से जुड़े सुधारों की वजह से, Tinder को 9.0.1 वर्शन से अपग्रेड करने के बाद, ANR में 13% की कमी देखने को मिली.
AGP 9.3 में, फिर से डिज़ाइन किया गया R8 एपीआई भी शामिल है. इसमें ज़्यादा साफ़ नाम, डिफ़ॉल्ट नियमों और इस्तेमाल नहीं किए जाने वाले रिसॉर्स को हटाने की प्रोसेस के लिए आसान डिफ़ॉल्ट सेटिंग, और कीप रूल के लिए AGP सोर्स डायरेक्ट्री की सुविधा शामिल है.
android {
buildTypes {
release {
optimization {
// no need to specify default
// rules and resource shrinking
enabled = true
}
}
}
...
}android {
buildTypes {
release {
// Enables code optimizations.
isMinifyEnabled = true
// Enables resource shrinking.
isShrinkResources = true
proguardFiles(
// Default rules.
getDefaultProguardFile(
"proguard-android-optimize.txt"
),
... // list out each file with keep rules
)
...
}
}
...
}आज हम जिन अपडेट के बारे में बात करेंगे वे डेवलपर के अनुभव को बेहतर बनाने के लिए भी हैं.
सबसे पहले, हम R8 Configuration Analyzer और R8 की स्किल का इस्तेमाल करने का तरीका बताएंगे. इससे, लेगसी रूल ओवरहेड की पहचान करने और उसे बेहतर बनाने का तरीका जानने में मदद मिलेगी.
इसके बाद, क्रिस बताएंगे कि ब्रॉड, वाइल्डकार्ड कीप नियमों से, सटीक, टाइप-सेफ़, और अपने-आप जनरेट होने वाले नए एनोटेशन पर कैसे स्विच किया जाए.
आखिर में, हम यह देखेंगे कि सीएलआई की पुष्टि करने वाले नए टूल का इस्तेमाल कैसे किया जाए, ताकि क्रैश की समस्या के बिना, ऐप्लिकेशन को बेहतर तरीके से ऑप्टिमाइज़ किया जा सके.
इसलिए, DEX ऑप्टिमाइज़ेशन की जांच शुरू करने के लिए, R8 Config Analyzer सबसे सही जगह है.
अगर आपने इस टूल का इस्तेमाल नहीं किया है, तो यहां देखें कि यह कैसा दिखता है.
यह एक एचटीएमएल रिपोर्ट है, जो आपके R8 कॉन्फ़िगरेशन की जानकारी दिखाती है.
यह रिपोर्ट मेरे ऐप्लिकेशन के लिए है. इसमें Analytics Bloat नाम की लाइब्रेरी शामिल है.
जैसा कि आपको पता है, यह कोई असली ऐप्लिकेशन या असली लाइब्रेरी नहीं है. हालांकि, इसे सबसे ऊपर नीले रंग में हाइलाइट किया गया है.
कॉन्फ़िगरेशन ऐनालाइज़र इन चीज़ों को मेज़र करता है: श्रिंकिंग, जो क्लास, मेथड, और फ़ील्ड का वह प्रतिशत है जिसे R8, 'कीप रूल' की मदद से श्रिंक होने से नहीं रोकता.
इसकी गिनती, R8 के पहले श्रिंकिंग पास के बाद की जाती है.
यह ऑप्टिमाइज़ेशन को भी मेज़र करता है. यह कोड का वह प्रतिशत होता है जो पूरे प्रोग्राम के ऑप्टिमाइज़ेशन के लिए उपलब्ध होता है. जैसे, मेथड इनलाइनिंग और क्लास मर्जिंग.
ऑब्फ़स्केशन, उन सदस्यों का प्रतिशत होता है जिन्हें छिपाया जा सकता है या छोटे आइडेंटिफ़ायर में बदला जा सकता है.
हम यह भी बताना चाहते हैं कि R8 Config Analyzer, इन स्कोर का हिसाब फ़ाइनल ऑप्टिमाइज़ेशन और DEX कंपाइलेशन से पहले लगाता है. इसलिए, ऑप्टिमाइज़ेशन, श्रिंकिंग, और कोड को छिपाने के लिए इस रिपोर्ट में दिखने वाली वैल्यू, Play Console में दिखने वाली वैल्यू से पूरी तरह मैच नहीं होंगी.
ऐसा इसलिए है, क्योंकि Google Play, फ़ाइनल DEX आउटपुट को मेज़र करता है.
इस अंतर की वजह यह है कि ऐप्लिकेशन डेवलप करते समय, आपको तेज़ और लोकल फ़ीडबैक लूप का ऐक्सेस मिल सके.
हालांकि, R8 Config Analyzer में दिखने वाले स्कोर, Play Store पर अपलोड करने से पहले DEX ऑप्टिमाइज़ेशन की क्षमता की पुष्टि करने में अब भी काफ़ी मददगार हैं.
रिपोर्ट में, 'नियम बनाए रखें' विश्लेषण टेबल भी शामिल होती है. यह वह मुख्य व्यू है जो आपको रिपोर्ट में जाने पर दिखता है.
इसके बाद, किसी भी नियम पर क्लिक करके, यह देखा जा सकता है कि किसी खास नियम में कितनी क्लास, तरीके, और फ़ील्ड शामिल हैं. इससे आपको यह पता चल सकता है कि किन नियमों में ज़रूरत से ज़्यादा क्लास, तरीके, और फ़ील्ड शामिल हैं.
इसमें कुछ नियम भी शामिल हैं. यहां उन्हें ज़ूम इन करके दिखाया गया है.
ये ऐसे नियम हैं जो किसी बड़े नियम को ओवरलैप करते हैं.
इन्हें हाइलाइट किया जाता है, ताकि आप ब्रॉड मैच वाले कीवर्ड को हटाकर, सिर्फ़ एग्ज़ैक्ट मैच वाले कीवर्ड को रख सकें.
यह आपको ऐसे नियम भी दिखाता है जिनका इस्तेमाल नहीं किया गया है. ये ऐसे नियम होते हैं जिन्हें मिटाने से आपके ऐप्लिकेशन की परफ़ॉर्मेंस पर कोई असर नहीं पड़ता.
इसलिए, कॉन्फ़िगरेशन ऐनलाइज़र को कुछ समस्याओं को हल करने के लिए डिज़ाइन किया गया था. इनमें से एक समस्या, लेगसी नियमों का इकट्ठा होना है.
पहले, हर नियम के लिए विज़िबिलिटी की सुविधा नहीं थी. इस वजह से, पुराने नियमों को मिटाना थोड़ा जोखिम भरा था.
इसलिए, टीमें उन्हें अकेला छोड़ देती थीं या सिर्फ़ ब्रॉड वाइल्डकार्ड जोड़ती थीं.
दूसरा, इससे आपको लाइब्रेरी के नियमों से जुड़ी ऐसी कमियों का पता चलता है जिन पर आपका ध्यान नहीं गया.
इसलिए, जब कोई एसडीके जोड़ा जाता है, तो यह बताना मुश्किल होता है कि उस लाइब्रेरी के साथ बंडल किया गया कोई नियम बहुत ज़्यादा ब्रॉड है या नहीं. इसके अलावा, यह भी बताना मुश्किल होता है कि 10 या उससे ज़्यादा वर्शन के बाद भी उसकी ज़रूरत है या नहीं.
हम लाइब्रेरी के बारे में भी थोड़ी देर में बात करेंगे.
तीसरा, इससे आपको ऑप्टिमाइज़ेशन के असर का आकलन करने में मदद मिलती है.
पहले, यह पता लगाने का कोई आसान तरीका नहीं था कि कोई नियम दो क्लास को सुरक्षित रख रहा है या 2,000 क्लास को.
आइए, देखते हैं कि मेरे ऐप्लिकेशन और उस लाइब्रेरी के संदर्भ में R8 कैसे काम करता है जिसे हमने पहले देखा था.
यहां मेरे पास एक ऐप्लिकेशन मॉड्यूल और तीन लाइब्रेरी हैं.
साथ ही, दाईं ओर मौजूद तीसरी लाइब्रेरी पर खास ध्यान दें.
यह Analytics Bloat लाइब्रेरी है, जिसमें पैकेज-वाइड कीप रूल शामिल है.
सबसे पहले, R8 मर्ज किए गए सभी कीप रूल से रूट एंट्री पॉइंट की पहचान करता है. इन्हें यहां पीले रंग से हाइलाइट किया गया है.
आम तौर पर, ये ऐप्लिकेशन के मुख्य एंट्री पॉइंट होने चाहिए, जैसे कि मेनिफ़ेस्ट में मौजूद क्लास.
Analytics Bloat लाइब्रेरी के वाइल्डकार्ड कीप रूल की वजह से, लाइब्रेरी की हर क्लास को एंट्री पॉइंट माना जाता है.
इसके बाद, R8 उन रूट से कॉल ग्राफ़ में, ऐक्सेस किए जा सकने वाले कोड को ट्रेस करता है.
रूट से ऐक्सेस की जा सकने वाली किसी भी क्लास को हरे रंग में मार्क किया जाता है, ताकि उसे बनाए रखा जा सके. वहीं, जिन क्लास का रेफ़रंस नहीं दिया गया है उन्हें डेड कोड के तौर पर कैटगरी में रखा जाता है और यहां लाल रंग में मार्क किया जाता है.
इसके बाद, R8 ट्री शेकिंग की प्रोसेस को पूरा करता है और ऐसे कोड को हटा देता है जिसका इस्तेमाल नहीं किया जा रहा है.
इसलिए, ट्री शेकिंग या पहली बार छोटा करने के बाद, यह बचता है.
हमारे ऐप्लिकेशन का श्रिंकिंग स्कोर 42.5% है. यह Google Play के तय किए गए 25% थ्रेशोल्ड से ज़्यादा है. हालांकि, यह अब भी उस लेवल पर नहीं है जहां हम इसे देखना चाहते हैं.
ऐसा मुख्य रूप से, Analytics लाइब्रेरी के पैकेज-वाइड कीप रूल की वजह से होता है.
इस वजह से, बाकी बची 40 क्लास में से सिर्फ़ 17 क्लास को और छोटा किया जा सकता है.
हमारा मकसद आपको यह बताना है कि डेटा सेव रखने का एक गलत नियम, आपके ऐप्लिकेशन के ऑप्टिमाइज़ेशन में बड़ी समस्याएं पैदा कर सकता है.
इसलिए, चलिए लाइब्रेरी पर फ़ोकस करते हैं.
पिछले साल, हमारी टीम ने यह समझने में काफ़ी समय लगाया कि लाइब्रेरी के उपभोक्ता के लिए डेटा सेव रखने के नियम, नेटवर्क के ऑप्टिमाइज़ेशन पर किस तरह असर डाल रहे हैं.
कई लाइब्रेरी, स्थिरता के नाम पर बहुत ज़्यादा नियमों के साथ शिप की जाएंगी.
इसके अलावा, वे अपने दस्तावेज़ में सामान्य नियम भी तय करेंगे, ताकि ऐप्लिकेशन डेवलपर उन्हें अपने ऐप्लिकेशन में खुद जोड़ सकें.
हालांकि, स्थिरता के लिए सामान्य नियमों के साथ शिपिंग करना ज़रूरी नहीं है. इससे परफ़ॉर्मेंस पर बुरा असर पड़ता है. इसमें, ऐप्लिकेशन डेवलपर के तौर पर आपको ही मुख्य रूप से लागत उठानी पड़ती है.
हमने ऐसे मामले देखे हैं जहां पूरी लाइब्रेरी को पैकेज-वाइड कीप नियम के ज़रिए सुरक्षित रखा जाता है. जैसे, आंकड़ों से जुड़ी ब्लोट लाइब्रेरी. इस लाइब्रेरी के मामले में, मेरे ऐप्लिकेशन को ऑप्टिमाइज़ करने की क्षमता पर काफ़ी असर पड़ा.
मैं यह भी बताना चाहता हूं कि Google के अपने एसडीके भी इससे अछूते नहीं थे. पिछले साल, Google की एक दर्जन से ज़्यादा लाइब्रेरी ने डेटा को सुरक्षित रखने से जुड़े नियमों को ठीक किया है.
अगर आपको कॉन्फ़िगरेशन ऐनलाइज़र का इस्तेमाल करना है, तो यह AGP 9.3 में पहले से मौजूद है. हालांकि, अगर R8 वर्शन को अपग्रेड किया जाता है, तो यह पुराने वर्शन के लिए भी उपलब्ध है.
इसलिए, इस निर्देश को चलाने से वह एचटीएमएल रिपोर्ट जनरेट होगी जो हमने दिखाई थी. इसमें आपके कीप रूल, श्रिंकिंग पर पड़ने वाले असर, ऑप्टिमाइज़ेशन, अलग-अलग नियमों के लिए कोड को छिपाने की प्रोसेस, और आपके ऐप्लिकेशन की पूरी जानकारी दिखेगी.
अगर R8 रिलीज़ के लिए तैयार बिल्ड का पूरा वर्शन इस्तेमाल किया जा रहा है, तो रिपोर्ट अपने-आप जनरेट हो जाती है. इसे इस फ़ाइल पाथ पर देखा जा सकता है.
इस स्टैंडअलोन टास्क का फ़ायदा यह है कि इसे जनरेट करने में, पूरी बिल्ड को जनरेट करने की तुलना में कम समय लगता है.
WHOOP की टीम ने कॉन्फ़िगरेशन ऐनलाइज़र चलाया. इसमें उन्हें 74% श्रिंकिंग, 74% ऑब्फ़स्केशन, और 0% ऑप्टिमाइज़ेशन मिला. WHOOP की टीम शायद यहां कहीं मौजूद हो.
ज़ाहिर है कि ऑप्टिमाइज़ेशन की इस वैल्यू को देखकर, टीम को बहुत हैरानी हुई.
कॉन्फ़िगरेशन ऐनलाइज़र से मिली जानकारी से उन्हें पता चला कि उनका बिल्ड, ProGuard की एक लेगसी फ़ाइल का इस्तेमाल कर रहा था. इस वजह से, ऑप्टिमाइज़ेशन की सुविधा बंद थी. इसलिए, अपडेट किए गए वर्शन पर स्विच करके, वे अपने ऐप्लिकेशन के लिए ऑप्टिमाइज़ेशन की सुविधा चालू कर पाए.
इससे, APK का साइज़ 4% कम हो गया और 55% ऑप्टिमाइज़ेशन हुआ.
बेसलाइन प्रोफ़ाइलों के साथ-साथ, उन्होंने कोल्ड स्टार्ट के समय को 38% तक कम किया. साथ ही, फ़्रीज़ हुए फ़्रेम की संख्या को 28% तक कम किया.
बाईं ओर मौजूद स्क्रीन पर, बेसलाइन प्रोफ़ाइलें नहीं हैं और दाईं ओर मौजूद स्क्रीन पर हैं. जैसा कि आप देख सकते हैं, बेसलाइन प्रोफ़ाइलें जोड़ने से ऐप्लिकेशन तेज़ी से चालू होता है.
चलिए, इसे एक बार फिर से चलाते हैं.
चलिए.
इस लिंक पर क्लिक करने से, आपको कॉन्फ़िगरेशन ऐनलाइज़र की गाइड पर रीडायरेक्ट कर दिया जाएगा. रिपोर्ट की मैन्युअल तरीके से समीक्षा करना एक अच्छा तरीका है. हालांकि, आपके ऐप्लिकेशन के R8 कॉन्फ़िगरेशन की स्थिति के आधार पर, इसमें काफ़ी समय लग सकता है.
इसलिए, इस प्रोसेस को तेज़ करने के लिए, हमारे पास R8 Analyzer स्किल है.
$ android skills list
$ android skills add r8-analyzerएजेंट से अपने R8 कॉन्फ़िगरेशन का विश्लेषण करने के लिए कहा जा सकता है. यह स्किल, R8 कॉन्फ़िगरेशन ऐनालाइज़र की रिपोर्ट जनरेट करने के साथ-साथ, सबसे असरदार नियमों के बारे में भी बताती है.
यहां उदाहरण के तौर पर एक आउटपुट दिया गया है. इसमें कुछ जानकारी दी गई है. इसमें 42% ऑप्टिमाइज़ेशन स्कोर बताया गया है. इसका मतलब है कि 52%-- 58% कोड बेस को R8 से ऑप्टिमाइज़ नहीं किया जा सकता.
अगर हम खास तौर पर कीप रूल सेक्शन को देखें, तो Analytics ब्लोट लाइब्रेरी के लिए पैकेज-वाइड कीप रूल, कोड बेस के 22% हिस्से को ऑप्टिमाइज़ होने से रोकता है. स्किल यह भी सुझाव देती है कि हम इस कीप रूल को बेहतर बनाएं, क्योंकि जैसा कि हम पहले से जानते हैं, यह बहुत बड़ा है.
अब R8 कॉन्फ़िगरेशन ऐनालाइज़र और स्किल का इस्तेमाल करके, यह विश्लेषण किया जा सकता है कि ऐप्लिकेशन का साइज़ किस वजह से बढ़ रहा है और DEX ऑप्टिमाइज़ेशन क्यों ब्लॉक हो रहा है.
हालांकि, अगर आपको ऐसा नियम मिलता है जो ज़रूरत से ज़्यादा क्लास को बनाए रखता है, तो उसे सुरक्षित तरीके से कैसे बदला जाए, ताकि अचानक कोई क्रैश न हो?
अब मैं क्रिस को बुलाना चाहूंगा, जो आपको बताएंगे कि लेगसी कीप रूल से, टाइप सेफ़्टी वाले अपने-आप जनरेट होने वाले नए कीप एनोटेशन पर कैसे स्विच करें.
धन्यवाद, ब्रियाना.
ठीक है, तो चलिए बात करते हैं कि हम यहां तक कैसे पहुंचे, है न?
इस बड़ी Analytics लाइब्रेरी में, इन सभी कीप रूल के साथ-- इस कीप रूल के साथ, जो मूल रूप से सब कुछ सुरक्षित रखता है, मुख्य समस्या यह है कि हम रिफ़्लेक्शन का इस्तेमाल कर रहे हैं.
इसलिए, आइए यहां थोड़ा सा रिफ़्लेक्टिव कोड देखें.
fun createWithReflection(className: String): MyObject {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as MyObject
}यह एक ऑब्जेक्ट इंस्टैंशिएशन फ़ंक्शन है, जो किसी खास ऑब्जेक्ट को इनफ़्लेट करता है और ऐसा रिफ़्लेक्टिव तरीके से करता है.
डीईएक्स ऑप्टिमाइज़ेशन के मामले में, इसका नुकसान यह है कि यह इन सभी स्टैटिक कॉल को रनटाइम तक के लिए रोक देता है.
सिर्फ़ कोड को देखकर, हमारा R8 ऑप्टिमाइज़र यह नहीं देख पाता कि यहां क्या हो रहा है. यह इस बात पर निर्भर करता है कि क्लास का नाम कहां से आ रहा है.
इन स्थितियों में, Dex ऑप्टिमाइज़ेशन डिफ़ॉल्ट रूप से काम नहीं करता. इसलिए, आम तौर पर आपको Keep नियम जोड़ना होगा.
अब, आम तौर पर आप इससे बचते हैं, है न?
जैसे, आम तौर पर हम रिफ़्लेक्शन का इस्तेमाल करने से बचते हैं. खास तौर पर, अब 2026 में हमारे पास सीरियलाइज़ेशन और डिपेंडेंसी इंजेक्शन के लिए, कोड जनरेट करने वाले बेहतरीन टूल हैं. इनसे इस तरह की समस्या को पूरी तरह से खत्म किया जा सकता है.
लेकिन यह सब कुछ नहीं है, है न?
कुछ मामलों में, हमें अब भी रिफ़्लेक्शन की ज़रूरत होती है.
तो चलिए, जानते हैं कि इस लाइब्रेरी में यह कैसे हुआ.
यहां एक उदाहरण दिया गया है. यह एक सामान्य उदाहरण है, जो वैकल्पिक डिपेंडेंसी है.
// Problem 1 - optional dependency
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}
// Problem 2 - dynamically instantiate subclass
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}
// Problem 3 - serialization
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}यहां हम एक खास टाइप को इनफ़्लेट कर रहे हैं, जिसे लाइब्रेरी क्लासपाथ पर ऐक्सेस नहीं किया जा सकता. इसलिए, हमें इसे स्ट्रिंग के साथ करना होगा.
हम इस खास क्लास का रेफ़रंस दे रहे हैं, जो लाइब्रेरी बनाते समय मौजूद हो भी सकती है और नहीं भी. इसलिए, यह लाइब्रेरी किसी मिलती-जुलती लाइब्रेरी में मौजूद सदस्य को ढूंढने के लिए, रिफ़्लेक्शन का इस्तेमाल कर रही है.
हमारे पास एक और केस है, जिसमें सबक्लास को डाइनैमिक तरीके से इंस्टैंशिएट किया जाता है.
इसलिए, खास तौर पर अगर कोई चीज़ मेनिफ़ेस्ट फ़ाइल से आ रही है या सर्वर-साइड से भेजी जा रही है, तो हम स्थानीय ऐप्लिकेशन को सूचना देते हैं, "अरे, कृपया इस चीज़ को इनफ़्लेट करें, इस चीज़ को बाद में बनाएं."
इसके बाद, तीसरी कैटगरी है सीरियलाइज़ेशन.
मेरा मतलब है कि मेरे पास एक JSON सीरियलाइज़ेशन है, जो सभी सदस्यों को दोहराकर हो रहा है.
यह वाकई में बहुत ही घिनौना है, लेकिन कभी-कभी इस तरह की चीज़ें इंटरनेट पर मौजूद रहती हैं.
ज़रूरी नहीं है कि समस्या को तुरंत ठीक किया जाए.
लेकिन, आइए बात करते हैं कि इसका क्या नतीजा निकलता है, है न?
जैसे-जैसे ये रिफ़्लेक्टिव कॉल इकट्ठा होते जाते हैं, आपको आसान काम करने की प्रेरणा मिलती है. साथ ही, उपयोगकर्ता अनुभव के बजाय डेवलपर अनुभव को ऑप्टिमाइज़ करने की प्रेरणा मिलती है.
मुझे लगता है कि यह काफ़ी बुरा है. इसलिए, मैं एक टास्क जोड़ूँगा और सब ठीक हो जाएगा.
# TODO: clean this up
-keep com.example.analytics.bloat.** { * };किसी दिन, मैं इसे ठीक कर दूंगा, है ना?
इसलिए, आइए इस बारे में बात करते हैं कि हमें क्या करना चाहिए. कम से कम आज से पहले हमें क्या करना चाहिए था. यहां डेटा को सेव रखने का एक सही नियम कैसे लिखा जा सकता है.
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}ठीक है, तैयार हो जाएं.
इसलिए, यहां पहला हिस्सा यह है कि हमें उस क्लास को बनाए रखना है जिसे हम इस रिफ़्लेक्टिव फ़ंक्शन से रिटर्न कर रहे हैं.
### Keep the EventFilter
-keep class com.example.analytics.bloat.EventFilter {
*
}नहीं, रुकिए.
क्या आपको पता है?
यह सही नहीं है.
यह Kotlin क्लास है. मुझे यह पक्का करना होगा कि यह स्ट्रिंग टाइप हो. इसके लिए, मुझे इसमें KT जोड़ना होगा.
### Keep the actual EventFilter
-keep class com.example.analytics.bloat.EventFilterKt {
*
}यहां ध्यान देने वाली बात यह है कि मैं असल में EventFilter नहीं, बल्कि EventFilter की सबक्लास दिखा रहा हूं. इसलिए, मुझे EventFilter को एक्सटेंड करने वाली क्लास को बनाए रखना होगा, ताकि इसे सही तरीके से इनफ़्लेट किया जा सके.
### Keep the EventFilter subclasses
-keep class ** extends com.example.analytics.bloat.EventFilterKt {
*
}अगर मुझे इसे और बेहतर तरीके से करना है, तो मुझे क्या करना चाहिए? यह तरीका काम कर सकता है, लेकिन इसे और बेहतर बनाने के लिए, एक अच्छे लाइब्रेरी डेवलपर के तौर पर मुझे और क्या करना चाहिए?
मुझे -keepclasseswithmembers का इस्तेमाल करना चाहिए और सिर्फ़ उन क्लास को रखना चाहिए जिनमें यह खास <init> कंस्ट्रक्टर है. इसका इस्तेमाल, मैं ऑब्जेक्ट को इनफ़्लेट करने के लिए कर रहा हूं.
### Keep the EventFilter subclasses, if they have this constructor
-keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}अगर मुझे और बेहतर तरीके से काम करना है, तो मैं allowaccessmodification जोड़ूंगा. इससे, इस Keep नियम से टारगेट किए जा रहे सभी एपीआई को और भी ऑप्टिमाइज़ किया जा सकेगा. ऐसा इसलिए, क्योंकि मुझे R8 से ऑप्टिमाइज़ेशन को रोकना नहीं है. मुझे एक अच्छा नागरिक बनने की कोशिश करनी है.
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}इसके बाद, आखिरी चरण आता है. इसे शायद ही कोई करता है, क्योंकि इसमें थोड़ा ज़्यादा काम करना पड़ता है. हालांकि, लाइब्रेरी के लिए यह बहुत अच्छा है. इसमें, इसे शर्त के साथ लागू किया जाता है.
### -if makes this conditional, critical for libraries
-if class com.example.analytics.bloat.EventToolsKt {
class com.example.analytics.bloat.EventFilterKt createEventFilter(java.lang.String);
}
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}अब, सैद्धांतिक तौर पर, आपको उन सभी सबक्लास को रखने की ज़रूरत नहीं है. ऐसा तब तक करें, जब तक कि आप इस फ़ंक्शन को कॉल न कर रहे हों. Keep नियम के सबसे ऊपर मौजूद, if स्टेटमेंट यही करता है.
हालांकि, मैं आपसे हाथ उठाकर जवाब देने के लिए कह सकता हूं. लेकिन मुझे पता है कि शायद ही कोई ऐसा करता हो, है न?
जैसे, उसे लिखना और उसे शर्त के मुताबिक बनाना.
तो, क्या होगा अगर हम इसे थोड़ा और बेहतर बना सकें?
आइए, सबसे पहले देखते हैं कि
शायद इसे करने का कोई और तरीका हो.
इसलिए, हमने इस बारे में बात की कि अगर मैं मैन्युअल तरीके से लिखा गया, सीमित दायरे वाला Keep नियम लिखूं, तो क्या होगा?
तकनीकी तौर पर ऐसा किया जा सकता है, लेकिन इसे लिखना और पढ़ना मुश्किल है. साथ ही, इसका रखरखाव करना भी बहुत मुश्किल है, क्योंकि इसमें स्ट्रिंग टाइप के कई छोटे-छोटे बिट होते हैं. इनमें से किसी एक में भी बदलाव होने पर, पूरा कोड काम करना बंद कर सकता है.
इसके नीचे कुछ है, जिसके नीचे बदलाव होता है.
हमारे पास इसके विकल्प भी हैं.
हमारे पास @Keep जैसे एनोटेशन हैं.
@Keep, AndroidX एनोटेशन पैकेज में काफ़ी समय से मौजूद है. हालांकि, आजकल इसका इस्तेमाल करने से मना किया जाता है.
@androidx.annotation.Keep
class EventFilter { … }
@androidx.annotation.Keep
class SizeBasedEventFilter { … }
@androidx.annotation.Keep
class DropLogsEventFilter { … }यह लाइब्रेरी डेवलपर के लिए अच्छा समाधान नहीं है. असल में, इसे अंदरूनी तौर पर प्रतिबंधित किया गया है. ऐसा इसलिए, क्योंकि इसका रखरखाव करना आसान है, लेकिन यह बहुत ज़्यादा लचीला नहीं है. साथ ही, इसमें वे ऑप्टिमाइज़ेशन शामिल नहीं हैं जिनके बारे में मैंने पहले बात की थी.
ऐसा नहीं हो सकता.
इसकी वजह से, साइट का विश्लेषण नहीं किया जा सकता.
अब, अगर हम पहले वाले मामले को देखें, तो "अरे, मैं अपने EventFilter पर @Keep का इस्तेमाल कर सकता हूँ, है न?"
@Keep
class EventFilter { … }नहीं.
@Keep, सबक्लास को टारगेट नहीं करता. यह सिर्फ़ उस क्लास को टारगेट करता है.
@Keep
class EventFilter { … }
@Keep
class SizeBasedEventFilter { … }
@Keep
class DropLogsEventFilter { … }इसलिए, मुझे सभी सबक्लास को टारगेट करना होगा. अब मैं उस एक कीप रूल के मुकाबले ज़्यादा @Keep एनोटेशन लिख रहा हूं जिसे लिखना मुश्किल था.
इसलिए, इन दोनों में समस्याएं हैं, है न?
इन्हें भूलना आसान है, इनके बारे में देर से पता चलता है, और ये डिफ़ॉल्ट रूप से कॉलर पर निर्भर नहीं होते.
खास तौर पर लाइब्रेरी के लिए, हम एक बेहतर तरीका चाहते हैं.
हम चाहते हैं कि लोगों को आसानी से ऐसी चीज़ें मिलें जो तुरंत इस्तेमाल की जा सकें.
यहां हमें एक बड़ी समस्या यह दिखती है कि ये चीज़ें उस रिफ़्लेक्शन से काफ़ी दूर हैं जिससे हमें असल में सुरक्षा करनी है.
आखिर में, हमें एक ऐसा समाधान चाहिए जो यहां मौजूद रिफ़्लेक्शन कोड के ख़िलाफ़ काम करे. यह कोड, टारगेट की गई चीज़ों को बढ़ा रहा है.
चलिए, Jetpack Keep के बारे में बात करते हैं. यह AndroidX की नई लाइब्रेरी और प्लग इन है. इसे इसी समस्या को हल करने के लिए डिज़ाइन किया गया है.
इसलिए, Jetpack Keep का मुख्य मकसद, एनोटेशन से Keep के नियम जनरेट करना है. इसके अलावा, आपके कोड बेस में पहले से मौजूद रिफ़्लेक्शन से सीधे एनोटेशन जनरेट करना भी इसका मकसद है.
इसलिए, इसे सेट अप करना बहुत आसान है.
plugins {
id("com.android.library")
id("androidx.annotation.keep")
}
dependencies {
implementation("androidx.annotation.keep:keep")
...
}यह एक Gradle प्लगिन है और एनोटेशन लाइब्रेरी है.
हालांकि, आइए देखते हैं कि यह असल में कैसे काम करता है-- बिल्ड टाइम पर कैसे काम करता है.
इसलिए, अगर मेरे पास यह पिछला उदाहरण है, जहां मैं किसी जाने-पहचाने टाइप के सबक्लास को इनफ़्लेट कर रहा हूं, तो मुझे क्या करना होगा?
मैं सिर्फ़ lintFix चलाता हूं.
./gradlew analyticsbloat:lintFixलिंट यहां इस तरीके में होने वाले सभी रिफ़्लेक्शन का विश्लेषण करता है और कहता है, "आपको इस एनोटेशन की ज़रूरत है."
@UsesReflectionToConstruct(
classConstant = EventFilter::class,
parameterTypes = []
)
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}यह एनोटेशन ठीक से बताता है कि इस कॉल में मौजूद रिफ़्लेक्शन क्या कर रहा है.
इसका मतलब है, "मैं इस टाइप की क्लास को इनफ़्लेट कर रहा हूं."
इस मामले में, EventFilter एक जानी-मानी क्लास है.
मैं इसे रेफ़रंस देने के लिए स्ट्रिंग का इस्तेमाल नहीं कर रहा हूं, क्योंकि मुझे इसकी ज़रूरत नहीं है.
यह अतिरिक्त सुरक्षा है और यह-- मैं parameterTypes को भी एन्कोड कर रहा हूं, ताकि यह सटीक तौर पर बताया जा सके कि कौनसा कंस्ट्रक्टर रखा जा रहा है.
लेकिन इससे क्या होता है?
जैसे, इससे R8 बिल्ड को कैसे जानकारी मिलती है?
यह कीप रूल जनरेट करता है. जैसा कि आपने पहले देखा था, यह वही कीप रूल है जो हमारे पास पहले था.
# Generated Keep Rule
-if class com.example.analytics.bloat.EventTools {
class com.example.analytics.bloat.EventFilter createEventFilter(java.lang.String);
}
-keepclasseswithmembers,allowaccessmodification class ** extends class com.example.analytics.bloat.EventFilter {
void <init>();
}यह बिलकुल एक जैसी क्लास और सबक्लास को टारगेट कर रहा है. साथ ही, यह इस रिफ़्लेक्शन पॉइंट पर निर्भर करता है.
मुझे सभी सबक्लास को एनोटेट करने की ज़रूरत नहीं है.
मुझे डेटा को सेव रखने के नियम वाली फ़ाइल को कहीं और सेव करने की ज़रूरत नहीं है.
यह डेटा को सेव रखने के नियमों को मैनेज करने का आसान तरीका है.
आइए, वैकल्पिक डिपेंडेंसी के उदाहरण को फिर से देखें.
@UsesReflectionToConstruct(
className = "com.example.analytics.bloat.crash.CrashReporterTransport",
parameterTypes = [Context::class]
)
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}अब, वहां, क्लास क्लास पाथ पर नहीं थी, लेकिन मैं फिर भी इसका रेफ़रंस दे सकता हूं.
मैं स्ट्रिंग का इस्तेमाल करके ऐसा करता हूं. हालांकि, पैरामीटर टाइप जैसी अन्य चीज़ों को क्लास रेफ़रंस से रेफ़र किया जा सकता है. इसलिए, मैं ऐसा करता हूं.
इस मामले में भी, हम डेटा को सेव रखने का सबसे सही नियम जनरेट करते हैं.
# Generated Keep Rule
-if class com.example.analytics.bloat.CrashConnection {
class com.example.analytics.bloat.EventTransport tryInitCrashReporter(android.content.Context);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.crash.CrashReporterTransport {
void <init>(android.content.Context);
}यह शर्त के हिसाब से, खास, और टारगेट किया गया होता है. साथ ही, यह टारगेट की गई क्लास पर जहां भी संभव हो वहां ऑप्टिमाइज़ेशन की सुविधा देता है.
यहां फ़ील्ड पर रिफ़्लेक्शन दिया गया है. इस मामले में, मैं किसी ऑब्जेक्ट के अंदर मौजूद सभी फ़ील्ड पर बार-बार काम कर रहा था. हालांकि, यहां हम इस स्टार ऑपरेटर की मदद से सभी फ़ील्ड को टारगेट करके, इसे ठीक से मैनेज करते हैं.
// TODO: migrate to Kotlinx Serialization or Moshi codegen.
@UsesReflectionToAccessField(
classConstant = UserActionEvent::class,
fieldName = "*"
)
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}इससे मुझे सभी फ़ील्ड को टारगेट करने की सुविधा मिलती है. साथ ही, यह बिलकुल वैसा ही कीप रूल जनरेट करता है जैसा कि इस रिफ़्लेक्शन के लिए उम्मीद की जा सकती है. यह रिफ़्लेक्शन मेरे ऐप्लिकेशन में नहीं होना चाहिए, लेकिन मैं वापस आकर इसे हटा दूंगा. मैं वादा करता हूं.
# Generated Keep Rule
-if class com.example.analytics.bloat.serialization.EventSerializer {
class org.json.JSONObject serializeEvent(com.example.analytics.bloat.model.UserActionEvent);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.model.UserActionEvent {
<fields>;
}Jetpack Keep क्या करता है?
इससे कीप रूल के रखरखाव में लगने वाला समय काफ़ी कम हो जाता है.
अपने-आप जनरेट होने वाली हर चीज़ के लिए, यह नियमों को बनाए रखने के लिए, आपकी चिंता और जोखिम वाले हिस्से को हटा देता है.
यह आपको लिंट का डिटरमिनिज़्म भी देता है.
यह सिर्फ़ आपके लिए चीज़ों को ठीक नहीं करता, क्योंकि आप बार-बार कोशिश कर रहे हैं और माइग्रेट करने की कोशिश कर रहे हैं. यह नए रिफ़्लेक्शन को पकड़ता है और कोड में बदलाव होने पर, रिफ़्लेक्टिव बदलावों को पकड़ता है. यह सुरक्षा बेहद ज़रूरी है.
क्या मैं इसका इस्तेमाल कर सकता/सकती हूं?
तो यहां डिपेंडेंसी क्या हैं?
प्लगिन और लिंट के लिए, एजीपी 8.7 ज़रूरी है.
यह सुविधा करीब-करीब सभी के लिए उपलब्ध होनी चाहिए.
हालांकि, इसे कहीं भी इस्तेमाल किया जा सकता है.
इसका इस्तेमाल ऐप्लिकेशन में किया जा सकता है.
इसका इस्तेमाल Android लाइब्रेरी, एएआर, केएमपी लाइब्रेरी, और जेएआर में किया जा सकता है. हालांकि, इसके लिए आपको कुछ मैन्युअल सेटिंग करनी होंगी.
हालांकि, इसकी मुख्य सुविधा यह है कि लाइब्रेरी डेवलपर के तौर पर इसका इस्तेमाल करना सुरक्षित है.
अगर कोई व्यक्ति R8 के पुराने वर्शन या आपकी लाइब्रेरी का इस्तेमाल करने वाले किसी तीसरे पक्ष के टूल का इस्तेमाल कर रहा है, तो भी हम असल स्टैंडर्ड उपभोक्ता नियम एक्सपोर्ट करते हैं. इससे कोई भी व्यक्ति अब भी आपका इस्तेमाल कर सकता है और उसे किसी नए काम न करने वाले फ़ॉर्मैट का इस्तेमाल करने की चिंता नहीं करनी पड़ती.
मैंने आपको Jetpack Keep के बारे में बताया. यह आपके ऐप्लिकेशन को ऑप्टिमाइज़ करने में मदद करता है. साथ ही, रिफ़्लेक्टिव कोड कॉल को सुरक्षित रखने में मदद करता है. अब हम उन नई सुविधाओं के बारे में बात करते हैं जिन्हें हमने हाल ही में Android का सीएलआई में लॉन्च किया है. इनसे रिलीज़ के आखिरी चरण की दोबारा जांच की जा सकती है.
Breana ने पहले बताया था कि Play Findings में ऐसे टूल हैं जो आपको आपके ऐप्लिकेशन के साइज़, DEX साइज़, और ऑप्टिमाइज़ेशन के प्रतिशत के बारे में जानकारी देंगे.
अब हमने इसे Android का सीएलआई के ज़रिए भी उपलब्ध कराना शुरू कर दिया है.
यह ठीक उसी लॉजिक का इस्तेमाल करता है जो Play Store करता है. इसलिए, Play Store पर बंडल अपलोड किए बिना, सीएलआई पर ही बंडल का विश्लेषण किया जा सकता है.
$ android analyze <mybundle.aab>
$ android analyze <mybundle.apk>साथ ही, आपको स्थानीय विश्लेषण के साथ, सीधे और तुरंत वही जानकारी मिलती है.
इस मामले में, ऐप्लिकेशन को ऑप्टिमाइज़ेशन की दर बहुत कम होने की चेतावनी मिल रही है. साथ ही, उसे यह भी बताया जा रहा है कि "यह आपके DEX का साइज़ है और यहां आपके ऑप्टिमाइज़ेशन का प्रतिशत दिया गया है." ठीक उसी तरह जैसे Play Store आपको बताता है.
हालांकि, यहां हमारे पास JSON आउटपुट भी है. इससे, अगर आपको इनमें से किसी भी वैल्यू में अचानक आई गिरावट के ख़िलाफ़ रिग्रेशन टेस्टिंग को ऑटोमेट करने के लिए स्क्रिप्ट लिखनी है, तो ऐसा किया जा सकता है. ऐसा इसलिए, क्योंकि ये वे नंबर हैं जिन्हें Google Play लागू करता है. अगर आपको इन्हें ट्रैक करना है, तो इस JSON वैल्यू यानी कि इन JSON रिपोर्ट की मदद से, ऐसा आसानी से किया जा सकता है.
{
"optimization": {
"bundleAnalysisResult": {
"agpVersion": "9.3.2",
"r8Json": {
"version": "9.3.16",
"options": {
"isRepackageClassesEnabled": true,
"isProguardCompatibilityModeEnabled": false
},
"stats": {
"shrinkingRatio": 0.2306,
"optimizationRatio": 0.2351,
"obfuscationRatio": 0.2322
},
...
}
"mappingObfuscationRatio": 0.21631891,
"dexHeuristicObfuscationRatio": 0.25118626,
"uncompressedDexSizeBytes": "16198152"
}यहां हमें वही संख्याएं दिख रही हैं.
android analyze में सिर्फ़ यही सुविधा नहीं है.
android analyze को लाइब्रेरी पर भी चलाया जा सकता है.
$ android analyze <library.aar>
$ android analyze <library.jar>जैसा कि ब्रियाना ने बताया, शिपिंग के साथ चलने वाली लाइब्रेरी में कई समस्याएं हैं. इनमें, डेटा को सेव रखने के लिए दुर्भाग्यपूर्ण नियम भी शामिल हैं.
इसलिए, आइए इसका इस्तेमाल करके इनमें से कुछ समस्याओं को देखें.
इसलिए, जब मैं इसे अपने किसी खास एएआर पर चलाता हूं, तो मुझे दिखता है कि इस लाइब्रेरी में कई समस्याएं हैं. ये समस्याएं सबसे ऊपर हाइलाइट की गई हैं.
इस लाइब्रेरी का कीप रेशियो ज़्यादा है. इसका मतलब है कि इसमें बिना किसी सीमा के लागू होने वाले कीप रूल की वजह से, बहुत ज़्यादा डेटा सेव किया जा रहा है.
इसमें ऐसे कीप रूल शामिल हैं जो इसे ऑप्टिमाइज़ होने से रोकते हैं. यहां कुछ संख्याएं दी गई हैं, जिनसे पता चलता है कि यह कितना खराब है. इसमें ऐसे कीप रूल भी शामिल हैं जो शायद इनमें से सबसे डरावने हैं. ये किसी भी चीज़ को टारगेट नहीं कर रहे हैं. इसका मतलब है कि वे किसी और को टारगेट कर रहे हैं.
बहुत खतरनाक कोड स्मेल.
इसलिए, एएआर में इन सभी चीज़ों की जांच की जा सकती है. खास तौर पर, लाइब्रेरी पब्लिशर के तौर पर, यह पक्का करने का यह एक शानदार तरीका है कि आप खराब कीप रूल शिप न करें. साथ ही, यह भी पक्का करें कि जब कोई व्यक्ति लाइब्रेरी पर काम कर रहा हो, तो उसे कीप रूल लिखने में विशेषज्ञता न हो.
यहां आपको दिखेगा कि हम कीप रेशियो भी आउटपुट करते हैं.
इससे आपको अपनी लाइब्रेरी की क्वालिटी को बेहतर बनाने में मदद मिलती है.
अगर मैं R8 की मदद से, किसी लाइब्रेरी को पूरी तरह से अलग से ऑप्टिमाइज़ करूं, जिसमें कोई रेफ़रंस न हो, तो मुझे शून्य बाइट दिखना चाहिए, है न?
जैसे, यह खाली होना चाहिए, क्योंकि इसमें कुछ भी नहीं है.
यह सबसे सही तरीका होगा.
इस मामले में, ऐसा नहीं है.
R8 और D8 साइज़ को सीधे तौर पर देखा जा सकता है.
हम कुछ ऐसे नियमों को भी हाइलाइट करते हैं जो सबसे खराब हैं, ताकि आप उन्हें आसानी से देख सकें.
यह भी JSON फ़ॉर्मैट में होता है. इसलिए, अगर आप ऐप्लिकेशन डेवलपर हैं और आपको लाइब्रेरी के अपडेट को रोकना है या आप लाइब्रेरी डेवलपर हैं और आपको अपनी लाइब्रेरी की क्वालिटी में बड़ी गड़बड़ियों को रोकना है, तो आपके पास दोनों ही मामलों में एक जैसा विश्लेषण करने का विकल्प होता है. साथ ही, Android का सीएलआई को रैप करने वाली एक आसान स्क्रिप्ट की मदद से, इन समस्याओं को होने से रोका जा सकता है.
{
"optimization": {
"libraryAnalysisResult": {
"dex": {
"d8CompileResult": {
"dexSizeBytes": 4523352
},
"r8CompileResult": {
"dexSizeBytes": 3266596,
"configuration": {
"usedPackageWideKeepRules": [
{
"source": "-keep class com.example.analytics.bloat.** { *; }",
"keptItemCount": 125579,
},
...
],
"unusedPackageWideKeepRules": [
{
"source": "-keep class some.other.library.** { *; }",
},
...
]
},
...
}इसलिए, अगर आपको किसी लाइब्रेरी में कोई समस्या मिलती है, तो सबसे पहले उसे अपडेट करने की कोशिश करें.
जैसा कि Breana ने पहले बताया था, हमने Google की एक दर्जन से ज़्यादा लाइब्रेरी को ठीक किया है, ताकि कुछ बेहद खराब कीप नियमों को ठीक किया जा सके. साथ ही, ऐसी अन्य लाइब्रेरी भी हैं जिन्हें ठीक किया गया है. हालांकि, उन्हें इस्तेमाल करने वाले ऐप्लिकेशन उन्हें अपडेट नहीं कर रहे हैं.
यह एक बेहतरीन शुरुआत है.
दूसरा, आपको पता है कि इसके बाद, किसी समस्या को ढूंढा जा सकता है और लाइब्रेरी डेवलपर से संपर्क किया जा सकता है. साथ ही, हो सकता है कि आप कोई ऐसी ऐनलाइज़र रिपोर्ट अटैच करें जो उन्हें बताती हो, "अरे, आप बहुत ज़्यादा डेटा सेव कर रहे हैं.
कृपया मेरे ऐप्लिकेशन को ऑप्टिमाइज़ करने में मेरी मदद करें."
हमने R8 कॉन्फ़िगरेशन ऐनलाइज़र के बारे में बात की है. इससे आपको R8 बिल्ड में होने वाली हर गतिविधि को देखने में मदद मिलती है. साथ ही, यह पता चलता है कि किस चीज़ को ऑप्टिमाइज़ किया जा सकता है और किसे नहीं.
हमने Jetpack Keep के बारे में बात की है. यह एक नई लाइब्रेरी है, जो आपको बिना किसी मेहनत के, अपने-आप कीप रूल लिखने में मदद करती है.
Android का सीएलआई, एक नया टूल है. इसकी मदद से, आखिरी चरण की जांच की जा सकती है और यह पक्का किया जा सकता है कि आपके एजेंट या आपकी टीम के जूनियर इंजीनियर ने उपभोक्ताओं के लिए कुछ भी खराब न किया हो.
अगर आपको इस बारे में ज़्यादा जानना है, तो R8, कॉन्फ़िगरेशन ऐनालाइज़र, और Jetpack Keep के दस्तावेज़ पढ़ें.
Android का सीएलआई पर दस्तावेज़ भी मौजूद है. इसमें विश्लेषण करने के निर्देश और उसे इस्तेमाल करने के तरीके के बारे में बताया गया है.
बहुत-बहुत धन्यवाद.
android {
buildTypes {
release {
optimization {
// no need to specify default
// rules and resource shrinking
enabled = true
}
}
}
...
}android {
buildTypes {
release {
// Enables code optimizations.
isMinifyEnabled = true
// Enables resource shrinking.
isShrinkResources = true
proguardFiles(
// Default rules.
getDefaultProguardFile(
"proguard-android-optimize.txt"
),
... // list out each file with keep rules
)
...
}
}
...
}$ android skills list
$ android skills add r8-analyzerfun createWithReflection(className: String): MyObject {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as MyObject
}// Problem 1 - optional dependency
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}
// Problem 2 - dynamically instantiate subclass
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}
// Problem 3 - serialization
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}# TODO: clean this up
-keep com.example.analytics.bloat.** { * };fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}### Keep the EventFilter
-keep class com.example.analytics.bloat.EventFilter {
*
}### Keep the actual EventFilter
-keep class com.example.analytics.bloat.EventFilterKt {
*
}### Keep the EventFilter subclasses
-keep class ** extends com.example.analytics.bloat.EventFilterKt {
*
}### Keep the EventFilter subclasses, if they have this constructor
-keepclasseswithmembers class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}### -if makes this conditional, critical for libraries
-if class com.example.analytics.bloat.EventToolsKt {
class com.example.analytics.bloat.EventFilterKt createEventFilter(java.lang.String);
}
### Keep the EventFilter subclasses, if they have this constructor, allow more optimization
-keepclasseswithmembers,allowaccessmodification class ** extends com.example.analytics.bloat.EventFilterKt {
void <init>();
}@androidx.annotation.Keep
class EventFilter { … }
@androidx.annotation.Keep
class SizeBasedEventFilter { … }
@androidx.annotation.Keep
class DropLogsEventFilter { … }@Keep
class EventFilter { … }@Keep
class EventFilter { … }
@Keep
class SizeBasedEventFilter { … }
@Keep
class DropLogsEventFilter { … }plugins {
id("com.android.library")
id("androidx.annotation.keep")
}
dependencies {
implementation("androidx.annotation.keep:keep")
...
}./gradlew analyticsbloat:lintFix@UsesReflectionToConstruct(
classConstant = EventFilter::class,
parameterTypes = []
)
fun createEventFilter(className: String): EventFilter {
return Class.forName(className)
.getDeclaredConstructor()
.newInstance() as? EventFilter
}# Generated Keep Rule
-if class com.example.analytics.bloat.EventTools {
class com.example.analytics.bloat.EventFilter createEventFilter(java.lang.String);
}
-keepclasseswithmembers,allowaccessmodification class ** extends class com.example.analytics.bloat.EventFilter {
void <init>();
}@UsesReflectionToConstruct(
className = "com.example.analytics.bloat.crash.CrashReporterTransport",
parameterTypes = [Context::class]
)
fun tryInitCrashReporter(context: Context): EventTransport? {
return runCatching {
Class.forName("com.example.analytics.bloat.crash.CrashReporterTransport")
.getDeclaredConstructor(Context::class.java)
.newInstance(context) as? EventTransport
}.getOrNull()
}# Generated Keep Rule
-if class com.example.analytics.bloat.CrashConnection {
class com.example.analytics.bloat.EventTransport tryInitCrashReporter(android.content.Context);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.crash.CrashReporterTransport {
void <init>(android.content.Context);
}// TODO: migrate to Kotlinx Serialization or Moshi codegen.
@UsesReflectionToAccessField(
classConstant = UserActionEvent::class,
fieldName = "*"
)
fun serializeEvent(event: UserActionEvent): JSONObject {
val json = JSONObject()
for (field in event.javaClass.declaredFields) {
field.isAccessible = true
json.put(field.name, field.get(event))
}
return json
}# Generated Keep Rule
-if class com.example.analytics.bloat.serialization.EventSerializer {
class org.json.JSONObject serializeEvent(com.example.analytics.bloat.model.UserActionEvent);
}
-keepclasseswithmembers,allowaccessmodification class com.example.analytics.bloat.model.UserActionEvent {
<fields>;
}$ android analyze <mybundle.aab>
$ android analyze <mybundle.apk>{
"optimization": {
"bundleAnalysisResult": {
"agpVersion": "9.3.2",
"r8Json": {
"version": "9.3.16",
"options": {
"isRepackageClassesEnabled": true,
"isProguardCompatibilityModeEnabled": false
},
"stats": {
"shrinkingRatio": 0.2306,
"optimizationRatio": 0.2351,
"obfuscationRatio": 0.2322
},
...
}
"mappingObfuscationRatio": 0.21631891,
"dexHeuristicObfuscationRatio": 0.25118626,
"uncompressedDexSizeBytes": "16198152"
}$ android analyze <library.aar>
$ android analyze <library.jar>{
"optimization": {
"libraryAnalysisResult": {
"dex": {
"d8CompileResult": {
"dexSizeBytes": 4523352
},
"r8CompileResult": {
"dexSizeBytes": 3266596,
"configuration": {
"usedPackageWideKeepRules": [
{
"source": "-keep class com.example.analytics.bloat.** { *; }",
"keptItemCount": 125579,
},
...
],
"unusedPackageWideKeepRules": [
{
"source": "-keep class some.other.library.** { *; }",
},
...
]
},
...
}