टूल और परफ़ॉर्मेंस

Compose में बेहतर तरीके से काम करें

स्पीकर

इसके बारे में

बातचीत के बारे में जानकारी

Jetpack Compose की मदद से, अच्छी क्वालिटी के यूज़र इंटरफ़ेस डिज़ाइन करना आसान हो जाता है. हालांकि, यूआई की परफ़ॉर्मेंस पर असर डालने वाली समस्याओं को ट्रैक करना मुश्किल हो सकता है. इस तरह की समस्याओं की वजह से, आपके उपयोगकर्ताओं को समस्याएं आ सकती हैं. जैसे, स्टार्टअप लेटेंसी और ड्रॉप किए गए फ़्रेम, जो स्क्रोल जैंक या रुक-रुककर चलने वाले ऐनिमेशन के तौर पर दिखते हैं.

इस सेशन में, हम ऐसे टूल के बारे में बताएंगे जिनसे यह पता चलता है कि आपके यूज़र इंटरफ़ेस (यूआई) की परफ़ॉर्मेंस पर किस वजह से असर पड़ रहा है. इसमें Perfetto और (री-)कंपोज़िशन ट्रेसिंग पर फ़ोकस किया जाएगा. इसके बाद, हम उन एंटी-पैटर्न के बारे में जानेंगे जिनका इस्तेमाल शायद आप कर रहे हों और जिनकी वजह से आपको बिना जानकारी के पैसे चुकाने पड़ रहे हों. साथ ही, हम यह भी जानेंगे कि उन्हें अपने कोड में कैसे ढूंढा जा सकता है और उनकी जगह कौनसे सही पैटर्न इस्तेमाल किए जा सकते हैं. इस सेशन के आखिर तक, आपको Compose में परफ़ॉर्मेंस को ध्यान में रखकर काम करने का तरीका समझ आ जाएगा. साथ ही, आपको अपने ऐप्लिकेशन में परफ़ॉर्मेंस ऑप्टिमाइज़ेशन को प्रोफ़ाइल करने, उसका विश्लेषण करने, और उसकी पुष्टि करने का तरीका भी पता चल जाएगा.

खास जानकारी

  • Perfetto ट्रेस, डिवाइस पर की गई गतिविधि को रिकॉर्ड करते हैं. साथ ही, मुख्य थ्रेड के साथ-साथ कंपोज़िशन और लेआउट जैसे कंपोज़ स्लाइस दिखाते हैं. फ़्रेम की टाइमलाइन की जांच करने और फ़ेज़ की अवधि को ट्रैक करने के लिए, इनका इस्तेमाल करें.
  • कंपोज़ेबल के अंदर हर ऑपरेशन के लिए कुछ न कुछ कीमत चुकानी पड़ती है. इसमें कोरूटीन को लॉन्च करना और रद्द करना शामिल है. साइड इफ़ेक्ट एपीआई की जांच करें और कंपोज़िशन ओवरहेड को कम करने के लिए, एक जैसी कुंजियों वाले इफ़ेक्ट को मर्ज करें.
  • कंपोज़िशन ट्रेसिंग, ऐप्लिकेशन बनाने की सुविधा को एनोटेट करती है. वहीं, रीकंपोज़िशन ट्रेसिंग (1.13+) की सुविधा, स्टेट रीड और राइट को ट्रैक करती है. इससे रीकंपोज़िशन होता है. इनकी मदद से, Perfetto ट्रेस में सीधे तौर पर अनचाहे काम का पता लगाया जा सकता है.
  • ComputedState (Compose 1.13+) की मदद से, अमान्य होने की अनचाही स्थितियों से बचा जा सकता है. साथ ही, रीड-आफ़्टर-राइट के अमान्य होने की स्थितियों के दौरान, यह आम तौर पर DerivedState की तुलना में ज़्यादा तेज़ी से काम करता है. DerivedState तब काम आता है, जब ज़्यादा कंप्यूटेशन या मेमोरी ऐलोकेशन की ज़रूरत होती है और कैश मेमोरी का इस्तेमाल करने से फ़ायदा मिलता है.
  • कंपोज़ेबल को स्किप करने पर, रखरखाव की लागत और पैरामीटर की तुलना करने का ओवरहेड आता है. स्थिरता को ज़बरदस्ती लागू करने के बजाय, स्ट्रॉन्ग स्किपिंग और ऑब्जेक्ट आइडेंटिटी इक्वालिटी पर भरोसा करें या अमान्यताओं से पूरी तरह बचें.

कौशल

संसाधन

Gemini Notebook

विषय

  • jetpack-compose
  • android-performance
  • perfetto
  • मानदंड
  • कोरूटीन
  • कंपोज़िशन-ट्रेसिंग
  • android-cli

ट्रांसक्रिप्ट

ट्रांसक्रिप्ट डाउनलोड करें

परिचय

  • नमस्ते.

  • सभी को नमस्ते।

  • उम्मीद है कि आपका ब्रेक अच्छा रहा होगा.

  • उम्मीद है कि आपने चाय-कॉफ़ी पी ली होगी, मॉइस्चराइज़र लगा लिया होगा, और बाकी सब कर लिया होगा.

  • आज हम इस बारे में ज़्यादा जानकारी देंगे.

  • हां, बिल्कुल. चलिए, शुरू करते हैं.

  • आज हम इस बारे में ज़्यादा जानकारी देंगे.

  • मेरा नाम आंद्रेई शिकोव है. आज मेरे साथ एंड्रयू बेली भी स्टेज पर मौजूद हैं.

  • हम दोनों, Compose कंपाइलर रनटाइम टीम में काम करते हैं. हालांकि, हम परफ़ॉर्मेंस के साथ-साथ कई अन्य चीज़ों पर भी काम करते हैं.

  • आज हम परफ़ॉर्मेंस से जुड़े आपके सवालों के जवाब देंगे. ऐसा इसलिए, क्योंकि Jetpack Compose को Android डेवलपमेंट के लिए डिफ़ॉल्ट यूज़र इंटरफ़ेस (यूआई) टूलकिट के तौर पर सेट किया गया है.

  • अब कई लोग यह पूछ रहे हैं कि हम यह कैसे पक्का कर सकते हैं कि हमारा Compose यूज़र इंटरफ़ेस (यूआई) ज़्यादा से ज़्यादा बेहतर परफ़ॉर्म करे.

  • इसलिए, आज हम इस बारे में ज़्यादा जानकारी देंगे.

  • साथ ही, हम कुछ ऐसे टूल भी लॉन्च करने वाले हैं जिनकी मदद से, Compose में बेहतर तरीके से काम किया जा सकेगा.

  • सबसे पहले, मैं इस बारे में बात करना चाहता हूं कि हमारे लिए परफ़ॉर्मेंस क्यों मायने रखती है.

  • जब क्वालिटी और अच्छी क्वालिटी वाले ऐप्लिकेशन बनाने की बात आती है, तो आम तौर पर लोग ऐनिमेशन और बेहतरीन सुविधाओं के बारे में सोचते हैं. इस वजह से, कभी-कभी परफ़ॉर्मेंस पर ध्यान नहीं दिया जाता. हालांकि, उपयोगकर्ता आपके ऐप्लिकेशन की परफ़ॉर्मेंस के आधार पर ही उसके बारे में राय बनाते हैं.

  • इसलिए, अगर ऐनिमेशन रुक-रुककर चलते हैं, तो लोगों को लगता है कि ऐप्लिकेशन की परफ़ॉर्मेंस खराब है.

  • इसलिए, आज हम जिन मेट्रिक और संख्याओं के बारे में बात करेंगे वे असल में प्रॉक्सी मेट्रिक हैं. इन्हें हमने यह दिखाने के लिए चुना है कि उपयोगकर्ता, ऐप्लिकेशन के बारे में क्या सोचते हैं. साथ ही, वे सभी ऐनिमेशन और स्क्रीन पर होने वाली सभी चीज़ों के बारे में क्या सोचते हैं.

  • इसलिए, अपने ऐप्लिकेशन को ऑप्टिमाइज़ करने का मतलब है कि उसे बेहतर अनुभव के लिए ऑप्टिमाइज़ करना.

  • इसलिए, हम चाहते हैं कि आप इस मानसिकता के साथ आगे बढ़ें, क्योंकि आज हम कई विषयों पर गहराई से चर्चा करेंगे.

  • अब जब आपने अपने ऐप्लिकेशन की परफ़ॉर्मेंस को देखना शुरू कर दिया है, तो मैं चाहता हूं कि आप तीन चीज़ें करें.

  • सबसे पहले, अगर आपको परफ़ॉर्मेंस को मेज़र करना है, तो उसे रिलीज़ मोड में मेज़र करें. इससे हमें डीबग करने लायक ज़्यादा जानकारी नहीं मिलती और हमारा ऐप्लिकेशन धीमा नहीं होता.

  • दूसरी बात, R8 या अन्य मिनीफ़ायर चालू करें.

  • इसी साल, हमने एक ऑप्टिमाइज़ेशन पेश किया है. इससे कोरूटीन की परफ़ॉर्मेंस दोगुनी हो जाती है. इसके अलावा, हम और भी कई काम करते हैं.

  • Compose को ऑप्टिमाइज़ करते समय, हम R8 पर काफ़ी भरोसा करते हैं.

  • आखिरी बात, खास तौर पर स्टार्टअप मेट्रिक के लिए, आपको यह पक्का करना होगा कि आपने बेसलाइन प्रोफ़ाइल का इस्तेमाल किया हो.

  • इसलिए, स्टार्टअप में लोड की जाने वाली क्लास, ऐप्लिकेशन के शुरू होने पर पहले से कंपाइल और लोड हो जाती हैं.

  • जैसा कि आपने देखा, यह ऐप्लिकेशन के डीबग किए जा सकने वाले सामान्य बिल्ड से छह गुना ज़्यादा है.

  • इन सभी चीज़ों को मेज़र करने पर, ये आपके कोड में मौजूद अन्य फ़ायदों के साथ जुड़ जाएंगी.

  • अब आपने ये सब कर लिया है, लेकिन ऐप्लिकेशन अब भी थोड़ा धीमा चल रहा है या परफ़ॉर्मेंस से जुड़ी अन्य समस्याएं अब भी बनी हुई हैं. ऐसे में, आपको इस परफ़ॉर्मेंस लूप में जाना होगा.

  • आपको कुछ खास स्थितियों में कुछ मेट्रिक देखनी हैं.

  • उदाहरण के लिए, मान लें कि आपके ऐप्लिकेशन को लोड होने में बहुत ज़्यादा समय लगता है. ऐसे में, हो सकता है कि आप अपने ऐप्लिकेशन को रीसेट करना चाहें, उसे सामान्य लॉन्चर की तरह शुरू करना चाहें, और यह देखना चाहें कि ऐप्लिकेशन को पहली डेटा स्क्रीन पर आने में कितना समय लगता है.

  • ऐसा भी हो सकता है कि ऐप्लिकेशन थोड़ा रुक-रुककर काम करे. इसलिए, उसे स्क्रोल करें और पक्का करें कि यह स्क्रोल बार-बार किया जा सके और स्टेबल हो.

  • इसलिए, इस स्लाइड में दिखाया गया स्टेबल एनवायरमेंट बहुत ज़रूरी है. ऐसा इसलिए, क्योंकि बाद में ऑप्टिमाइज़ेशन करने पर, आपको यह पक्का करना होगा कि आपकी मेट्रिक में सुधार दिख रहा है. साथ ही, यह पुष्टि की जा सके कि वे ऑप्टिमाइज़ेशन असल में उम्मीद के मुताबिक काम कर रहे हैं.

  • इसके लिए, मानदंड बहुत अच्छे होते हैं, क्योंकि मानदंड पहले से ही बहुत सारा डेटा कैप्चर करते हैं. वे कई बार ऐसा करते हैं, ताकि यह पक्का किया जा सके कि आपके डिवाइस में ज़्यादा शोर न हो. साथ ही, वे बहुत सारे डिवाइस सेटअप भी करते हैं, जिससे आपको मदद मिलती है.

  • साथ ही, मानदंड डिफ़ॉल्ट रूप से दोहराए जा सकने वाले परिदृश्य भी होते हैं. हम यहां यही करने की कोशिश कर रहे हैं.

  • हालांकि, अगर आप चाहें, तो कई स्क्रिप्ट की मदद से भी ऐसा किया जा सकता है.

  • आखिर में, अपनी प्रोफ़ाइल बनाएं, अपने ऐप्लिकेशन में कुछ सुधार करें, डेटा देखें, और फिर से दोहराएं. इसके बाद, हो सकता है कि आप अलग-अलग स्थितियों की मेट्रिक के साथ फिर से दोहराएं, जहां आपके ऐप्लिकेशन को समस्या आ रही है.

Perfetto ट्रेस की मदद से प्रोफ़ाइलिंग करना

  • डेटा देखते समय, आपको सबसे पहले Perfetto ट्रेस के बारे में जानना चाहिए. यह पहला टूल है, जिसके बारे में हम आज आपको बता रहे हैं.

  • Perfetto ट्रेस, ट्रेस रिकॉर्ड करने के दौरान डिवाइस पर होने वाली हर गतिविधि को रिकॉर्ड करते हैं. उदाहरण के लिए, मैक्रोबेंचमार्क रन के दौरान या रिकॉर्ड की जा रही किसी स्क्रिप्ट के दौरान.

  • यह Android की परफ़ॉर्मेंस पर काम करने वाले हर इंजीनियर के लिए बहुत मददगार है.

  • मैं आजकल अपने काम के ज़्यादातर समय में, Perfetto ट्रेस देखता हूं.

  • ये कुछ इस तरह दिखते हैं.

  • यह बहुत सारा डेटा है.

  • जैसा कि मैंने बताया, ये डिवाइस पर होने वाली हर गतिविधि को कैप्चर करते हैं.

  • इसलिए, हो सकता है कि आपको शुरुआत में अपना ऐप्लिकेशन ढूंढने में परेशानी हो. हमारा सुझाव है कि आप इस खोज बार का इस्तेमाल करें.

  • Compose और अपने ऐप्लिकेशन पैकेज जैसी चीज़ें खोजें.

  • बस जानी-पहचानी चीज़ों पर ध्यान दें.

  • जब आपको अपने ऐप्लिकेशन की प्रोसेस मिल जाएगी, तब आपको फ़्रेम टाइम लाइन दिखेगी. साथ ही, यह भी दिखेगा कि आपके ऐप्लिकेशन पर कई चीज़ों में कितना समय लगता है.

  • अगर हम इनमें से किसी एक को अलग करें, तो आपको कुछ ऐसा दिखेगा.

  • पहला है, मुख्य थ्रेड का काम.

  • Compose ज़्यादातर काम यहीं करता है.

  • इसके बाद, RenderThread आता है.

  • यहां हम कैनवस कमांड को प्रोसेस करते हैं और उन्हें जीपीयू में एन्कोड करते हैं.

  • इसके बाद, जीपीयू अपना काम करता है और सिस्टम उसे कंपोज़िट करता है.

  • आज हम सिर्फ़ मुख्य थ्रेड पर बात करेंगे.

  • ऐसा इसलिए, क्योंकि Compose यहीं पर काम करता है.

  • हम Compose के बारे में बात कर रहे हैं. इसलिए, सिर्फ़ इस पर फ़ोकस करना सही है.

  • जब हम उस एक फ़्रेम को मुख्य थ्रेड पर अलग करते हैं, तो हमें Compose स्लाइस दिखने लगते हैं.

  • मैंने आपके लिए कुछ विकल्प चुने हैं.

  • इसमें कंपोज़िशन, मेज़र, लेआउट, और ड्रॉ जैसे टूल शामिल हैं.

  • अगर आपने Compose के फ़ेज़ के बारे में सुना है, तो आपको परफ़ॉर्मेंस की जांच करते समय ये फ़ेज़ दिख सकते हैं.

  • इसलिए, Perfetto ट्रेस में इनके अलग-अलग स्लाइस होते हैं. इससे यह पता लगाना बहुत आसान हो जाता है कि हर फ़ेज़ में कितना समय लगता है और हर फ़्रेम में कौनसे फ़ेज़ चलते हैं.

  • इसलिए, हमने इस काम को ऑप्टिमाइज़ करने के बारे में काफ़ी बात की है. साथ ही, आपको यह पक्का करना होगा कि सही समय पर सही काम किया जाए.

  • उदाहरण के लिए, अगर आपके पास बॉक्स के रंग को अपडेट करने वाला ऐनिमेशन या ऐसा ही कुछ है, तो आपको सिर्फ़ ड्रॉ फ़ेज़ को अमान्य करना होगा. कंपोज़िशन और लेआउट जैसी चीज़ों को अमान्य करने की ज़रूरत नहीं है.

  • यह परफ़ॉर्मेंस 101 है. आज हम कुछ चीज़ों के बारे में ज़्यादा गहराई से बात करेंगे. साथ ही, हम खास तौर पर कंपोज़िशन पर फ़ोकस करेंगे.

  • अब ऐंड्रू आपको इस बारे में बताएंगे.

  • धन्यवाद, आंद्रेई.

  • जैसा कि आंद्रेई ने बताया, हमने पहले परफ़ॉर्मेंस के बारे में बात करने में काफ़ी समय बिताया है. जैसे, बैकवर्ड रीड से बचना, यह पक्का करना कि कंपोज़िशन लाइफ़ साइकल में सही फ़ेज़ में स्टेट रीड को डिफ़र किया जाए, और ऑप्टिमाइज़ करना. साथ ही, यह पक्का करना कि आपके कंपोज़ेबल आपकी उम्मीद के मुताबिक अमान्य हो रहे हों, ताकि आपको हर फ़्रेम पर ज़्यादा काम न करना पड़े.

  • यह एक अच्छी शुरुआत है, लेकिन हमारे पास इस बारे में पहले से ही काफ़ी जानकारी है. हम इस बातचीत को थोड़ा आगे बढ़ाना चाहते हैं.

  • इसलिए, आज हम इस बारे में बात करेंगे कि उन ऑप्टिमाइज़ेशन को करने के बाद, कंपोज़िशन में क्या बचा है. साथ ही, यह भी जानेंगे कि आपके कंपोज़ेबल खुद क्या कर रहे हैं.

  • Compose कोड में मौजूद हर चीज़ और आपके ऐप्लिकेशन के साथ शिप की जाने वाली हर चीज़ की परफ़ॉर्मेंस के लिए, आपको कुछ न कुछ कीमत चुकानी पड़ती है.

  • आपके ऐप्लिकेशन में मौजूद कोई भी कोड मुफ़्त नहीं होता. यहां तक कि स्टेट को रीड करने और कंपोज़िशन में साइड इफ़ेक्ट जैसी चीज़ों को कंपोज़ करने जैसी मामूली चीज़ों के लिए भी कुछ कीमत चुकानी पड़ती है. इनके बारे में ध्यान रखना ज़रूरी है. खास तौर पर, तब जब आपको यह पता लगाना हो कि आपके कंपोज़ेबल असल में क्या कर रहे हैं और आपको अपने ऐप्लिकेशन की परफ़ॉर्मेंस को बेहतर बनाना हो.

  • यह दिखाने के लिए कि सबसे छोटे स्केल पर यह असल में कैसा दिखता है, यहां कुछ सबसे छोटे ऑपरेशन दिए गए हैं जिन्हें अपने कोड में कहीं भी किया जा सकता है. खास तौर पर, अपने कंपोज़ेबल के अंदर और इस रिज़ॉल्यूशन पर, हम नैनोसेकंड स्केल के बारे में बात कर रहे हैं. इसमें यह बताया गया है कि इन चीज़ों की कीमत कितनी है.

  • अगर आपको अपनी परफ़ॉर्मेंस को बेहतर बनाना है, तो आपके पास दो विकल्प हैं.

  • इसके लिए, कम खर्च वाले बहुत सारे ऑपरेशन या ज़्यादा खर्च वाले कुछ ऑपरेशन हटाए जा सकते हैं.

  • खास तौर पर इस चार्ट में, कोरूटीन को ज़्यादा महंगे ऑपरेशन के तौर पर दिखाया गया है. यह इस बात का अच्छा उदाहरण है कि हमें कहां से शुरुआत करनी चाहिए. ऐसा इसलिए, क्योंकि अगर हम इनमें से कुछ इंस्टेंस हटा दें, तो हमें अपने ऐप्लिकेशन में ज़्यादा फ़ायदे तेज़ी से दिख सकते हैं.

  • एक और बात जो आपको ध्यान में रखनी चाहिए, वह यह है कि जैसा कि मैंने स्लाइड में बताया है, कोरूटीन को लॉन्च करने के साथ-साथ उसे रद्द करने में भी खर्च आता है.

  • रद्द करने की लागत पर खास तौर पर ध्यान देना चाहिए, क्योंकि ऐसा मुख्य रूप से तब होता है, जब कॉन्टेंट को हटाया जा रहा हो और उसे आपके ऐप्लिकेशन के अंदर अन्य जगहों पर ले जाया जा रहा हो.

  • कई ऐप्लिकेशन में, नेविगेट करना बहुत महंगा होता है. इनमें आपको फ़्रेम टाइम का ज़्यादा से ज़्यादा फ़ायदा लेना होता है. साथ ही, यह पक्का करना होता है कि आपके पास जो कुछ भी है उसका ज़्यादा से ज़्यादा इस्तेमाल किया जा रहा हो.

  • इसलिए, इन दोनों लागतों का ध्यान रखना ज़रूरी है.

  • इन सभी को, चाहे हम कुछ भी देख रहे हों, इस स्केल पर जोड़ा जाएगा. इसमें करीब 1.6 करोड़ नैनोसेकंड लगेंगे.

  • इसलिए, हमारे पास यहां कुछ बजट है, जिसे हमें कम करना है. कोरूटीन, शुरुआत करने के लिए एक अच्छा उदाहरण है.

खराब असर को मैनेज करना

  • Compose में, कोरूटीन अक्सर इफ़ेक्ट एपीआई के साथ दिखते हैं.

  • यहां कई तरह के एपीआई मौजूद हैं. इनका इस्तेमाल SideEffects के साथ किया जा सकता है. इनमें ज़्यादा फ़ंक्शन वाले एपीआई से लेकर, इंतज़ार का समय कम करने वाले एपीआई तक शामिल हैं.

  • इस स्पेक्ट्रम के सबसे फ़ंक्शनल साइड में, हमारे पास LaunchedEffect है. इसमें एसिंक्रोनस काम के लिए सब कुछ शामिल है. खास तौर पर, ऐनिमेशन जैसी चीज़ें जिनके लिए आपको कोरूटीन स्कोप की ज़रूरत होती है.

  • अगर आपको इसकी ज़रूरत नहीं है और आपका काम कम कीमत वाले एपीआई से हो सकता है, तो आपको उसका इस्तेमाल करना चाहिए. हालांकि, इसके लिए ज़रूरी है कि वह आपके इस्तेमाल के उदाहरण के हिसाब से सही हो.

  • इसे बेहतर तरीके से समझाने के लिए, मैंने यहां एक मीडिया प्लेयर कंपोज़ेबल बनाया है. इससे सभी अलग-अलग SideEffects और उनके दिखने के समय के बारे में पता चलता है.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val mediaPlayer = rememberMediaPlayer()
    
        // Asynchronous work and animations
        LaunchedEffect(contentSource) { mediaPlayer.load(contentSource) }
    }
  • हम LaunchedEffect से शुरुआत कर सकते हैं.

  • इस उदाहरण में, इसका इस्तेमाल मीडिया प्लेयर में डेटा को एसिंक्रोनस तरीके से लोड करने के लिए किया जा रहा है.

  • अगर आपको कोरूटीन स्कोप की ज़रूरत है या सामान्य तौर पर कोरूटीन की ज़रूरत है या सस्पेंडिंग कॉल करना है, तो LaunchedEffect का इस्तेमाल करें.

  • हालांकि, यह अब भी ज़्यादा महंगा है और कोरूटीन से जुड़ी लागत भी है. जब तक इसका फ़ायदा मिल रहा है और यह सही है, तब तक काम के लिए सही एपीआई का इस्तेमाल करें.

  • हालांकि, अगर आपको लगता है कि LaunchedEffect का इस्तेमाल किया जा रहा है, लेकिन कोरूटीन का फ़ायदा नहीं मिल रहा है, तो ऐसे अन्य एपीआई इस्तेमाल किए जा सकते हैं जो कम कीमत में यही काम कर सकते हैं.

  • इसके लिए, स्पेक्ट्रम में नीचे की ओर जाने पर, हमें DisposableEffect जैसे खराब असर के अन्य उदाहरण दिखते हैं.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val activity = LocalActivity.current
    
        // Setup and teardown
        DisposableEffect(Unit) {
            activity.requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE
            onDispose {
                activity.requestedOrientation = SCREEN_ORIENTATION_UNSPECIFIED
            }
        }
    }
  • DisposableEffect का इस्तेमाल तब करना चाहिए, जब आपके इफ़ेक्ट के साथ सेटअप और टियरडाउन प्रोसेस जुड़ी हो.

  • यहां, मीडिया प्लेयर के उसी उदाहरण में, मेरे पास एक उदाहरण है जो कंपोज़ेबल मौजूद होने पर, गतिविधि के ओरिएंटेशन को लैंडस्केप पर लॉक कर देता है. इसके बाद, कुछ समय बाद कंपोज़ेबल के चले जाने पर, ओरिएंटेशन लॉक को हटा देता है.

  • अगर आपको LaunchedEffect और DisposableEffect का एक बार इस्तेमाल होने वाला इफ़ेक्ट चाहिए, लेकिन आपको कोरूटीन की ज़रूरत नहीं है और आपके पास डिसपोज़ इफ़ेक्ट नहीं है, तो Compose 1.12 में पेश किए गए नए SideEffect with keys API का भी इस्तेमाल किया जा सकता है.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val mediaPlayer = rememberMediaPlayer()
    
        // One-shot events
        SideEffect(Unit) { mediaPlayer.play() }
    }
  • अगर आपने अन्य दो एपीआई का इस्तेमाल किया है, तो यह उनके विकल्प के तौर पर बहुत अच्छा है. भले ही, आपने LaunchedEffect सुविधा के लिए या DisposableEffect वन-शॉट इफ़ेक्ट के लिए ऐसा किया हो. कुंजियों वाले एपीआई के साथ SideEffect पर नेविगेट या माइग्रेट करने से, कम ओवरहेड और बेहतर परफ़ॉर्मेंस के साथ वही काम किया जा सकता है. खास तौर पर, अगर पूरे ऐप्लिकेशन के दौरान कई बार माइग्रेट किया गया हो.

  • सबसे कम कीमत वाला एपीआई, SideEffect एपीआई है.

    @Composable
    fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
        val mediaPlayer = rememberMediaPlayer()
    
        // Synchronize the outside world to the state of composition
        SideEffect { mediaPlayer.playbackRate = playbackRate }
    }
  • यह तब सबसे सही होता है, जब आपको कंपोज़िशन में चल रही चीज़ों को बाहरी दुनिया की स्थिति के साथ सिंक करना हो.

  • मीडिया प्लेयर के उदाहरण में, मैंने एक और उदाहरण दिया है. इसमें कंपोज़िशन से मिलने वाले किसी पैरामीटर के साथ, कुछ प्लेबैक स्टेट को सिंक करने का तरीका बताया गया है.

  • एक बात का ध्यान रखें, अगर आपको साइड इफ़ेक्ट के इस्तेमाल का ऑडिट करना है और कम खर्च वाले इफ़ेक्ट पर माइग्रेट करना है, तो ध्यान रखें कि हमने जिन अन्य व्यवहारों और इस्तेमाल के उदाहरणों के बारे में बात की है उनके अलावा, साइड इफ़ेक्ट के साथ ऑर्डर करने की गारंटी में भी अंतर होता है.

  • सबसे पहले, सभी डिस्पोज़ेबल इफ़ेक्ट लागू होते हैं. इसके बाद, सामान्य साइड इफ़ेक्ट लागू होते हैं, चाहे वे कीड हों या नहीं. फ़्रेम पूरा होने के बाद, लॉन्च किए गए सभी इफ़ेक्ट लागू होते हैं, क्योंकि इसमें डिस्पैच शामिल होता है.

  • अगर आपके लॉन्च किए गए इफ़ेक्ट इस समयसीमा पर निर्भर हैं, तो माइग्रेट करने से पहले इस बात का ध्यान रखें.

  • किसी भी माइग्रेशन के लिए, अपने कोड की जांच ज़रूर करें. साथ ही, पक्का करें कि आप इनमें से किसी भी टाइमिंग गारंटी पर निर्भर न हों और आपका कोड बाद में भी काम करता रहे.

  • अगर साइड इफ़ेक्ट की जटिलता को कम नहीं किया जा सकता, तो साइड इफ़ेक्ट की संख्या को कम किया जा सकता है. इसके लिए, उन्हें एक साथ मर्ज करें.

    @Composable
    fun ShoppingCartScreen(
        viewModel: CartViewModel
    ) {
        LaunchedEffect(Unit) { viewModel.loadCart() }
        LaunchedEffect(Unit) { viewModel.loadPaymentMethods() }
        LaunchedEffect(Unit) { viewModel.loadDeliveryAddresses() }
    
        CartContent(...)
    }
  • अगर आपके पास एक ही कुंजी का इस्तेमाल करने वाले कई इफ़ेक्ट हैं, तो इस पर विचार करना खास तौर पर अच्छी बात है.

  • इन्हें मर्ज किया जा सकता है, क्योंकि इन्हें एक साथ चलाया जा सकता है.

  • इस उदाहरण में, मैंने कई LaunchedEffect इस्तेमाल किए हैं. इनमें से हर एक, किसी व्यू मॉडल में एसिंक्रोनस कॉल कर रहा है.

  • अगर मैं उन्हें एक LaunchedEffect से बदल दूं और फिर उसके अंदर अन्य लॉन्च कॉल करूं, भले ही मेरे पास अब भी उतने ही कोरूटीन चल रहे हों जितने यहां चल रहे हैं, फिर भी इसमें कम ओवरहेड होगा, क्योंकि यह कंपोज़िशन से बाहर निकलकर कोरूटीन में काम करता है.

    @Composable
    fun ShoppingCartScreen(
        viewModel: CartViewModel
    ) {
        LaunchedEffect(Unit) {
            launch { viewModel.loadCart() }
            launch { viewModel.loadPaymentMethods() }
            launch { viewModel.loadDeliveryAddresses() }
        }
    
        CartContent(...)
    }
  • इसलिए, ऐसी स्थितियों में इफ़ेक्ट को माइग्रेट करना एक अच्छा आइडिया है, ताकि इफ़ेक्ट की संख्या को कम किया जा सके.

  • इफ़ेक्ट और कोरूटीन, आपके ऐप्लिकेशन में ऑडिट की जा सकने वाली जगहों का सिर्फ़ एक उदाहरण हैं. हालांकि, हर कंपोज़ेबल और हर ऐप्लिकेशन अलग-अलग होगा और उनमें अलग-अलग अवसर होंगे.

  • आपके ऐप्लिकेशन के लिए क्या ज़रूरी है, यह समझने के लिए हमें उस अगले टूल के बारे में बात करनी होगी जिसे आंद्रेई पेश करने वाले हैं. यह टूल है कंपोज़िशन ट्रेसिंग.

कंपोज़िशन और रीकंपोज़िशन ट्रेसिंग

  • ठीक है।

  • इसलिए, कंपोज़िशन ट्रेसिंग, जैसा कि आपने Perfetto फ़्रेम के इस रीक्रिएशन में देखा है, जिसे हमने पहले एक अलग स्लाइड में देखा था. आपको दिखेगा कि यह हमें अलग-अलग फ़ेज़ के साथ समय की जानकारी देता है. हालांकि, यह हमें इस बारे में ज़्यादा जानकारी नहीं देता कि क्या हो रहा है.

  • क्या?

  • हां, कंपोज़िशन के अंदर क्या हो रहा है. इसमें यह भी पता चलता है कि ऐप्लिकेशन बनाने की कौनसी सुविधाएं इस्तेमाल की जा रही हैं.

  • कंपोज़िशन ट्रेसिंग से हमें इसमें मदद मिलती है.

  • इसलिए, हम Compose कंपाइलर में कुछ मार्कर जोड़ते हैं. ये मार्कर, हर फ़ंक्शन के बारे में जानकारी देते हैं और उसे Perfetto ट्रेस पर दिखाते हैं. इससे यह पता लगाया जा सकता है कि हर फ़ंक्शन को पूरा होने में कितना समय लगता है.

  • इसका इस्तेमाल करके, यह पता लगाया जा सकता है कि कौनसे फ़ंक्शन फिर से कंपोज़ हो रहे हैं.

  • हालांकि, इसमें थोड़ा ओवरहेड होता है. इसलिए, अगर आपको परफ़ॉर्मेंस का सबसे सटीक डेटा कैप्चर करना है, तो Perfetto का इस्तेमाल करें या Perfetto कैप्चर के बिना चलाएं और अन्य मेट्रिक का इस्तेमाल करें.

  • हालांकि, अगर आपको यह जानना है कि हर कंपोज़ेबल को रेंडर होने में कितना समय लगता है, तो कंपोज़िशन ट्रेसिंग काफ़ी काम की है.

  • कभी-कभी आपको यह भी जानना होता है कि चीज़ें फिर से क्यों कंपोज़ हो रही हैं. हम कंपोज़ ट्रेसिंग रनटाइम ट्रेसिंग की अगली रिलीज़, यानी कि 1.13 में इस समस्या को हल करने की कोशिश कर रहे हैं.

  • अब हम रीकंपोज़िशन ट्रेसिंग की सुविधा लॉन्च कर रहे हैं. यह सुविधा, हर स्टेट रीड और स्टेट राइट को एनोटेट करती है. इसकी वजह से, रीकंपोज़िशन होता है.

  • इसलिए, आपको Perfetto ट्रेस में कुछ ऐसा दिखेगा, जहां आपको हर स्टेट राइट और कॉल स्टैक दिखेंगे, जहां राइट और रीड होता है. इससे उन्हें अपने कोड की उन असल लाइनों से मैच किया जा सकता है जहां उन स्टेट को ऐक्सेस किया गया था या उनमें बदलाव किया गया था.

  • अगर आपको इस बारे में ज़्यादा जानना है, तो यहां दिए गए लिंक पर जाएं.

  • इसमें कंपोज़िशन ट्रेसिंग और रीकंपोज़िशन ट्रेसिंग, दोनों के बारे में पूरी जानकारी मौजूद है.

  • अब ऐंड्रयू आपको बताएंगे कि स्टेट राइट से बचने के लिए, इसका बेहतर तरीके से इस्तेमाल कैसे किया जा सकता है.

स्टेट ऑप्टिमाइज़ेशन और अमान्य करना

  • धन्यवाद, आंद्रेई.

  • इस जांच से मिले नतीजों के आधार पर, आपको पता चल सकता है कि आपके कंपोज़िशन में कुछ ऐसे इनवैलिडेशन हैं जो आपके ऐप्लिकेशन के लिए काम के नहीं हैं. साथ ही, इन्हें पहले ही टाला जा सकता था.

  • उदाहरण के लिए, सबसे आम उदाहरण यह है कि कुछ स्क्रोल टू टॉप बटन, विज़िबिलिटी या स्क्रोल प्रोग्रेशन पर निर्भर करते हैं.

    @Composable
    fun ScrollToTopButton(
        scrollState: ScrollState
    ) {
        val showScrollToTopButton = scrollState.value > 0
        val coroutineScope = rememberCoroutineScope()
    
        if (showScrollToTopButton) {
            FloatingActionButton(
                onClick = {
                    coroutineScope.launch { scrollState.animateScrollTo(0) }
                }
            ) {
                Icon(Icons.Default.ArrowUpward, "Scroll to Top")
            }
        }
    }
  • यहां, मैं कंपोज़िशन के अंदर स्क्रोलिंग स्टेट की वैल्यू पढ़ रहा हूं. यह बहुत बार बदलने वाली स्टेट है. यह हर फ़्रेम में एक बार बदल सकती है. इससे कई बार फिर से कंपोज़िशन करना पड़ सकता है. इससे बचा जा सकता है, क्योंकि भले ही स्क्रोल स्टेट खुद बदल रही हो, लेकिन असल शर्त जिसके बारे में हम यहां ध्यान देते हैं वह अक्सर नहीं बदलती.

  • यह एक उदाहरण है, जहां हम कंप्यूट किए गए स्टेट जैसी किसी चीज़ का इस्तेमाल करने का सुझाव देंगे. इससे, स्टेट में होने वाले उन बदलावों को अमान्य होने से बचाया जा सकता है जो आपके ऐप्लिकेशन के लिए मायने नहीं रखते.

  • इस उदाहरण में, मैंने कैलकुलेशन को कंप्यूट की गई स्थिति में रैप किया है. इस स्थिति को पढ़ने पर, स्क्रोल की पोज़िशन के आधार पर अमान्य करने के बजाय, यह कंपोज़ेबल को पूरी शर्त के आधार पर अमान्य कर देगा. साथ ही, इसे दूसरी स्थिति से ही हासिल करेगा.

    @Composable
    fun ScrollToTopButton(
        scrollState: ScrollState
    ) {
        val showScrollToTopButton by remember { computedStateOf { scrollState.value > 0 } }
        val coroutineScope = rememberCoroutineScope()
    
        if (showScrollToTopButton) {
            FloatingActionButton(
                onClick = {
                    coroutineScope.launch { scrollState.animateScrollTo(0) }
                }
            ) {
                Icon(Icons.Default.ArrowUpward, "Scroll to Top")
            }
        }
    }
  • अगर आपने यह पैटर्न पहले देखा है, तो हो सकता है कि आपने इसे डिराइव्ड स्टेट के साथ इस्तेमाल होते हुए देखा हो.

  • ComputedState, Compose 1.13 में नया है. ComputedState और DerivedState, दोनों का मकसद एक ही है. दोनों का मकसद, कैलकुलेशन में बदलाव होने पर, शर्त के हिसाब से अमान्य करना है. हालांकि, दोनों के काम करने के तरीके में थोड़ा अंतर है.

  • DerivedState, Compose की शुरुआत से ही उपलब्ध है. यह अपनी कंप्यूट की गई वैल्यू को कैश मेमोरी में सेव करता है और उसे तब तक फिर से इस्तेमाल करता है, जब तक कि उसकी डिपेंडेंसी नहीं बदल जातीं.

  • ComputedState का इस्तेमाल करने पर, हर बार वैल्यू को पढ़ने पर यह उसे कैलकुलेट करता है.

  • कैश मेमोरी का इस्तेमाल करना फ़ायदेमंद लग सकता है. हालांकि, कई मामलों में कैश मेमोरी का इस्तेमाल करना, नतीजे को पढ़ने से ज़्यादा महंगा होता है.

  • यह समझने के लिए कि ऐसा कैसे होता है, हम एक उदाहरण देख सकते हैं. इसमें बताया गया है कि आपके ऐप्लिकेशन में इन स्थितियों का इस्तेमाल कैसे किया जा सकता है. साथ ही, यह भी बताया गया है कि इनमें से हर स्थिति असल में क्या करती है.

  • यहां मेरे पास वही स्टेट है जो करीब-करीब वही काम करती है जो हम स्क्रोल करके सबसे ऊपर जाने वाले बटन में कर रहे थे. इसमें कुछ ऐसी शर्त है जो किसी दूसरी स्टेट पर आधारित है. साथ ही, मेरे पास यह DerivedState और ComputedState, दोनों के तौर पर है.

    var a by mutableStateOf(0)
    
    val derivedState = derivedStateOf { a > 0 }
    val computedState = computedStateOf { a > 0 }
    
    // First read
    derivedState.value
    computedState.value
    
    // Second read
    derivedState.value
    computedState.value
  • मैं स्टेट को दो बार पढ़ रहा हूं और हमें दोनों बार एक ही वैल्यू मिलेगी. हालांकि, दोनों के बीच जो कुछ होता है वह अलग होता है.

  • Perfetto जैसे टूल में असल में क्या दिखेगा, इसे आसानी से समझने के लिए, हम यह देख सकते हैं कि इन दोनों स्थितियों को पढ़ने पर असल में क्या होता है.

  • यहां हम computedState.value को कॉल करके शुरुआत करते हैं.

  • यह कैलकुलेशन लैम्डा को एक्ज़ीक्यूट करेगा. इसके बाद, यह स्टेट की वैल्यू को पढ़ेगा. साथ ही, यह सूचना देने के लिए थोड़ा सा रखरखाव भी करना होगा कि वैल्यू को पढ़ लिया गया है.

  • यह बात Compose में सभी स्नैपशॉट स्टेट पर लागू होगी.

  • दूसरी बार डेटा फ़ेच करने की प्रोसेस, पहली बार डेटा फ़ेच करने की प्रोसेस की तरह ही काम करती है. साथ ही, इसकी परफ़ॉर्मेंस की विशेषताएं भी एक जैसी होती हैं.

  • इसकी तुलना में, DerivedState को पहली बार पढ़ने पर भी ऐसा ही होता है. इसमें अब भी कैलकुलेशन लैम्ब्डा को कॉल करना पड़ता है और अन्य सूचनाएं भी देनी पड़ती हैं. हालांकि, इसमें कैश मेमोरी को मैनेज करने का थोड़ा ओवरहेड होता है. पहली बार में यह थोड़ा शॉर्ट-सर्किट होता है, क्योंकि जांच करने के लिए कोई कैश मेमोरी नहीं होती.

  • इसलिए, यह कैश मेमोरी को मैनेज करने के लिए थोड़ा समय लेता है. इसके बाद, यह उसी काम को थोड़ा ज़्यादा समय में पूरा करता है.

  • हालांकि, DerivedState के लिए दूसरी बार पढ़ने की प्रोसेस, ComputedState के लिए दूसरी बार पढ़ने की प्रोसेस से बेहतर होती है. भले ही, ये दोनों ही सामान्य स्थितियां हैं.

  • जैसा कि मैंने पिछली स्लाइड में बताया था, कुछ मामलों में स्नैपशॉट को रीड करने की लागत काफ़ी ज़्यादा होती है. साथ ही, कैश मेमोरी से रीड करना, सीधे रीड करने की तुलना में ज़्यादा तेज़ होता है. इसलिए, हमें DerivedState के लिए, दूसरी बार रीड करने पर थोड़ी सी बढ़त दिख रही है.

  • हालांकि, असल में दिलचस्प बात यह है कि जब स्टेट को अमान्य करना होता है, तब क्या होता है.

  • इसे रीड-आफ़्टर-राइट सिनेरियो कहा जाता है. इसमें, कैलकुलेशन में इस्तेमाल की जा रही किसी एक स्थिति को अपडेट किया जाता है और कैलकुलेशन को फिर से लागू करना पड़ता है.

  • कोड में यह कुछ ऐसा दिखता है: हम a में बदलाव करते हैं, शायद उसे बढ़ाते हैं, और फिर से वैल्यू को पढ़ते हैं या शायद फिर से कंपोज़ करते हैं.

    var a by mutableStateOf(0)
    
    val derivedState = derivedStateOf { a > 0 }
    val computedState = computedStateOf { a > 0 }
    
    ...
    
    // Read-after-write
    a++
    derivedState.value
    computedState.value
  • असल डायग्राम में, ComputedState के लिए भी यह बिलकुल वैसा ही दिखेगा.

  • इसमें मैनेज करने के लिए कोई कैश मेमोरी नहीं होती. यह बिलकुल वैसा ही दिखता है, जैसा कि पहली, दूसरी, तीसरी या जो भी रीडिंग है.

  • हालांकि, DerivedState के लिए यह काम आसान नहीं है.

  • यह कैश मेमोरी को अमान्य देखने के लिए बहुत महंगे पाथ से गुज़रता है. इसके बाद, यह अपडेट होता है और कैलकुलेशन को कॉल करता है. इसके बाद, इसे डिपेंडेंसी ट्रैकिंग करनी होती है और कैश मेमोरी की वैल्यू को फिर से लिखने के लिए, बहुत सारे डिपेंडेंट को मैनेज करना होता है. साथ ही, यह समझना होता है कि ये सभी वैल्यू असल में बदल गई हैं और रखरखाव को खुद मैनेज करना होता है.

  • इन सभी वजहों से, DerivedState को अमान्य करने की लागत, ComputedState की तुलना में बहुत ज़्यादा हो जाती है.

  • इन सभी दावों को आंकड़ों में बदलने के लिए, ComputedState आम तौर पर पहली बार पढ़ने पर करीब 200 नैनोसेकंड तेज़ होता है.

  • Pixel 6 पर, ComputedState को करीब 350 नैनोसेकंड का नुकसान होता है. खास तौर पर, मानदंड की कुछ खास शर्तों के तहत.

  • इसलिए, दूसरी बार पढ़ने पर, ComputedState की परफ़ॉर्मेंस DerivedState की तुलना में थोड़ी कम हो जाएगी.

  • हालांकि, जिस मामले में हम अमान्य कर रहे हैं और शायद जिसमें आपको अपने फ़्रेम पर काम करना है, उसमें ComputedState, DerivedState की तुलना में लगभग दोगुने से भी ज़्यादा तेज़ है.

  • इसलिए, इस बात के आधार पर कि स्टेट का इस्तेमाल किस लिए किया जा रहा है, ComputedState एक बहुत अच्छा विकल्प हो सकता है.

  • आम तौर पर, DerivedState और ComputedState का इस्तेमाल करते समय, अब भी फ़ैसला लेना बाकी है.

  • हम ComputedState को बंद नहीं कर रहे हैं. दोनों का इस्तेमाल अब भी किया जा सकता है.

  • आम तौर पर, अगर आपको यह नहीं पता है कि कौनसा विकल्प चुनना है, तो कंप्यूटेड स्टेट एक अच्छा डिफ़ॉल्ट विकल्प है.

  • डिराइव्ड स्टेट सबसे ज़्यादा तब काम आती है, जब आपको कैश मेमोरी का फ़ायदा मिलता है. इसका मतलब है कि आपको कोई मुश्किल एल्गोरिदम, बड़ा ऐलोकेशन, और स्टेट को कई बार पढ़ने जैसा कोई मुश्किल काम करना हो. इससे आपको कैश मेमोरी का फ़ायदा मिलता है. इसके अलावा, आपको कोई मुश्किल काम करना हो या ऑब्जेक्ट की समानता और ऑब्जेक्ट की पहचान की समानता जैसी चीज़ों पर निर्भर रहना हो, तो आपको अंतर पता चलता है और डिराइव्ड स्टेट से मिलने वाली कैश मेमोरी का फ़ायदा मिलता है.

Compose में स्किप करना और स्टेबिलिटी

  • एक और अहम बात जिसके बारे में मुझे बात करनी है, वह यह है कि डिराइव्ड स्टेट और कंप्यूटेड स्टेट, दोनों ही कंपोज़ेबल को अमान्य होने से बचाने की कोशिश करते हैं.

  • यह किसी कंपोज़ेबल को स्किप करने से अलग है. आम तौर पर, हम इसके बारे में तब बात करते हैं, जब हमें किसी शर्त के आधार पर अमान्य करना होता है या किसी कंपोज़ेबल के अंदर काम नहीं करना होता है.

  • हम यहां यह बताना चाहते हैं कि Compose में स्टेबिलिटी और स्किप करने के बारे में बहुत चर्चा होती है. हालांकि, यह सब कुछ नहीं है और इसे ऑप्टिमाइज़ करना हमेशा सही नहीं होता.

  • जैसा कि हमने पहले बताया था, कंपोज़िशन में हर चीज़ की कुछ न कुछ कीमत होती है. इसमें स्किप करना भी शामिल है.

    // What you write
    @Composable fun ContactListItem(
        contact: Contact
    ) { ... }
    
    // What you get
    @Composable fun ContactListItem(
        contact: Contact,
        $composer: Composer?
        $changed: Int
    ) {
        val $dirty = $changed or
            $composer.changed(contact)
    
        if ($dirty) { /* Your function body */ }
        else $composer.skipToGroupEnd()
    }
  • स्किप करना ज़रूरी नहीं है.

  • इस सुविधा को चालू करने के लिए, हमें काफ़ी रखरखाव करना पड़ता है.

  • हमें आपके सभी पैरामीटर सेव करने और शुरुआती कंपोज़िशन में उनके लिए जगह ढूंढने जैसे काम करने होंगे. इसके बाद, आपके फिर से कंपोज़ करने पर उस रिकॉर्ड को अपडेट करना होगा.

  • साथ ही, हमें आपकी वैल्यू की तुलना पिछली वैल्यू से करनी होगी, ताकि यह समझा जा सके कि आपके कंपोज़ेबल को स्किप करना चाहिए या नहीं.

  • इसके बाद, यह सब सिर्फ़ एक if चेक की मदद से होता है. यह चेक, कोड को स्किप करता है या नहीं.

  • यहां जो कुछ हो रहा है उसके हिसाब से, यह काफ़ी महंगा हो सकता है.

  • कंपाइलर के लिए, ऑप्टिमाइज़ेशन की एक सुविधा मौजूद है. इसके तहत, अगर एक ही पैरामीटर को पास किया जाता है, तो हम समानता की जांचों को फिर से इस्तेमाल कर सकते हैं. हालांकि, यह पूरी तरह से सही नहीं है और इसे आसानी से हराया जा सकता है.

  • इस तरह की गड़बड़ी का उदाहरण देखने के लिए, हम स्क्रोल की जा सकने वाली सूची का एक क्लासिक उदाहरण देख सकते हैं. इसमें एक बड़ा ऑब्जेक्ट होता है, जिसमें स्क्रीन पर दिखने वाला सारा डेटा मौजूद होता है.

    data class ContactsModel(
        val header: String,
        val contacts: List<Contact>
    )
    
    data class Contact(...)
    
    @Composable
    fun ContactsScreen(model: ContactsModel) {
        Column {
            TopAppBar(title = { Text(model.header) })
            ContactsList(model.contacts)
        }
    }
    
    @Composable
    fun ContactsList(contacts: List<Contact>) {
        LazyColumn {
            items(contacts) { ContactListItem(it) }
        }
    }
    
    @Composable
    fun ContactListItem(contact: Contact) { ... }
  • इसलिए, यहां स्क्रीन मॉडल में हेडर स्क्रीन जैसी कोई चीज़ होती है. इसके बाद, संपर्कों की पूरी सूची होती है. इसके बाद, यह सूची कुछ कंपोज़ेबल से होकर गुज़रती है. ये कंपोज़ेबल, लेज़ी लिस्ट को कॉल करते हैं और फिर उन्हें कुछ आइटम सेल के साथ स्क्रीन पर दिखाते हैं.

  • इस कोड के लिए कंपोज़िशन की हैरारकी कुछ इस तरह दिखती है. इसमें हम फिर से संपर्क स्क्रीन से शुरू करते हैं, थोड़ा सा फ़ैन आउट करते हैं, और फिर कुछ अन्य कंपोज़ेबल होते हैं जो हमारे आइटम को कॉल करते हैं और उन्हें दिखाते हैं.

  • अगर हम यह देखें कि हमारा डेटा कैसे पास होता है, तो हम सबसे ऊपर मौजूद मॉडल से शुरू करते हैं.

  • हम इसे एक स्ट्रिंग और फिर एक सूची में बांटते हैं. इसके बाद, वह सूची लेज़ी लिस्ट के लिए आइटम लैम्ब्डा में बदल जाती है. इसके बाद, लेज़ी लिस्ट स्क्रीन पर दिखने वाले सभी आइटम को कंपोज़ करेगी.

  • आइए देखते हैं कि इस सूची में मौजूद आखिरी संपर्क में बदलाव करने पर क्या होता है.

  • हम चाहते हैं कि सूची में मौजूद आखिरी आइटम को अमान्य कर दिया जाए. हालांकि, यहां ऐसा नहीं होगा.

  • अगर हम इस रीकंपोज़िशन के लिए कंपोज़िशन ट्रेस देखें, तो यह कुछ ऐसा दिखेगा. इसमें हम संपर्क स्क्रीन से शुरू करते हैं और फिर आखिर में इस सूची के आइटम तक पहुंचते हैं.

  • यह यहां तक पहुंचने का काफ़ी महंगा तरीका है.

  • हम यह भी देख सकते हैं कि यहां ऐसे कई सेक्शन हैं जहां ट्रेस में कुछ भी नहीं हो रहा है.

  • हालांकि, यहां यह साफ़ तौर पर नहीं बताया गया है, लेकिन असल में कंपोज़ेबल यहीं तय करते हैं कि उन्हें स्किप करना है या नहीं.

  • इस उदाहरण में, लेज़ी लिस्ट के फिर से कॉम्पोज़ होने के तरीके की वजह से, सिर्फ़ दो कॉम्पोज़ेबल को स्किप किया जाता है.

  • जब इसे नया कॉन्टेंट लैम्ब्डा दिया जाता है, तो यह अपने सभी आइटम को अमान्य कर देता है.

  • इसलिए, यह उन दो संपर्कों को अमान्य कर देगा जिनमें बदलाव नहीं हुआ है. साथ ही, उस संपर्क को भी अमान्य कर देगा जिसमें बदलाव हुआ है. ये दो ही ऐसे आइटम हैं जिन्हें स्किप किया जा सकता है.

  • बाकी सभी चीज़ें, बराबरी की तुलना को स्किप कर रही हैं. ऐसा इसलिए, क्योंकि उन्हें पता है कि उन्हें असल में कोड को एक्ज़ीक्यूट करना है.

  • अगर आपके ऑब्जेक्ट स्टेबल हैं, तो यह तुलना काफ़ी महंगी हो सकती है.

  • अगर हमें आपके सभी डेटा स्ट्रक्चर को देखने के लिए मजबूर किया जाता है, तो इससे असल में डेटा बढ़ सकता है. साथ ही, सिर्फ़ यह बताने के लिए कि दो ऑब्जेक्ट बराबर हैं, आपको काफ़ी खर्च करना पड़ सकता है.

  • इससे भी खराब बात यह है कि हमने डीस्ट्रक्चर किया है, इसलिए हमें सूची में दो बार एक ही तुलना करनी होगी.

  • अगर कंपाइलर को डिफ़ॉल्ट ऐक्शन और सेटिंग के साथ काम करने दिया जाए और स्ट्रॉन्ग स्किपिंग का इस्तेमाल किया जाए, तो भी स्किपिंग की सुविधा मिलती है. साथ ही, कंपोज़ेबल को शर्त के हिसाब से स्किप करने के लिए जांच की जा सकती है. हालांकि, ये स्ट्रक्चरल समानता के बजाय ऑब्जेक्ट आइडेंटिटी की समानता का इस्तेमाल करके, ज़्यादा तेज़ी से काम करते हैं.

  • इसकी मदद से, हम तेज़ी से कंप्यूट कर पाते हैं. भले ही, हम इस फ़्रेम पर एक जैसे आइटम फिर से कंपोज़ करते हैं, लेकिन जहां हमें तेज़ी से काम करना होता है वहां हम यह पता लगाने में ज़्यादा समय बर्बाद नहीं करते कि हम इन कंपोज़ेबल को स्किप नहीं कर सकते.

  • इसलिए, हम सीधे तौर पर रीकंपोज़िशन की संख्या के बजाय, बेंचमार्क और ट्रेस जैसे टूल इस्तेमाल करने पर ज़ोर देते हैं. इससे आपको यह समझने में मदद मिलती है कि आपके ऐप्लिकेशन में क्या हो रहा है.

  • ये मेट्रिक, प्रॉक्सी के तौर पर काम आ सकती हैं. हालांकि, अपने ऐप्लिकेशन को ऑप्टिमाइज़ करते समय, इन पर बहुत ज़्यादा ध्यान देने या इन्हें ज़रूरत से ज़्यादा इंडेक्स करने की ज़रूरत नहीं है.

  • याद रखें कि कंपोज़ेबल को स्किप करने के बजाय, उसे अमान्य होने से बचाएं.

तरीकों की सूची की मदद से, कोड के लागू होने का विश्लेषण करना

  • हमने इस उदाहरण में देखा कि कंपोज़िशन ट्रेस से, इस बारे में काफ़ी जानकारी मिल सकती है कि क्या हो रहा है. हालांकि, ज़रूरी नहीं है कि वे पूरी जानकारी दिखाएं.

  • इसके लिए, आंद्रेई हमें तरीकों की सूची के बारे में बताएंगे.

  • ठीक है।

  • जैसा कि ऐंड्रयू ने पहले ही बताया है कि कंपोज़िशन ट्रेसिंग से आपको हमेशा यह नहीं पता चलता कि क्या हो रहा है. ऐसा इसलिए, क्योंकि यह सिर्फ़ कंपोज़ेबल फ़ंक्शन के बारे में बताता है. कुछ मामलों में, आपको कंपोज़िशन ट्रेस में सिर्फ़ खाली जगह दिखती है, जो Perfetto ट्रेस को पहले से ही बेहतर बनाती है. हालांकि, यह अब भी वहां होने वाली हर चीज़ को नहीं दिखाता है.

  • कभी-कभी, ऐसा Compose की वजह से हो सकता है. इसके अलावा, आपके कोड में मौजूद किसी गड़बड़ी की वजह से भी ऐसा हो सकता है. यहां कंपोज़िशन ट्रेस को देखकर, इस बारे में पता लगाना काफ़ी मुश्किल है.

  • अब आपको तरीकों की सूची देखनी होगी.

  • Android Studio में प्रोफ़ाइलर में एक आइटम होता है. इसके अलावा, ट्रेस को कैप्चर करने के लिए एडीबी कमांड का भी इस्तेमाल किया जा सकता है. इससे आपको Java में होने वाले हर फ़ंक्शन कॉल की जानकारी मिलेगी.

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

  • एक बात और, मुझे इस आइटम को बीच में देखना है.

  • मैं आपको बताना चाहता हूं कि तरीकों की सूची बनाने में बहुत ज़्यादा ओवरहेड होता है.

  • आपके पास 10 से 20 गुना ज़्यादा ओवरहेड है. यह इस बात पर निर्भर करता है कि फ़ंक्शन ट्री कितना जटिल है, क्योंकि हर फ़ंक्शन कॉल में थोड़ा-थोड़ा ओवरहेड जुड़ता है.

  • इसे समझने के लिए, हम स्क्रीनशॉट देख सकते हैं.

  • ऐप्लिकेशन के शुरू होने पर, तरीकों की सूची आम तौर पर ऐसी दिखती है. आपके मन में यह सवाल आ सकता है कि "मैं इसे कैसे समझूं?"

  • आम तौर पर, कंपोज़िशन ट्रेस की शुरुआत करने के लिए, यहां मौजूद फ़ंक्शन देखे जाते हैं.

  • इसलिए, यह कंपोज़िशन ट्रेस का आसान वर्शन है. इसमें ScreenContent, कुछ बटन, और कुछ अन्य फ़ंक्शन शामिल हैं.

  • मान लें कि हमें यह पता लगाना है कि ScreenContent में क्या हो रहा है.

  • Perfetto में एक खोज बार होता है.

  • Perfetto व्यूअर में तरीकों की सूची लोड करें. इसके बाद, यहां मौजूद फ़ंक्शन को खोजें. जैसे, ScreenContent. इससे आपको स्क्रीन के लिए तरीकों की सूची मिल जाएगी.

  • यहां आपको Composer.startGroup और ग्रुप कॉल की सुविधा दिख रही होगी.

  • बैकग्राउंड में रनटाइम और कंपाइलर यही काम करते हैं, ताकि यह पक्का किया जा सके कि आपका कंपोज़िशन सही तरीके से काम करे. हालांकि, यहां दिलचस्प कॉल Composer.changed है.

  • इससे हमें यह पता चलता है कि कंपोज़ेबल के पिछले और मौजूदा इन्वोकेशन के बीच पैरामीटर बदले हैं या नहीं.

  • इनमें से ज़्यादातर Composer.changed, ScreenModel.equals से आते हैं.

  • इसलिए, हो सकता है कि ScreenModel कोई बिग डेटा क्लास हो और हमने इसे स्टेबल बनाया हो, ताकि रीकंपोज़िशन की संख्या में मदद मिल सके. इसलिए, हो सकता है कि यह कंपाइलर रिपोर्ट में दिखा हो.

  • इस मामले में, स्टेबल एनोटेशन को हटाने से परफ़ॉर्मेंस बेहतर हो सकती है.

  • इसलिए, आपको इस समस्या को ढूंढना होगा. इसके लिए, कंपोज़िशन के तरीकों की सूची सबसे अच्छा टूल है.

  • ठीक है।

  • आज हमने कई चीज़ों के बारे में जाना. अब मैं ऐंड्रू को बुलाना चाहूंगा, ताकि वे इस सेशन को खत्म कर सकें.

तेज़ी से कंपोज़ेबल डिज़ाइन और परफ़ॉर्मेंस टूल

  • आज हमने जो भी बातें की हैं उनकी खास जानकारी देने के लिए, यहां कुछ बातें बताई गई हैं. कंपोज़ेबल डिज़ाइन करते समय और अपने मौजूदा ऐप्लिकेशन को ऑप्टिमाइज़ करते समय, इन बातों का ध्यान रखें.

  • सबसे पहले, यह याद रखें कि कम कीमत वाले कंपोज़ेबल और ऐसे कंपोज़ेबल जिन्हें अमान्य नहीं किया जाता वे उन कंपोज़ेबल से बेहतर होते हैं जिन्हें स्किप किया जाता है.

  • याद रखें कि कंपोज़ेबल डिज़ाइन करते समय, ऐसे आर्ग्युमेंट इस्तेमाल करना बेहतर होता है जो छोटे हों और जिनकी तुलना करना आसान हो. साथ ही, ऑप्टिमाइज़ेशन का फ़ायदा पाने के लिए, अन्य फ़ंक्शन कॉल के साथ तुलनाओं का फिर से इस्तेमाल किया जा सकता है.

  • कंपोज़ेबल में एपीआई का इस्तेमाल करते समय, ध्यान रखें कि परफ़ॉर्मेंस या फ़्रेम टाइम, एपीआई की लागत और उनके इस्तेमाल की फ़्रीक्वेंसी पर निर्भर करता है.

  • इस बात का ध्यान रखें कि ये शुल्क कैसे जुड़ते हैं. साथ ही, यह भी ध्यान रखें कि इनसे जुड़े टियरडाउन शुल्क भी हो सकते हैं. ये शुल्क आपके ऐप्लिकेशन के लिए सबसे कम सही समय पर लग सकते हैं.

  • साथ ही, याद रखें कि अपने ऐप्लिकेशन में ऑप्टिमाइज़ेशन करते समय, एक अच्छा नागरिक होना ज़रूरी है.

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

  • यह भी ध्यान रखें कि आपके ऐप्लिकेशन में, रखरखाव और परफ़ॉर्मेंस के बीच संतुलन बनाना ज़रूरी है.

  • इसलिए, वह करें जो आपके और आपकी टीम के लिए सबसे सही हो. इससे यह पक्का किया जा सकेगा कि आपने ज़रूरत से ज़्यादा ऑप्टिमाइज़ेशन नहीं किया है और आपको रखरखाव में कोई परेशानी नहीं होगी.

  • ज़्यादा मदद पाने के लिए, आपके पास एक और टूल है. यह एआई एजेंट है, जो आपके पास पहले से मौजूद है.

  • हमने Android सीएलआई के हिस्से के तौर पर और अपनी स्किल के हिस्से के तौर पर, Jetpack Compose की परफ़ॉर्मेंस स्किल को लॉन्च किया है. इसमें वह ज़्यादातर जानकारी शामिल है जिसके बारे में हमने आज बात की है. यह आपके ऐप्लिकेशन की जांच और ऑडिट कर सकता है. साथ ही, इस तरह के ऑप्टिमाइज़ेशन के लिए सुझाव दे सकता है.

  • Android का सीएलआई इस्तेमाल करके, इसे अपने मौजूदा एजेंट में इंस्टॉल किया जा सकता है.

    android skills add jetpack-compose-performance
  • Android का सीएलआई के बारे में ज़्यादा जानने के लिए, developer.android.com पर जाएं.

  • हमारी GitHub रिपॉज़िटरी पर जाकर, स्किल के बारे में ज़्यादा जानकारी भी हासिल की जा सकती है.

  • इसके साथ ही, सुरक्षित तरीके से मानदंड तय करें और बेहतरीन तरीके से कंपोज़ करें.

कोड

कोड स्निपेट

"साइड इफ़ेक्ट मैनेज करना" में 09:34 पर Kotlin स्निपेट
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val mediaPlayer = rememberMediaPlayer()

    // Asynchronous work and animations
    LaunchedEffect(contentSource) { mediaPlayer.load(contentSource) }
}
"खराब असर मैनेज करना" में 10:15 पर Kotlin स्निपेट
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val activity = LocalActivity.current

    // Setup and teardown
    DisposableEffect(Unit) {
        activity.requestedOrientation = SCREEN_ORIENTATION_LANDSCAPE
        onDispose {
            activity.requestedOrientation = SCREEN_ORIENTATION_UNSPECIFIED
        }
    }
}
"साइड इफ़ेक्ट मैनेज करना" में 10:36 पर Kotlin स्निपेट
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val mediaPlayer = rememberMediaPlayer()

    // One-shot events
    SideEffect(Unit) { mediaPlayer.play() }
}
"साइड इफ़ेक्ट मैनेज करना" में 11:12 पर Kotlin स्निपेट
@Composable
fun MediaPlayer(contentSource: Uri, playbackRate: Float) {
    val mediaPlayer = rememberMediaPlayer()

    // Synchronize the outside world to the state of composition
    SideEffect { mediaPlayer.playbackRate = playbackRate }
}
"साइड इफ़ेक्ट मैनेज करना" में 12:12 पर Kotlin स्निपेट
@Composable
fun ShoppingCartScreen(
    viewModel: CartViewModel
) {
    LaunchedEffect(Unit) { viewModel.loadCart() }
    LaunchedEffect(Unit) { viewModel.loadPaymentMethods() }
    LaunchedEffect(Unit) { viewModel.loadDeliveryAddresses() }

    CartContent(...)
}
"साइड इफ़ेक्ट मैनेज करना" में 12:36 पर Kotlin स्निपेट
@Composable
fun ShoppingCartScreen(
    viewModel: CartViewModel
) {
    LaunchedEffect(Unit) {
        launch { viewModel.loadCart() }
        launch { viewModel.loadPaymentMethods() }
        launch { viewModel.loadDeliveryAddresses() }
    }

    CartContent(...)
}
"स्टेट ऑप्टिमाइज़ेशन और इनवैलिडेशन" में 15:12 पर Kotlin स्निपेट
@Composable
fun ScrollToTopButton(
    scrollState: ScrollState
) {
    val showScrollToTopButton = scrollState.value > 0
    val coroutineScope = rememberCoroutineScope()

    if (showScrollToTopButton) {
        FloatingActionButton(
            onClick = {
                coroutineScope.launch { scrollState.animateScrollTo(0) }
            }
        ) {
            Icon(Icons.Default.ArrowUpward, "Scroll to Top")
        }
    }
}
"स्टेट ऑप्टिमाइज़ेशन और इनवैलिडेशन" में 15:51 पर Kotlin स्निपेट
@Composable
fun ScrollToTopButton(
    scrollState: ScrollState
) {
    val showScrollToTopButton by remember { computedStateOf { scrollState.value > 0 } }
    val coroutineScope = rememberCoroutineScope()

    if (showScrollToTopButton) {
        FloatingActionButton(
            onClick = {
                coroutineScope.launch { scrollState.animateScrollTo(0) }
            }
        ) {
            Icon(Icons.Default.ArrowUpward, "Scroll to Top")
        }
    }
}
"स्टेट ऑप्टिमाइज़ेशन और इनवैलिडेशन" में 16:49 पर Kotlin स्निपेट
var a by mutableStateOf(0)

val derivedState = derivedStateOf { a > 0 }
val computedState = computedStateOf { a > 0 }

// First read
derivedState.value
computedState.value

// Second read
derivedState.value
computedState.value
"स्टेट ऑप्टिमाइज़ेशन और अमान्य करना" में 18:35 पर Kotlin स्निपेट
var a by mutableStateOf(0)

val derivedState = derivedStateOf { a > 0 }
val computedState = computedStateOf { a > 0 }

...

// Read-after-write
a++
derivedState.value
computedState.value
"Compose में स्किप करना और स्थिरता" में 21:07 पर Kotlin स्निपेट
// What you write
@Composable fun ContactListItem(
    contact: Contact
) { ... }

// What you get
@Composable fun ContactListItem(
    contact: Contact,
    $composer: Composer?
    $changed: Int
) {
    val $dirty = $changed or
        $composer.changed(contact)

    if ($dirty) { /* Your function body */ }
    else $composer.skipToGroupEnd()
}
"Compose में स्किप करना और स्टेबिलिटी" में 21:53 पर Kotlin स्निपेट
data class ContactsModel(
    val header: String,
    val contacts: List<Contact>
)

data class Contact(...)

@Composable
fun ContactsScreen(model: ContactsModel) {
    Column {
        TopAppBar(title = { Text(model.header) })
        ContactsList(model.contacts)
    }
}

@Composable
fun ContactsList(contacts: List<Contact>) {
    LazyColumn {
        items(contacts) { ContactListItem(it) }
    }
}

@Composable
fun ContactListItem(contact: Contact) { ... }
"तेज़ कंपोज़ेबल डिज़ाइन और परफ़ॉर्मेंस टूल" में 29:40 पर शेल स्निपेट
android skills add jetpack-compose-performance