प्रॉडक्ट से जुड़ी खबरें

R8 के कीप नियमों को कॉन्फ़िगर करना और उनसे जुड़ी समस्याओं को हल करना

पढ़ने में 7 मिनट लगेंगे
अजेश पाई की प्रोफ़ाइल देखें बेन वेइस की प्रोफ़ाइल देखें
Ajesh Pai & Ben Weiss

आजकल, Android ऐप्लिकेशन डेवलपमेंट में, छोटा, तेज़, और सुरक्षित ऐप्लिकेशन उपलब्ध कराना, उपयोगकर्ताओं की बुनियादी ज़रूरत है. Android के बिल्ड सिस्टम का मुख्य टूल R8  ऑप्टिमाइज़र है. यह कंपाइलर, इस्तेमाल न होने वाले कोड और रिसॉर्स को हटाने, कोड का नाम बदलने या उसे छोटा करने, और ऐप्लिकेशन को ऑप्टिमाइज़ करने का काम करता है.

R8 को चालू करना, ऐप्लिकेशन को रिलीज़ के लिए तैयार करने का एक अहम हिस्सा है. हालांकि, इसके लिए डेवलपर को "कीप नियमों" के ज़रिए निर्देश देने होते हैं.

यह लेख पढ़ने के बाद, YouTube पर परफ़ॉर्मेंस स्पॉटलाइट वीक का वीडियो देखें. इसमें R8 ऑप्टिमाइज़र को चालू करने, डीबग करने, और उससे जुड़ी समस्याओं को हल करने के बारे में बताया गया है.

 

 

कीप नियमों की ज़रूरत क्यों होती है

कीप नियम लिखने की ज़रूरत इसलिए पड़ती है, क्योंकि R8 एक स्टैटिक एनालिसिस टूल है. हालांकि, Android ऐप्लिकेशन अक्सर डाइनैमिक एक्ज़ीक्यूशन पैटर्न पर निर्भर करते हैं. जैसे, JNI (Java Native Interface) का इस्तेमाल करके, नेटिव कोड में रिफ़्लेक्शन या कॉल करना और नेटिव कोड से कॉल करना.

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

कीप नियम, R8 कंपाइलर को साफ़ तौर पर निर्देश देता है. इसमें कहा जाता है: "यह खास क्लास, तरीका या फ़ील्ड, एंट्री पॉइंट है. इसे रनटाइम में डाइनैमिक तरीके से ऐक्सेस किया जाएगा. आपको इसे सुरक्षित रखना होगा, भले ही आपको इसका डायरेक्ट रेफ़रंस न मिले."

कीप नियमों के बारे में ज़्यादा जानकारी के लिए, आधिकारिक गाइड देखें.

कीप नियम कहां लिखें

किसी ऐप्लिकेशन के लिए, कीप नियम टेक्स्ट फ़ाइल में लिखे जाते हैं. आम तौर पर, इस फ़ाइल का नाम proguard-rules.pro होता है. यह ऐप्लिकेशन या लाइब्रेरी मॉड्यूल के रूट में मौजूद होती है. इसके बाद, इस फ़ाइल को आपके मॉड्यूल की build.gradle.kts फ़ाइल के release बिल्ड टाइप में तय किया जाता है.

release {

    isShrinkResources = true

    isMinifyEnabled = true

    proguardFiles(

        getDefaultProguardFile("proguard-android-optimize.txt"),

        "proguard-rules.pro",

    )

}

सही डिफ़ॉल्ट फ़ाइल का इस्तेमाल करना

getDefaultProguardFile तरीका, Android SDK में दिए गए नियमों के डिफ़ॉल्ट सेट को इंपोर्ट करता है. गलत फ़ाइल का इस्तेमाल करने पर, हो सकता है कि आपका ऐप्लिकेशन ऑप्टिमाइज़ न हो. proguard-android-optimize.txt का इस्तेमाल करना न भूलें. इस फ़ाइल में, Android के स्टैंडर्ड कॉम्पोनेंट के लिए, कीप नियमों का डिफ़ॉल्ट सेट मौजूद होता है.और यह R8 के कोड ऑप्टिमाइज़ेशन को चालू करती है. पुरानी हो चुकी proguard-android.txt फ़ाइल में सिर्फ़ कीप नियम मौजूद होते हैं. इसमें R8 के ऑप्टिमाइज़ेशन चालू करने की सुविधा नहीं होती.

progaurd.png

यह परफ़ॉर्मेंस से जुड़ी एक गंभीर समस्या है. इसलिए, हम डेवलपर को गलत फ़ाइल का इस्तेमाल करने के बारे में चेतावनी दे रहे हैं. यह चेतावनी, Android Studio Narwhal 3 Feature Drop से शुरू हो रही है.Android Gradle प्लग इन के वर्शन 9.0 से, हम पुरानी हो चुकी proguard-android.txt फ़ाइल के लिए अब सहायता उपलब्ध नहीं कराएंगे. इसलिए, पक्का करें कि आपने ऑप्टिमाइज़ किए गए वर्शन पर अपग्रेड कर लिया हो.

कीप नियम लिखने का तरीका

कीप नियम के तीन मुख्य हिस्से होते हैं:

  1. कोई विकल्प , जैसे कि -keep या -keepclassmembers
  2. ज़रूरी नहीं है कि मॉडिफ़ायर शामिल हों, जैसे कि allowshrinking
  3. क्लास स्पेसिफ़िकेशन, जो मैच करने के लिए कोड तय करता है

पूरे सिंटैक्स और उदाहरणों के लिए, कीप नियम जोड़ने के बारे में जानकारी देखें.

कीप नियम के एंटी-पैटर्न

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

ग्लोबल ऑप्शन

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

-dontotptimize का इस्तेमाल करने से, R8 के परफ़ॉर्मेंस ऑप्टिमाइज़ेशन बंद हो जाते हैं. इससे ऐप्लिकेशन की स्पीड कम हो जाती है.

-dontobfuscate का इस्तेमाल करने पर, नाम बदलने की सभी सुविधाएं बंद हो जाती हैं. वहीं, -dontshrink का इस्तेमाल करने पर, इस्तेमाल न होने वाले कोड को हटाने की सुविधा बंद हो जाती है. इन दोनों ग्लोबल नियमों से, ऐप्लिकेशन का साइज़ बढ़ जाता है.

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

बहुत ज़्यादा ब्रॉड कीप नियम

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

-keep class com.example.package.** { *;} // WIDE KEEP RULES CAUSE PROBLEMS

इनवर्ज़न ऑपरेटर (!)

इनवर्ज़न ऑपरेटर (!), किसी नियम से किसी पैकेज को बाहर रखने का एक असरदार तरीका लगता है. हालांकि, यह इतना आसान नहीं है. यह उदाहरण देखें:

-keep class !com.example.my_package.** { *; } // USE WITH CAUTION

आपको लग सकता है कि इस नियम का मतलब है "`com.example.package` में मौजूद क्लास को सुरक्षित न रखेंcom.example.package." हालांकि, इसका मतलब है "पूरे ऐप्लिकेशन में मौजूद हर क्लास, तरीके, और प्रॉपर्टी को सुरक्षित रखें, जो com.example.package में नहीं है." अगर आपको यह जानकर हैरानी हुई है, तो R8 कॉन्फ़िगरेशन में, किसी भी नेगेशन की जांच करना सबसे अच्छा है.

Android कॉम्पोनेंट के लिए, ज़रूरत से ज़्यादा नियम

एक और आम गलती यह है कि लोग अपने ऐप्लिकेशन की ActivitiesServices या BroadcastReceivers के लिए, कीप नियम मैन्युअल तरीके से जोड़ते हैं. यह ज़रूरी नहीं है. डिफ़ॉल्ट proguard-android-optimize.txt फ़ाइल में, Android के इन स्टैंडर्ड कॉम्पोनेंट के लिए, काम के नियम पहले से शामिल होते हैं.

साथ ही, कई लाइब्रेरी में अपने कीप नियम होते हैं. इसलिए, आपको इनके लिए अपने नियम लिखने की ज़रूरत नहीं होती. अगर इस्तेमाल की जा रही किसी लाइब्रेरी के कीप नियमों में कोई समस्या है, तो समस्या के बारे में जानने के लिए, लाइब्रेरी के डेवलपर से संपर्क करना सबसे अच्छा है.

कीप नियम के सबसे सही तरीके

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

सटीक कीप नियम लिखना

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

नियमक्वालिटी

 

-keep class com.example.** { ; }

 

कम: इससे पूरा पैकेज और उसके सब-पैकेज सुरक्षित रहते हैं

 

-keep class com.example.MyClass { ; }

 

कम: इससे पूरी क्लास सुरक्षित रहती है. हालांकि, यह अब भी बहुत ब्रॉड है
-keepclassmembers class com.example.MyClass {

    private java.lang.String secretMessage;

    public void onNativeEvent(java.lang.String);

}
ज़्यादा: इससे किसी खास क्लास के सिर्फ़ काम के तरीके और प्रॉपर्टी सुरक्षित रहते हैं

कॉमन ऐनसेस्टर का इस्तेमाल करना

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

# Keep all fields of any class that implements SerializableModel

-keepclassmembers class * implements com.example.models.SerializableModel {

    <fields>;

}

एक से ज़्यादा क्लास को टारगेट करने के लिए, एनोटेशन का इस्तेमाल करना

कोई कस्टम एनोटेशन (जैसे, @Serialize) बनाएं और इसका इस्तेमाल, उन क्लास को "टैग" करने के लिए करें जिनके फ़ील्ड को सुरक्षित रखना है. यह एक और साफ़, डिक्लेरेटिव, और ज़्यादा से ज़्यादा क्लास के लिए काम करने वाला पैटर्न है. फ़्रेमवर्क में पहले से मौजूद एनोटेशन के लिए भी कीप नियम बनाए जा सकते हैं.

# Keep all fields of any class annotated with @Serialize

-keepclassmembers class * {

    @com.example.annotations.Serialize <fields>;

}

सही कीप ऑप्शन चुनना

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

कीप ऑप्शनयह क्या करता है
-keepइससे क्लास और डिक्लेरेशन में बताए गए कोड कॉम्पोनेंट को हटाया या उनका नाम बदला नहीं जा सकता.
-keepclassmembersइससे तय किए गए कोड कॉम्पोनेंट को हटाया या उनका नाम बदला नहीं जा सकता. हालांकि, क्लास को हटाया जा सकता है. ऐसा सिर्फ़ उन क्लास के लिए किया जा सकता है जिन्हें किसी और वजह से नहीं हटाया गया है.
-keepclasseswithmembersयह एक कॉम्बिनेशन है: इससे क्लास और उसके कोड कॉम्पोनेंट सुरक्षित रहते हैं. हालांकि, ऐसा सिर्फ़ तब होता है, जब तय किए गए सभी कोड कॉम्पोनेंट मौजूद हों.

कीप ऑप्शन के बारे में ज़्यादा जानने के लिए, कीप ऑप्शन के बारे में जानकारी के लिए हमारे दस्तावेज़ देखें.

मॉडिफ़ायर की मदद से ऑप्टिमाइज़ेशन की अनुमति देना

allowshrinking और allowobfuscation जैसे मॉडिफ़ायर, ब्रॉड -keep नियम को आसान बनाते हैं. इससे R8 को ऑप्टिमाइज़ेशन की सुविधा वापस मिल जाती है. उदाहरण के लिए, अगर कोई पुरानी लाइब्रेरी, आपको पूरी क्लास पर -keep का इस्तेमाल करने के लिए मजबूर करती है, तो सिकुड़ने और नाम बदलने की अनुमति देकर, कुछ ऑप्टिमाइज़ेशन वापस पाया जा सकता है:

# Keep this class, but allow R8 to remove it if it's unused and allow R8 to rename it.

-keep,allowshrinking,allowobfuscation class com.example.LegacyClass

अतिरिक्त ऑप्टिमाइज़ेशन के लिए ग्लोबल ऑप्शन जोड़ना

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

-repackageclasses एक असरदार ऑप्शन है. यह R8 को निर्देश देता है कि वह नाम बदली गई सभी क्लास को एक ही पैकेज में ले जाए. इससे, पैकेज के नाम वाली ज़रूरत से ज़्यादा स्ट्रिंग को हटाकर, DEX फ़ाइल में काफ़ी जगह बचाई जा सकती है.

-allowaccessmodification की मदद से, R8 को ऐक्सेस बढ़ाने (जैसे, private से public) की अनुमति मिलती है, ताकि ज़्यादा इनलाइनिंग की जा सके. proguard-android-optimize.txt का इस्तेमाल करने पर, यह सुविधा अब डिफ़ॉल्ट रूप से चालू होती है.

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

साथ ही, Android Gradle प्लग इन के वर्शन 9.0 में, हम लाइब्रेरी के ऑप्टिमाइज़ेशन के ग्लोबल फ़्लैग को पूरी तरह से अनदेखा करेंगे. 

लाइब्रेरी के लिए सबसे सही तरीके

हर Android ऐप्लिकेशन, किसी न किसी तरह से लाइब्रेरी पर निर्भर करता है. इसलिए, लाइब्रेरी के लिए सबसे सही तरीकों के बारे में बात करते हैं.

लाइब्रेरी डेवलपर के लिए

अगर आपकी लाइब्रेरी में रिफ़्लेक्शन या JNI का इस्तेमाल किया जाता है, तो आपको इसके उपभोक्ताओं को ज़रूरी कीप नियम उपलब्ध कराने की ज़िम्मेदारी लेनी होगी. ये नियम, consumer-rules.pro फ़ाइल में रखे जाते हैं. इसके बाद, यह फ़ाइल अपने-आप लाइब्रेरी की AAR फ़ाइल में बंडल हो जाती है.

android {

    defaultConfig {

        consumerProguardFiles("consumer-rules.pro")

    }

    ...

}

लाइब्रेरी के उपभोक्ताओं के लिए

समस्या पैदा करने वाले कीप नियमों को फ़िल्टर करना

अगर आपको ऐसी लाइब्रेरी का इस्तेमाल करना है जिसमें समस्या पैदा करने वाले कीप नियम शामिल हैं, तो AGP 9.0 से शुरू होने वाले वर्शन में, build.gradle.kts फ़ाइल में उन्हें फ़िल्टर किया जा सकता है. इससे R8 को, किसी खास डिपेंडेंसी से आने वाले नियमों को अनदेखा करने का निर्देश मिलता है.

release {

    optimization.keepRules {

        // Ignore all consumer rules from this specific library

        it.ignoreFrom("com.somelibrary:somelibrary")

    }

}

सबसे अच्छा कीप नियम, कोई कीप नियम न होना है

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

लाइब्रेरी चुनने के तरीके के बारे में ज़्यादा जानकारी के लिए, समझदारी से लाइब्रेरी चुनें लेख पढ़ें.

अपने R8 कॉन्फ़िगरेशन को डीबग करना और उससे जुड़ी समस्याओं को हल करना

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

डुप्लीकेट और ग्लोबल कीप नियम ढूंढना

R8, कई सोर्स से नियमों को मर्ज करता है. इसलिए, यह जानना मुश्किल हो सकता है कि "फ़ाइनल" नियम सेट क्या है. proguard-rules.pro फ़ाइल में यह फ़्लैग जोड़ने पर, पूरी रिपोर्ट जनरेट होती है:

# Outputs the final, merged set of rules to the specified file

-printconfiguration build/outputs/logs/configuration.txt

इस फ़ाइल में, ज़रूरत से ज़्यादा नियमों को ढूंढा जा सकता है. साथ ही, समस्या पैदा करने वाले नियम (जैसे, -dontoptimize) को उस खास लाइब्रेरी तक ट्रेस किया जा सकता है जिसमें वह शामिल था.

R8 से पूछना: आपने इसे सुरक्षित क्यों रखा है?

अगर आपकी उम्मीद के मुताबिक, कोई क्लास आपके ऐप्लिकेशन से नहीं हटाई गई है, तो R8 आपको इसकी वजह बता सकता है. बस यह नियम जोड़ें:

# Asks R8 to explain why it's keeping a specific class

class com.example.MyUnusedClass

-whyareyoukeeping 

बिल्ड के दौरान, R8, रेफ़रंस की उस सटीक चेन को प्रिंट करेगा जिसकी वजह से उसने उस क्लास को सुरक्षित रखा. इससे आपको रेफ़रंस को ट्रेस करने और अपने नियमों में बदलाव करने में मदद मिलेगी.

पूरी गाइड के लिए, R8 से जुड़ी समस्या हल करना सेक्शन देखें.

अगले चरण

R8, Android ऐप्लिकेशन की परफ़ॉर्मेंस को बेहतर बनाने का एक असरदार टूल है. इसकी परफ़ॉर्मेंस, स्टैटिक एनालिसिस इंजन के तौर पर इसके काम करने के तरीके को सही तरीके से समझने पर निर्भर करती है.

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

परफ़ॉर्मेंस स्पॉटलाइट वीक के दौरान, YouTube पर आज का स्पॉटलाइट वीक वीडियो देखना न भूलें. साथ ही, R8 की हमारी चुनौती में हिस्सा लें. R8 को चालू करने या उससे जुड़ी समस्याओं को हल करने के बारे में किसी भी सवाल के लिए, #optimizationEnabled का इस्तेमाल करें. हम यहां आपकी मदद करने के लिए हैं.

अब खुद देखें कि इससे क्या फ़ायदे मिलते हैं.

हम आपको चुनौती देते हैं कि आप अपने ऐप्लिकेशन के लिए, R8 का फ़ुल मोड आज ही चालू करें.

  1. शुरू करने के लिए, डेवलपर के लिए बनी हमारी गाइड देखें: ऐप्लिकेशन ऑप्टिमाइज़ेशन चालू करना.
  2. देखें कि अब भी proguard-android.txt का इस्तेमाल किया जा रहा है या नहीं. अगर किया जा रहा है, तो इसे proguard-android-optimize.txt से बदलें.
  3. इसके बाद, असर का अनुमान लगाएं. सिर्फ़ अंतर को महसूस न करें, बल्कि उसकी पुष्टि करें. स्टार्टअप के समय को पहले और बाद में मेज़र करने के लिए, हमारे GitHub पर मौजूद Macrobenchmark के सैंपल ऐप्लिकेशन से कोड को अपनाकर, परफ़ॉर्मेंस में हुए फ़ायदों को मेज़र करें.

हमें भरोसा है कि आपको अपने ऐप्लिकेशन की परफ़ॉर्मेंस में बेहतर नतीजे दिखेंगे.

इस दौरान, अपने सवाल पूछने के लिए, सोशल टैग #AskAndroid का इस्तेमाल करें. पूरे हफ़्ते, हमारे विशेषज्ञ आपके सवालों की निगरानी करते हैं और उनके जवाब देते हैं.

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

लेखक:
पढ़ना जारी रखें