अरे, ब्लूटूथ। वह तकनीक जिसे हम सभी प्यार से नफरत करते हैं। यह उस दोस्त की तरह है जो हमेशा बस कनेक्ट होने वाला होता है, लेकिन फिर... नहीं होता।
सालों से, एंड्रॉइड डेवलपर्स ब्लूटूथ के साथ एक नाटकीय, अक्सर दुखद, रोमांस में उलझे रहे हैं। हमने इसकी विचित्रताओं से संघर्ष किया है, इसे बस काम करने की विनती की है, और इसके रहस्यमय कनेक्शन ड्रॉप पर मौन आँसू बहाए हैं।
लेकिन क्या हो अगर मैं आपसे कहूं कि चीजें बेहतर होने वाली हैं? क्या हो अगर मैं आपसे कहूं कि एंड्रॉइड 16 के साथ, ब्लूटूथ के देवता आखिरकार हम पर मुस्कुराए हैं? यह कोई सपना नहीं, मेरे दोस्तों। यह AOSP 16 ब्लूटूथ स्कैनर है, और यह हमारे थके हुए डेवलपर आत्माओं में नई आशा लाने के लिए यहाँ है।
इस हैंडबुक में, हम एक यात्रा पर जा रहे हैं। AOSP 16 की नई ब्लूटूथ सुविधाओं के दिल में एक यात्रा। हम हँसेंगे, हम रोएंगे (उम्मीद है इस बार खुशी से), और सीखेंगे कि इन नई शक्तियों को भलाई के लिए कैसे उपयोग किया जाए। हम पैसिव स्कैनिंग के जादू, बॉन्ड लॉस कारणों के नाटक, और सर्विस UUID को बिना झंझट के प्राप्त करने की सुविधा का पता लगाएंगे।
इस महाकाव्य गाथा के अंत तक, आप सक्षम होंगे:
एक ऐसा ब्लूटूथ स्कैनर बनाएं जो इतना कुशल हो, कि वह व्यावहारिक रूप से मानसिक है।
एक अनुभवी जासूस की तरह कनेक्शन समस्याओं को डीबग करें।
अपने नए ब्लूटूथ मास्टरी से अपने दोस्तों और सहकर्मियों को प्रभावित करें।
पूर्वापेक्षाएँ:
गोता लगाने से पहले, एंड्रॉइड विकास और कोटलिन की बुनियादी समझ होना अच्छा है। अगर आपने कभी दो डिवाइस को आपस में बात कराने की कोशिश की है और अंत में अपने कंप्यूटर को खिड़की से बाहर फेंकना चाहा है, तो आप पूरी तरह से योग्य हैं।
तो अपना पसंदीदा पेय लें, अपनी कोडिंग केप पहनें, और ब्लूटूथ जागृति के लिए तैयार हो जाएं!
विषय सूची
एंड्रॉइड में ब्लूटूथ का संक्षिप्त इतिहास
AOSP 16 में क्या नया है: द थ्री मस्किटियर्स
डीप डाइव #1: पैसिव स्कैनिंग
BluetoothLeScanner को समझना
हैंड्स-ऑन: अपना पहला पैसिव स्कैनर बनाना
डीप डाइव #2: ब्लूटूथ बॉन्ड लॉस कारण
डीप डाइव #3: विज्ञापनों से सर्विस UUID
उन्नत विषय: अपने स्कैनिंग गेम को लेवल अप करना
वास्तविक दुनिया के उपयोग के मामले: जहां ब्लूटूथ सड़क से टकराता है
API संस्करण जांच: अपने ऐप को क्रैश न होने देना
परीक्षण और डीबगिंग: मजेदार हिस्सा (किसी ने कभी नहीं कहा)
प्रदर्शन और सर्वोत्तम अभ्यास: एक अच्छा ब्लूटूथ नागरिक कैसे बनें
निष्कर्ष: भविष्य पैसिव है (और यह ठीक है)
ब्लूटूथ का संक्षिप्त इतिहास (या: कैसे हमने चिंता करना बंद किया और रेडियो तरंगों से प्यार करना सीखा)
अंधेरे युग: क्लासिक ब्लूटूथ
शुरुआत में, क्लासिक ब्लूटूथ था। यह एक जोरदार, उधम मचाने वाले पार्टी मेहमान का डिजिटल समकक्ष था। यह बहुत सारा डेटा ले जा सकता था (जैसे आपके पसंदीदा गाने स्पीकर तक), लेकिन यह बैटरी का बहुत बड़ा खाने वाला था। यह ऑडियो स्ट्रीमिंग के लिए बहुत अच्छा था, लेकिन छोटे, कभी-कभार डेटा ट्रांसफर के लिए? यह हाउसप्लांट को पानी देने के लिए फायर होज़ का उपयोग करने जैसा था। ओवरकिल, और स्पष्ट रूप से, थोड़ा गड़बड़।
इस युग के डेवलपर्स अपने दिन BluetoothAdapter, BluetoothDevice, और खतरनाक BluetoothSocket से कुश्ती में बिताते थे। यह बड़ी अनिश्चितता का समय था, जहां एक साधारण कनेक्शन में सेकंड लग सकते थे, या... खैर, बस इतना समझ लें कि आप कॉफी का एक कप बनाने जा सकते थे। और बैटरी ड्रेन? आपके यूजर्स अपने फोन की पावर लेवल को लेड बैलून से भी तेजी से गिरते हुए देखेंगे।
पुनर्जागरण: ब्लूटूथ लो एनर्जी (BLE) का प्रवेश
फिर, एंड्रॉइड 4.3 के साथ, एक नया नायक उभरा: ब्लूटूथ लो एनर्जी, या BLE। यह आपके पापा का ब्लूटूथ नहीं था। BLE चिकना, कुशल, और रहस्यमय था। यह डेटा के छोटे विस्फोटों के लिए डिज़ाइन किया गया था, पावर को चुस्कियों में पीता था बजाय इसे गटकने के।
BLE ब्लॉक का कूल किड था। इसने हमें संभावनाओं की एक पूरी नई दुनिया से परिचित कराया: हार्ट-रेट मॉनिटर, स्मार्ट वॉच, और दस लाख एक IoT डिवाइस जो एक सिंगल कॉइन-सेल बैटरी पर महीनों चल सकते थे। यह एक गेम-चेंजर था।
लेकिन बड़ी शक्ति के साथ... बड़ी जटिलता आई। हमें GATT, GAP, सर्विसेज, और कैरेक्टरिस्टिक्स की एक पूरी नई भाषा सीखनी पड़ी। यह सरल स्क्रिप्ट लिखने से लेकर एक पूर्ण विकसित ओपेरा रचने जैसा था। क्षमता बहुत बड़ी थी, लेकिन सीखने की अवस्था खड़ी थी।
समस्या बच्चा: स्कैनिंग
और फिर थी स्कैनिंग। इन नए, पावर-सिपिंग डिवाइस को खोजने का कार्य। BLE के शुरुआती दिनों में, स्कैनिंग अभी भी थोड़ा वाइल्ड वेस्ट था। यह एक सक्रिय, शोर भरी प्रक्रिया थी। आपका फोन शून्य में चिल्लाता था, "क्या कोई बाहर है?", और फिर जवाब सुनता था। यह काम करता था, लेकिन फिर भी यह एक महत्वपूर्ण पावर ड्रेन था, खासकर अगर आपके ऐप को लंबे समय तक स्कैन करने की आवश्यकता होती थी।
यह क्लासिक डेवलपर दुविधा थी: आपको डिवाइस खोजने की ज़रूरत है, लेकिन आप यह नहीं चाहते कि आपके यूजर का फोन दोपहर तक डेड हो जाए। सालों से, हम इस तनी हुई रस्सी पर चलते रहे, खोज की आवश्यकता को बैटरी लाइफ की बेताब याचना के साथ संतुलित करते हुए।
यही वह दुनिया है जिसमें AOSP 16 का जन्म हुआ। एक दुनिया जो स्कैन करने के बेहतर तरीके के लिए रो रही थी। एक दुनिया एक नायक के लिए तैयार थी। और वह नायक, मेरे दोस्तों, पैसिव स्कैनिंग है। लेकिन इस पर थोड़ी देर में...
AOSP 16 में क्या नया है? (स्पॉइलर: यह वास्तव में कूल है)
ठीक है, अच्छी चीजों पर आते हैं। एंड्रॉइड टीम ने हमें AOSP 16 में क्या चमकदार नए खिलौने दिए? पता चला, काफी कुछ! लेकिन इससे पहले कि हम उपहारों को खोलें, आइए नए डिलीवरी शेड्यूल के बारे में बात करते हैं, क्योंकि यह भी अब थोड़ा अलग है।
दो रिलीज़ की कहानी
एक चौंकाने वाले प्लॉट ट्विस्ट में, एंड्रॉइड ने हमें 2025 में दो प्रमुख API रिलीज़ से नवाज़ा। पहले, हमें मुख्य कार्यक्रम मिला, एंड्रॉइड 16 (कोडनेम "बकलावा," क्योंकि किसे अच्छा पेस्ट्री पसंद नहीं?), जो Q2 में आया। यह आपका पारंपरिक, बड़ा धमाका रिलीज़ है जिसमें वे सभी व्यवहार परिवर्तन हैं जिन्हें आप जानते और प्यार करते हैं (या डरते हैं)।
लेकिन फिर, Q4 में, हमें एक सरप्राइज़ दूसरा एक्ट मिलता है: एक माइनर रिलीज़, जहां हमारे नए ब्लूटूथ गुडीज़ ने अपनी भव्य प्रविष्टि की। यह रिलीज़ नई सुविधाओं और API के बारे में है, बिना डरावने, ऐप-तोड़ने वाले परिवर्तनों के। यह बिल चुकाने के बाद मुफ्त मिठाई मिलने जैसा है।
ब्लूटूथ के थ्री मस्किटियर्स
तो, इस Q4 रिलीज़ ने ब्लूटूथ पार्टी में क्या लाया? मुझे खुशी है कि आपने पूछा। इसने तीन नए नायक लाए, जो हमें हमारी ब्लूटूथ परेशानियों से बचाने के लिए तैयार हैं। मैं उन्हें बुलाता हूँ... द थ्री मस्किटियर्स।
| फीचर | सार | आपको क्यों परवाह करनी चाहिए |
|---|---|---|
| पैसिव स्कैनिंग | उन पर चिल्लाए बिना ब्लूटूथ डिवाइस को सुनने की क्षमता। | आपका ऐप अब एक साइलेंट, बैटरी-बचाने वाला निंजा हो सकता है। |
| बॉन्ड लॉस कारण | आखिरकार, इस बात का पता कि आपके ब्लूटूथ कनेक्शन क्यों टूटते हैं। | आप अनुमान लगाने का खेल बंद कर सकते हैं और वास्तव में कनेक्शन समस्याओं को डीबग कर सकते हैं। |
| विज्ञापनों से सर्विस UUID | डिवाइस के महत्वपूर्ण आँकड़े सीधे उसके विज्ञापन से प्राप्त करें। | यह ब्लूटूथ डिवाइस के लिए स्पीड डेटिंग जैसा है। तेज़, अधिक कुशल कनेक्शन। |
ये सिर्फ मामूली बदलाव नहीं हैं, दोस्तों। ये जीवन की गुणवत्ता में सुधार हैं जो मौलिक रूप से बदल देंगे कि हम ब्लूटूथ-सक्षम ऐप कैसे बनाते और डीबग करते हैं। ऐसा लगता है मानो एंड्रॉइड टीम ने वास्तव में हमारी सामूहिक मदद की पुकार सुनी हो। (मुझे पता है, मुझे भी झटका लगा।)
अगले कुछ सेक्शन में, हम इनमें से प्रत्येक नई सुविधा के साथ करीब और व्यक्तिगत होने जा रहे हैं। हम कोड में गोता लगाएंगे, उपयोग के मामलों का पता लगाएंगे, और सीखेंगे कि उनकी शक्ति का उपयोग कैसे करें। तो, हमारे पहले मस्किटियर से मिलने के लिए तैयार हो जाइए: मजबूत, चुप प्रकार जिसे पैसिव स्कैनिंग के नाम से जाना जाता है।
डीप डाइव #1: पैसिव स्कैनिंग
कल्पना कीजिए कि आप एक लाइब्रेरी में हैं। आप एक दोस्त की तलाश कर रहे हैं, लेकिन आप नहीं जानते कि वे कहां हैं। आपके पास दो विकल्प हैं:
एक्टिव स्कैनिंग: आप लाइब्रेरी के बीच में खड़े होकर चिल्लाते हैं, "अरे, स्टीव! क्या तुम यहाँ हो?" यह प्रभावी है, लेकिन यह जोरदार, विघटनकारी भी है, और आपको लाइब्रेरियन द्वारा बाहर निकाल दिया जाएगा (जो इस अनुमान में, आपके यूजर की बैटरी है)।
पैसिव स्कैनिंग: आप चुपचाप लाइब्रेरी में घूमते हैं, अपने दोस्त की विशिष्ट, घरघराहट भरी हंसी सुनते हैं। आप एक शब्द नहीं कहते। आप बस सुनते हैं। यह चुपके भरा, कुशल है, और आपकी सामाजिक (या वास्तविक) बैटरी को खत्म नहीं करेगा।
सालों से, एंड्रॉइड की ब्लूटूथ स्कैनिंग लाइब्रेरी में चिल्लाने वाला वही आदमी रही है। लेकिन AOSP 16 के साथ, हम आखिरकार चुप श्रोता बन सकते हैं। यही पैसिव स्कैनिंग का जादू है।
एक्टिव बनाम पैसिव: तकनीकी तसलीम
BLE की दुनिया में, डिवाइस सूचना के छोटे पैकेट भेजते हैं जिन्हें "विज्ञापन" कहा जाता है। यह उनका तरीका है कहने का, "अरे, मैं यहाँ हूँ, और यह मैं करता हूँ!"
एक्टिव स्कैनिंग: जब आपका फोन एक्टिव स्कैन करता है, तो वह एक विज्ञापन सुनता है और फिर एक SCAN_REQ (स्कैन अनुरोध) वापस भेजता है। यह मूल रूप से कह रहा है, "मुझे और बताओ!" पेरिफेरल डिवाइस तब एक SCAN_RSP (स्कैन रिस्पांस) के साथ जवाब देता है, जिसमें अतिरिक्त जानकारी होती है।
पैसिव स्कैनिंग: पैसिव स्कैनिंग के साथ, आपका फोन विज्ञापन सुनता है... और बस। यह कुछ भी वापस नहीं भेजता। यह सिर्फ प्रारंभिक विज्ञापन पर ध्यान देता है और आगे बढ़ जाता है। यह एकतरफा बातचीत है।
पैसिव क्यों जाएं? मौन की शक्ति
तो, यह इतना बड़ा सौदा क्यों है? दो शब्द: पावर खपत। हर बार जब आपके फोन के रेडियो को कुछ ट्रांसमिट करना होता है (जैसे SCAN_REQ), तो यह ऊर्जा का उपयोग करता है। अगर आपका ऐप हर समय डिवाइस के लिए स्कैन कर रहा है, तो वे छोटे ट्रांसमिशन जुड़ जाते हैं, और आपके यूजर की बैटरी कीमत चुकाती है।
पैसिव स्कैनिंग पर स्विच करके, आप रेडियो को सिर्फ सुनने के लिए कह रहे हैं। कोई बात नहीं, बस सुनना। यह स्कैनिंग के लिए आवश्यक पावर को नाटकीय रूप से कम कर देता है, जिससे यह उन ऐप्स के लिए एक आदर्श समाधान बन जाता है जिन्हें लंबी अवधि में आस-पास के डिवाइस की निगरानी करने की आवश्यकता होती है।
कोड: ब्लूटूथ निंजा कैसे बनें
तो, हम इस नए चुपके मोड को कैसे लागू करते हैं? यह आश्चर्यजनक रूप से सरल है। यह सब उस ScanSettings पर निर्भर करता है जिसका उपयोग आप अपना स्कैन शुरू करते समय करते हैं।
पहले, आप कुछ ऐसा कर सकते थे:
val settings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) .build() अब, AOSP 16 के साथ, हमारे पास एक नया विकल्प है। पैसिव स्कैनिंग सक्षम करने के लिए, आप बस स्कैन प्रकार सेट करते हैं:
// यह जादुई लाइन है! .setScanMode(ScanSettings.SCAN_TYPE_PASSIVE) रुकिए, यह सही नहीं हो सकता। डॉक्यूमेंटेशन कहता है कि SCAN_TYPE_PASSIVE एक स्कैन प्रकार है, स्कैन मोड नहीं। और आप सही हैं! मेरी माफी, मैं थोड़ा ज्यादा उत्साहित हो गया। इसे करने का सही तरीका स्कैन मोड को पैसिव पर सेट करना है। आइए फिर से कोशिश करते हैं।
val settings = ScanSettings.Builder() // वास्तविक जादुई लाइन! .setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC) // यह पैसिव के सबसे करीब है .build() अरे, यह भी पूरी तरह सही नहीं है। लगता है मैंने अपने तार क्रॉस कर लिए हैं। आइए आधिकारिक स्क्रॉल देखें... अरे, यह रहा! Android 16 QPR2 में ScanSettings.Builder में एक नया मेथड है। यह setScanMode नहीं है, यह एक पूरी नई सेटिंग है।
आइए इसे एक बार और सभी के लिए सही करें। पैसिव स्कैनिंग सक्षम करने का सही तरीका यहाँ है:
// Android 16 QPR2 और बाद के संस्करणों में उपलब्ध val settings = ScanSettings.Builder() // यह असली जादुई लाइन है, वादा करता हूँ! .setScanType(ScanSettings.SCAN_TYPE_PASSIVE) .build() और बस। उस एक लाइन के साथ, आपने अपने ऐप को एक जोरदार, बैटरी-गज़लिंग टूरिस्ट से एक साइलेंट, कुशल ब्लूटूथ निंजा में बदल दिया है। आपके यूजर्स की बैटरी आपको धन्यवाद देगी।
बेशक, एक ट्रेड-ऑफ है। चूंकि आप SCAN_REQ नहीं भेज रहे हैं, आपको SCAN_RSP से अतिरिक्त डेटा नहीं मिलेगा। लेकिन कई उपयोग के मामलों के लिए, प्रारंभिक विज्ञापन ही आपकी ज़रूरत की हर चीज है। और पावर सेविंग इसके लायक से कहीं अधिक है।
अब जब हमने साइलेंट स्कैनिंग की कला में महारत हासिल कर ली है, तो आइए पहेली के अगले टुकड़े पर चलते हैं: BluetoothLeScanner को समझना।
BluetoothLeScanner को समझना (हमारे शो का सितारा)
इससे पहले कि हम ब्लूटूथ स्कैनिंग की कला में वास्तव में महारत हासिल कर सकें, हमें पहले अपने प्राथमिक हथियार को समझना होगा: BluetoothLeScanner। इसे घोस्टबस्टर्स के PKE मीटर के रूप में सोचें। यह वह उपकरण है जिसका उपयोग हम अपने चारों ओर तैर रही अदृश्य ऊर्जा (हमारे मामले में, BLE विज्ञापन) का पता लगाने के लिए करते हैं। लेकिन यह भूत-शिकार गैजेट वास्तव में कैसे काम करता है?
आर्किटेक्चर: पर्दे के पीछे एक झलक
उच्च स्तर पर, प्रक्रिया काफी सीधी है। आपका ऐप, अपनी छोटी सी दुनिया में आराम से रहता हुआ, तय करता है कि उसे कुछ BLE डिवाइस ढूंढने हैं। यह BluetoothLeScanner का एक इंस्टेंस लेता है और कहता है, "अरे, जाओ कुछ ढूंढो।"
हुड के नीचे, बहुत कुछ हो रहा है। BluetoothLeScanner एंड्रॉइड ब्लूटूथ स्टैक (कोडनेम "फ्लोराइड," जो ऐसा लगता है जैसे आपका डेंटिस्ट इस पर बहुत गर्व करेगा) से बात करता है। स्टैक तब डिवाइस के ब्लूटूथ कंट्रोलर, वास्तविक हार्डवेयर जो रेडियो तरंगों को भेजने और प्राप्त करने का काम करता है, के साथ संचार करता है। यह "यह जितना दिखता है उससे कहीं अधिक जटिल है" का एक क्लासिक मामला है।
वर्णमाला का सूप: GATT, GAP, और दोस्त
जब आप BLE की दुनिया में कदम रखते हैं, तो आप जल्दी से ढेर सारे संक्षिप्त नामों से टकराएंगे। घबराएं नहीं! वे उतने डरावने नहीं हैं जितने दिखते हैं। समझने के लिए दो सबसे महत्वपूर्ण हैं GAP और GATT।
GAP (जेनेरिक एक्सेस प्रोफाइल): यह सब इस बारे में है कि डिवाइस एक-दूसरे को कैसे खोजते और कनेक्ट करते हैं। GAP को नाइटक्लब के बाउंसर के रूप में सोचें। यह तय करता है कि कौन किससे बात कर सकता है। यह विज्ञापन (डिवाइस चिल्ला रहा है "मैं यहाँ हूँ!") और स्कैनिंग (आपका ऐप उन चिल्लाहट को सुन रहा है) का प्रबंधन करता है। हमारा BluetoothLeScanner GAP-वर्स में एक प्रमुख खिलाड़ी है।
GATT (जेनेरिक एट्रिब्यूट प्रोफाइल): एक बार जब दो डिवाइस कनेक्ट हो जाते हैं, तो GATT कार्यभार संभाल लेता है। यह परिभाषित करता है कि वे डेटा का आदान-प्रदान कैसे करते हैं। GATT को नाइटक्लब के अंदर होने वाली वास्तविक बातचीत के रूप में सोचें। यह सब सर्विसेज, कैरेक्टरिस्टिक्स, और डिस्क्रिप्टर्स के बारे में है। एक डिवाइस में "हार्ट रेट सर्विस" हो सकती है, जिसमें "हार्ट रेट मेजरमेंट कैरेक्टरिस्टिक" होती है। आपका ऐप इन कैरेक्टरिस्टिक्स से पढ़ता या लिखता है ताकि उसे आवश्यक डेटा मिल सके।
स्कैनिंग के उद्देश्य के लिए, हम ज्यादातर GAP की दुनिया में रह रहे हैं। हम क्लब के बाहर खड़े हैं, दिलचस्प विज्ञापनों को सुन रहे हैं।
स्कैनिंग जीवनचक्र: तीन अंकों में एक नाटकीय नाटक
ब्लूटूथ स्कैन का जीवन एक सरल, फिर भी सुंदर, नाटक है।
अंक I: तैयारी। आपका ऐप तय करता है कि स्कैन करने का समय आ गया है। यह BluetoothLeScanner प्राप्त करता है, ScanFilters का एक सेट बनाता है (केवल विशिष्ट डिवाइस खोजने के लिए) और ScanSettings (यह परिभाषित करने के लिए कि कैसे स्कैन करना है, जैसे हमारा नया पैसिव मोड), और एक ScanCallback परिभाषित करता है।
अंक II: स्कैन। आपका ऐप startScan() कॉल करता है। ब्लूटूथ रेडियो जीवन में आता है, आपके फिल्टर से मेल खाने वाले विज्ञापनों को सुनता है। जब यह एक ढूंढता है, तो यह आपके ScanCallback में onScanResult() मेथड के माध्यम से आपके ऐप को रिपोर्ट करता है।
अंक III: अंत। जब आपके ऐप को पर्याप्त हो जाता है (या, अधिक महत्वपूर्ण रूप से, जब आपको वह मिल जाता है जिसकी आप तलाश कर रहे थे), तो यह stopScan() कॉल करता है। रेडियो पावर डाउन हो जाता है, और एक बार फिर सब शांत हो जाता है। जब आपका काम हो जाए तो हमेशा अपना स्कैन रोकना महत्वपूर्ण है। एक दुष्ट स्कैन "मेरी बैटरी एक घंटे में खत्म हो जाती है" यूजर शिकायतों का नंबर एक कारण है।
और यह है BluetoothLeScanner संक्षेप में। यह BLE खोज की दुनिया का हमारा प्रवेश द्वार है। यह शक्तिशाली है, यह जटिल है, लेकिन जैसा कि हम सीख रहे हैं, यह हर नए एंड्रॉइड रिलीज़ के साथ होशियार और अधिक कुशल होता जा रहा है। अब जब हम अपना टूल जानते हैं, तो आइए अपने हाथ गंदे करें और अपना पहला पैसिव स्कैनर बनाएं!
हैंड्स-ऑन: अपना पहला पैसिव स्कैनर बनाना
थ्योरी बहुत अच्छी है, लेकिन आइए ईमानदार रहें, हम डेवलपर्स हैं। हम करने से सीखते हैं (या स्टैक ओवरफ्लो से कॉपी-पेस्ट करके)। अपनी आस्तीन ऊपर चढ़ाने, एंड्रॉइड स्टूडियो चालू करने, और कुछ बनाने का समय आ गया है। हम एक साधारण ऐप बनाने जा रहे हैं जो हमारी नई पैसिव स्कैनिंग शक्तियों का उपयोग करके आस-पास के BLE डिवाइस को ढूंढता है।
चरण 1: अनुमति पूछताछ
इससे पहले कि हम कोटलिन की एक भी लाइन लिखें, हमें एंड्रॉइड अनुमति देवताओं को प्रसन्न करना होगा। यह एक पवित्र और अक्सर निराशाजनक अनुष्ठान है। ब्लूटूथ स्कैनिंग के लिए, नियम सालों में थोड़े बदल गए हैं।
सबसे पहले, अपना AndroidManifest.xml खोलें और निम्नलिखित जोड़ें:
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- Android 12 (API 31) और ऊपर के लिए --> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <!-- पुराने संस्करणों के लिए, आपको स्थान अनुमतियों की आवश्यकता थी --> <!-- यदि आप पुराने डिवाइस का समर्थन करते हैं तो आपको अभी भी इसकी आवश्यकता हो सकती है --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> ऊपर घोषित अनुमतियों को देखते हुए, आप एंड्रॉइड के ब्लूटूथ अनुमति मॉडल के विकास को वास्तविक समय में देख सकते हैं।
पहली दो अनुमतियाँ, BLUETOOTH और BLUETOOTH_ADMIN, पुराने रक्षक हैं। वे एंड्रॉइड के शुरुआती दिनों से मौजूद हैं और बुनियादी ब्लूटूथ कार्यक्षमता और डिवाइस खोजने की क्षमता प्रदान करते हैं। फिर हमारे पास BLUETOOTH_SCAN है, जिसे एंड्रॉइड 12 (API 31) में पेश किया गया था और यह गोपनीयता के बारे में Google की सोच में एक बड़ा बदलाव दर्शाता है।
हाँ, आप सही देख रहे हैं। अच्छे पुराने दिनों में (एंड्रॉइड 12 से पहले), Google ने फैसला किया कि ब्लूटूथ डिवाइस ढूंढना मूल रूप से आपके यूजर के सटीक स्थान को जानने जैसा ही था। यह कुछ हद तक समझ में आता था: आखिरकार, अगर आप देख सकते हैं कि कौन से ब्लूटूथ बीकन पास में हैं, तो आप अपनी स्थिति को त्रिकोणित कर सकते हैं। लेकिन यह एक साधारण ब्लूटूथ स्कैनर ऐप के लिए स्थान पूछने के लिए थोड़ा डरावना भी था। इसने उस अजीब स्थिति को जन्म दिया जहां यूजर्स एक साधारण गैजेट-फाइंडर ऐप को अपने सटीक स्थान के लिए पूछते हुए देखते थे और समझदारी से संदेह करते थे।
शुक्र है, एंड्रॉइड 12 के साथ, उन्होंने BLUETOOTH_SCAN अनुमति पेश की, जो कहीं अधिक समझदार है। यह अनुमति अंततः ऐप्स को स्थान एक्सेस मांगे बिना ब्लूटूथ डिवाइस के लिए स्कैन करने की अनुमति देती है, जो यूजर के दृष्टिकोण से कहीं अधिक मायने रखता है। आपको अभी भी इस अनुमति को रनटाइम पर अनुरोध करना होगा, लेकिन कम से कम आपको अपने यूजर्स को यह समझाने की ज़रूरत नहीं है कि आपका साधारण गैजेट-फाइंडर ऐप यह क्यों जानना चाहता है कि वे कहाँ रहते हैं।
हालाँकि, उन अंतिम दो स्थान अनुमतियों पर ध्यान दें। वे पुराने सिस्टम के अवशेष हैं। यदि आप एक ऐसा ऐप बना रहे हैं जिसे एंड्रॉइड 11 या उससे नीचे चलने वाले पुराने डिवाइस का समर्थन करने की आवश्यकता है, तो आपको पिछड़े संगतता के लिए अपने मेनिफेस्ट में इन स्थान अनुमतियों को रखना होगा। आधुनिक डिवाइस पर, अकेले BLUETOOTH_SCAN अनुमति काम करेगी।
चरण 2: कोड जागता है
ठीक है, आइए मजेदार हिस्से पर आते हैं। यहाँ आपके Activity या Fragment में पैसिव स्कैनर को लागू करने का विवरण दिया गया है।
स्कैनर प्राप्त करें
सबसे पहले, हमें BluetoothLeScanner का एक इंस्टेंस चाहिए।
private val bluetoothAdapter: BluetoothAdapter? by lazy { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothManager.adapter } private val bleScanner: BluetoothLeScanner? by lazy { bluetoothAdapter?.bluetoothLeScanner } आइए ऊपर दिए गए कोड में जो हो रहा है उसे तोड़ते हैं। हम कोटलिन के lazy डेलीगेशन का उपयोग कर रहे हैं, जो एक फैंसी तरीका है कहने का "इस ऑब्जेक्ट को तब तक न बनाएं जब तक मुझे वास्तव में इसकी आवश्यकता न हो।" यह एक अच्छी प्रथा है क्योंकि ब्लूटूथ एडाप्टर प्राप्त करने में सिस्टम कॉल शामिल हैं, और यदि हम कभी इसका उपयोग नहीं करते हैं तो वह काम करने का कोई मतलब नहीं है।
सबसे पहले, हम सिस्टम सेवाओं से BluetoothManager लेते हैं। BluetoothManager को अपने डिवाइस पर सभी चीजों ब्लूटूथ का द्वारपाल समझें। इस मैनेजर से, हम BluetoothAdapter प्राप्त करते हैं, जो आपके डिवाइस के भौतिक ब्लूटूथ हार्डवेयर को दर्शाता है। ध्यान दें कि हम इसे नल योग्य (BluetoothAdapter?) के रूप में घोषित कर रहे हैं क्योंकि, मानो या न मानो, हर एंड्रॉइड डिवाइस में ब्लूटूथ नहीं होता। कुछ टैबलेट या अस्पष्ट डिवाइस में हार्डवेयर नहीं हो सकता है, इसलिए हमें उस संभावना के लिए तैयार रहना होगा।
एक बार हमारे पास एडाप्टर हो जाने पर, हम इससे BluetoothLeScanner मांग सकते हैं। यह वह वास्तविक ऑब्जेक्ट है जिसका उपयोग हम अपने स्कैन करने के लिए करेंगे। फिर से, हम सुरक्षित कॉल ऑपरेटर (?.) का उपयोग कर रहे हैं क्योंकि यदि एडाप्टर नल है (कोई ब्लूटूथ हार्डवेयर नहीं), तो हम निश्चित रूप से इससे स्कैनर प्राप्त नहीं कर सकते। यह रक्षात्मक प्रोग्रामिंग पागलपन लग सकती है, लेकिन यही वह चीज है जो रहस्यमय तरीके से क्रैश होने वाले ऐप्स को उन ऐप्स से अलग करती है जो एज केस को ग्रेसफुली संभालते हैं।
कॉलबैक परिभाषित करें
यहीं जादू होता है। ScanCallback एक ऑब्जेक्ट है जो स्कैन परिणामों को सुनेगा। हमें दो मेथड ओवरराइड करने होंगे: onScanResult और onScanFailed।
private val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { // हमें एक डिवाइस मिला! // 'result' ऑब्जेक्ट में डिवाइस, RSSI, और विज्ञापन डेटा होता है। Log.d("BleScanner", "डिवाइस मिला: ${result.device.address}, RSSI: ${result.rssi}") } override fun onScanFailed(errorCode: Int) { // यह ब्रह्मांड का तरीका है आपको ब्रेक लेने के लिए कहने का। // या कि कुछ भयानक रूप से गलत हो गया। Log.e("BleScanner", "स्कैन त्रुटि कोड के साथ विफल: $errorCode") } } ऊपर परिभाषित ScanCallback ब्लूटूथ दुनिया में आपके ऐप के कान हैं। जब स्कैनर एक डिवाइस ढूंढता है, तो यह जानकारी को कहीं स्टोर नहीं करता, यह इस कॉलबैक ऑब्जेक्ट के माध्यम से सक्रिय रूप से आपके ऐप पर वापस कॉल करता है। यह क्लासिक इवेंट-ड्रिवन प्रोग्रामिंग है, और यह है कि एंड्रॉइड आपके ऐप को मेन थ्रेड को ब्लॉक किए बिना रिस्पॉन्सिव रखता है।
onScanResult मेथड हर बार कॉल किया जाता है जब स्कैनर एक डिवाइस खोजता है जो आपके फिल्टर से मेल खाता है (या कोई भी डिवाइस यदि आप फिल्टर का उपयोग नहीं कर रहे हैं)। result पैरामीटर सूचना का खजाना है। इसमें BluetoothDevice ऑब्जेक्ट (जिसमें डिवाइस का MAC एड्रेस और नाम होता है), RSSI मान (रिसीव्ड सिग्नल स्ट्रेंथ इंडिकेटर - मूल रूप से डिवाइस कितना करीब है, उच्च संख्या का अर्थ करीब), और कच्चा विज्ञापन डेटा होता है जो डिवाइस प्रसारित कर रहा है।
ऊपर हमारे सरल उदाहरण में, हम सिर्फ MAC एड्रेस और RSSI लॉग कर रहे हैं, लेकिन एक वास्तविक ऐप में, आप शायद अपना UI अपडेट करना चाहेंगे, डिवाइस को सूची में जोड़ना चाहेंगे, या कनेक्शन ट्रिगर करना चाहेंगे।
callbackType पैरामीटर आपको बताता है कि क्यों इस कॉलबैक को ट्रिगर किया गया था। यह CALLBACK_TYPE_ALL_MATCHES (डिफ़ॉल्ट, अर्थात "यहाँ हर डिवाइस है जो हमने पाया"), CALLBACK_TYPE_FIRST_MATCH (पहली बार जब हमने इस डिवाइस को देखा), या CALLBACK_TYPE_MATCH_LOST (हमने इस डिवाइस को थोड़ी देर में नहीं देखा, इसलिए यह शायद चला गया) हो सकता है। हम उन्नत सेक्शन में इन प्रकारों में गहराई से जाएंगे।
फिर onScanFailed है, वह मेथड जिसे हम सभी आशा करते हैं कि कभी न बुलाया जाए लेकिन जिसे हमें बिल्कुल संभालना होगा। यह तब लागू होता है जब स्कैन के साथ कुछ भयावह रूप से गलत हो जाता है। शायद ब्लूटूथ एडाप्टर स्कैन के बीच में बंद हो गया, शायद आपके ऐप के पास सही अनुमतियाँ नहीं हैं, या शायद ब्लूटूथ कंट्रोलर का बस बुरा दिन था। errorCode आपको संकेत देगा कि क्या गलत हुआ, और आपको इसे हमेशा लॉग करना चाहिए और ग्रेसफुली संभालना चाहिए - शायद यूजर को एक संदेश दिखाकर या देरी के बाद स्कैन को पुनः आरंभ करने का प्रयास करके।
स्कैन कॉन्फ़िगर करें
अब, हम अपना ScanSettings बनाते हैं। यहीं हम एंड्रॉइड को बताते हैं कि हम एक पैसिव, बैटरी-बचाने वाला निंजा बनना चाहते हैं।
val scanSettings = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) // बैटरी के प्रति दयालु बनें .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) .setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE) .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) // प्रत्येक विज्ञापन को एक बार रिपोर्ट करें .setReportDelay(0L) // तुरंत रिपोर्ट करें // और यहाँ शो का सितारा है! .setScanType(ScanSettings.SCAN_TYPE_PASSIVE) .build() ऊपर हम जो ScanSettings ऑब्जेक्ट बना रहे हैं वह ब्लूटूथ स्कैनर के लिए एक विस्तृत निर्देश मैनुअल की तरह है। प्रत्येक मेथड कॉल ठीक से ठीक करता है कि स्कैन को कैसे व्यवहार करना चाहिए, और इन सेटिंग्स को सही करना बैटरी-फ्रेंडली ऐप और उस ऐप के बीच का अंतर है जिसे घंटों के भीतर अनइंस्टॉल कर दिया जाता है।
आइए प्रत्येक सेटिंग को देखें। सबसे पहले, setScanMode(SCAN_MODE_LOW_POWER) स्कैनर को कम-पावर स्कैनिंग मोड का उपयोग करने के लिए कहता है, जिसका अर्थ है कि यह निरंतर के बजाय अंतराल में स्कैन करेगा। यह अधिकांश उपयोग के मामलों के लिए एकदम सही है जहां आपको तत्काल परिणाम की आवश्यकता नहीं है और बैटरी लाइफ को संरक्षित करना चाहते हैं। स्कैनर जागेगा, थोड़ी देर स्कैन करेगा, सोएगा, और दोहराएगा। यह पावर नैप लेने के ब्लूटूथ समकक्ष है।
अगला, setCallbackType(CALLBACK_TYPE_ALL_MATCHES) का अर्थ है कि हम हर बार अधिसूचित होना चाहते हैं जब स्कैनर एक मिलान डिवाइस ढूंढता है। यह डिफ़ॉल्ट व्यवहार है और आप ज्यादातर समय इसका उपयोग करेंगे। जैसा कि हमने पहले उल्लेख किया है, आप अधिक परिष्कृत उपस्थिति पहचान के लिए CALLBACK_TYPE_FIRST_MATCH या CALLBACK_TYPE_MATCH_LOST का भी उपयोग कर सकते हैं।
setMatchMode(MATCH_MODE_AGGRESSIVE) सेटिंग नियंत्रित करती है कि हार्डवेयर को आपके फिल्टर के खिलाफ डिवाइस से मेल खाने के लिए कितनी आक्रामक रूप से प्रयास करना चाहिए। MATCH_MODE_AGGRESSIVE का अर्थ है "जल्दी से मेल रिपोर्ट करें, भले ही आप 100% निश्चित न हों," जबकि MATCH_MODE_STICKY का अर्थ है "रिपोर्ट करने से पहले तब तक प्रतीक्षा करें जब तक आप वास्तव में निश्चित न हों।" आक्रामक मोड आपको तेज़ परिणाम देता है लेकिन कभी-कभी झूठी सकारात्मकता दे सकता है।
फिर हमारे पास setNumOfMatches(MATCH_NUM_ONE_ADVERTISEMENT) है, जो स्कैनर को केवल एक विज्ञापन देखने के बाद डिवाइस को रिपोर्ट करने के लिए कहता है। विकल्प MATCH_NUM_FEW_ADVERTISEMENT है, जो रिपोर्ट करने से पहले कई विज्ञापनों की प्रतीक्षा करता है। एक विज्ञापन का उपयोग करने से आपको तेज़ खोज मिलती है, जबकि कुछ की प्रतीक्षा करने से उन डिवाइस से झूठी सकारात्मकता कम होती है जो बस गुजर रहे हैं।
setReportDelay(0L) सेटिंग महत्वपूर्ण है। 0 की देरी का अर्थ है "परिणाम तुरंत रिपोर्ट करें।" यदि आप इसे 5000 मिलीसेकंड पर सेट करते हैं, तो स्कैनर परिणामों को बैच करेगा और उन्हें हर 5 सेकंड में वितरित करेगा। बैचिंग बैकग्राउंड स्कैनिंग के लिए बहुत अच्छी है (जैसा कि हमने उन्नत सेक्शन में चर्चा की), लेकिन फोरग्राउंड स्कैनिंग के लिए जहां यूजर सक्रिय रूप से प्रतीक्षा कर रहा है, तत्काल रिपोर्टिंग वही है जो आप चाहते हैं।
और अंत में, हमारे शो का सितारा: setScanType(SCAN_TYPE_PASSIVE)। यह Android 16 QPR2 का नया API है जो हमारे स्कैनर को एक साइलेंट श्रोता में बदल देता है। हर डिवाइस को सक्रिय रूप से स्कैन अनुरोध भेजने के बजाय जो वह सुनता है, यह बस हवा में तैर रहे विज्ञापनों को सुनता है। यह अकेली सेटिंग स्कैनिंग के दौरान आपके ऐप की बैटरी खपत को नाटकीय रूप से कम कर सकती है। यह वह फीचर है जिसका हम इंतजार कर रहे थे, और यह शानदार है।
स्कैन शुरू करें और रोकें
अंत में, हमें अपना स्कैन शुरू और रोकने के लिए फ़ंक्शन चाहिए। याद रखें: हमेशा अपना स्कैन रोकें! एक भूला हुआ स्कैन बैटरी-मारने वाला राक्षस है।
private fun startBleScan() { // पहले अनुमतियों का अनुरोध करना न भूलें! if (bleScanner != null) { // आप यहां ScanFilters जोड़ सकते हैं विशिष्ट डिवाइस खोजने के लिए val scanFilters: List<ScanFilter> = listOf() bleScanner.startScan(scanFilters, scanSettings, scanCallback) Log.d("BleScanner", "स्कैन शुरू हुआ।") } else { Log.e("BleScanner", "ब्लूटूथ उपलब्ध नहीं है।") } } private fun stopBleScan() { if (bleScanner != null) { bleScanner.stopScan(scanCallback) Log.d("BleScanner", "स्कैन रोका गया।") } } ऊपर ये दो फ़ंक्शन आपके ब्लूटूथ स्कैनर के ऑन/ऑफ स्विच हैं, और वे जितने महत्वपूर्ण हैं उतने ही सरल हैं। आइए देखें कि प्रत्येक में क्या हो रहा है।
startBleScan() में, हम पहले जांचते हैं कि bleScanner नल नहीं है। यह हमारा सेफ्टी नेट है: यदि डिवाइस में ब्लूटूथ हार्डवेयर नहीं है या यदि ब्लूटूथ अक्षम है, तो स्कैनर नल होगा, और हम नल ऑब्जेक्ट पर मेथड कॉल करके क्रैश नहीं करना चाहते। यदि स्कैनर मौजूद है, तो हम तीन पैरामीटर्स के साथ startScan() कॉल करते हैं: ScanFilter ऑब्जेक्ट्स की एक सूची, हमारे सावधानीपूर्वक तैयार किए गए ScanSettings, और वह ScanCallback जिसे हमने पहले परिभाषित किया था।
scanFilters सूची वर्तमान में हमारे उदाहरण में खाली है, जिसका अर्थ है "सभी BLE डिवाइस ढूंढें।" एक वास्तविक ऐप में, आप आमतौर पर यहां फिल्टर जोड़कर अपनी खोज को सीमित करेंगे।
उदाहरण के लिए, यदि आप एक ऐसा ऐप बना रहे हैं जो केवल हार्ट रेट मॉनिटर के साथ काम करता है, तो आप एक फिल्टर बनाएंगे जो केवल हार्ट रेट सर्विस UUID का विज्ञापन करने वाले डिवाइस से मेल खाता है। यह प्रदर्शन और बैटरी लाइफ दोनों के लिए महत्वपूर्ण है: जब आप केवल फिटनेस ट्रैकर की परवाह करते हैं तो हर रैंडम ब्लूटूथ टूथब्रश के लिए अपने ऐप को क्यों जगाना?
startScan() मेथड स्कैनिंग प्रक्रिया को शुरू करता है। इस बिंदु से, ब्लूटूथ रेडियो सक्रिय रूप से (या हमारे मामले में, पैसिव रूप से) विज्ञापनों को सुन रहा है, और आपका scanCallback परिणाम प्राप्त करना शुरू कर देगा। यह एक एसिंक्रोनस ऑपरेशन है, जिसका अर्थ है कि आपका कोड यहां परिणाम की प्रतीक्षा में ब्लॉक नहीं होता - बल्कि, यह निष्पादन जारी रखता है, और परिणाम जब भी उपलब्ध होते हैं कॉलबैक के माध्यम से आते हैं।
अब stopBleScan() के बारे में बात करते हैं, जो आपके द्वारा लिखे गए सबसे महत्वपूर्ण फ़ंक्शन हो सकता है। जब आप अपने कॉलबैक के साथ stopScan() कॉल करते हैं, तो आप ब्लूटूथ रेडियो को बता रहे हैं, "ठीक है, हमारा काम हो गया, तुम वापस सो जाओ।" यह तुरंत स्कैनिंग प्रक्रिया को रोक देता है और संसाधनों को मुक्त कर देता है।
समझने वाली महत्वपूर्ण बात यह है कि यदि आप इसे कॉल नहीं करते हैं, तो स्कैन अनिश्चित काल तक चलता रहेगा, आपके यूजर की बैटरी को एक ऑल-यू-कैन-ईट ब्लड बैंक में पिशाच की तरह खत्म कर देगा। यही कारण है कि हम इस पर इतना जोर देते हैं: एक भूला हुआ stopScan() कॉल ब्लूटूथ ऐप्स में बैटरी ड्रेन शिकायतों के सबसे आम कारणों में से एक है।
ध्यान दें कि हम stopScan() में वही scanCallback ऑब्जेक्ट पास कर रहे हैं जिसका उपयोग हमने startScan() में किया था। इसी तरह एंड्रॉइड जानता है कि किस स्कैन को रोकना है - आपके पास सैद्धांतिक रूप से अलग-अलग कॉलबैक के साथ कई स्कैन चल रहे हो सकते हैं (हालांकि यह शायद ही कभी एक अच्छा विचार है)। हमेशा सुनिश्चित करें कि आप उसी स्कैन को रोक रहे हैं जिसे आपने उसी कॉलबैक रेफरेंस का उपयोग करके शुरू किया था।
सब कुछ एक साथ रखना
यहाँ एक पूर्ण उदाहरण है जिसे आप एक Activity में डाल सकते हैं। बस रनटाइम अनुमतियों को संभालना याद रखें!
// आपकी Activity क्लास में class MainActivity : AppCompatActivity() { // ... (ऊपर से एडाप्टर और स्कैनर के लिए लेजी प्रॉपर्टीज़) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... आपका UI सेटअप ... // उदाहरण: बटन क्लिक पर स्कैन शुरू करें val startButton = findViewById<Button>(R.id.startButton) startButton.setOnClickListener { // इसको कॉल करने से पहले आपको अनुमतियों का अनुरोध करना ही होगा! startBleScan() } // उदाहरण: दूसरे बटन पर स्कैन रोकें val stopButton = findViewById<Button>(R.id.stopButton) stopButton.setOnClickListener { stopBleScan() } } // ... (ऊपर से scanCallback, startBleScan, stopBleScan फ़ंक्शन) override fun onPause() { super.onPause() // जब एक्टिविटी विजिबल न हो तो हमेशा स्कैनिंग रोकें। stopBleScan() } } ऊपर पूर्ण उदाहरण दिखाता है कि सभी टुकड़े एक वास्तविक Activity में कैसे फिट होते हैं। यह एक न्यूनतम लेकिन कार्यात्मक ब्लूटूथ स्कैनर है जिसे आप वास्तव में चला सकते हैं। आइए कुछ महत्वपूर्ण पैटर्न को हाइलाइट करें जिनका हम यहां उपयोग कर रहे हैं।
सबसे पहले, ध्यान दें कि हम स्कैन जीवनचक्र को बटन क्लिक के माध्यम से उपयोगकर्ता क्रियाओं से कैसे जोड़ रहे हैं। यह एक सामान्य पैटर्न है: उपयोगकर्ता स्पष्ट रूप से स्कैन शुरू और रोकता है, जिससे उन्हें इस बात पर नियंत्रण मिलता है कि ऐप कब ब्लूटूथ का उपयोग कर रहा है। यह अच्छे UX और बैटरी लाइफ दोनों के लिए अच्छा है, क्योंकि स्कैन केवल तभी चलता है जब उपयोगकर्ता चाहता है।
लेकिन यहाँ वास्तव में महत्वपूर्ण हिस्सा है: onPause() ओवरराइड। यह एक महत्वपूर्ण सेफ्टी नेट है। जब आपकी Activity बैकग्राउंड में जाती है (शायद उपयोगकर्ता ने होम बटन दबाया, या वे दूसरे ऐप पर स्विच हो गए), onPause() कहा जाता है, और हम तुरंत स्कैन रोक देते हैं। यह आवश्यक है क्योंकि यदि उपयोगकर्ता आपका ऐप नहीं देख सकता, तो उन्हें स्कैन परिणामों की आवश्यकता नहीं है, और उनकी बैटरी खत्म करने का कोई कारण नहीं है। यह पैटर्न सुनिश्चित करता है कि भले ही उपयोगकर्ता "रोकें" बटन दबाना भूल जाए, स्कैन बैकग्राउंड में हमेशा के लिए नहीं चलेगा।
आप सोच रहे होंगे, "onResume() के बारे में क्या? क्या हमें उपयोगकर्ता के वापस आने पर स्कैन को पुनः आरंभ नहीं करना चाहिए?" यह एक डिज़ाइन निर्णय है। कुछ ऐप्स में, आप onResume() में स्वचालित रूप से स्कैनिंग पुनः आरंभ करना चाह सकते हैं। दूसरों में, आप चाह सकते हैं कि उपयोगकर्ता फिर से स्पष्ट रूप से "प्रारंभ" दबाए। यह आपके उपयोग के मामले पर निर्भर करता है। एक डिवाइस-खोज ऐप के लिए जहां उपयोगकर्ता सक्रिय रूप से खोज रहा है, ऑटो-रिज्यूम समझ में आता है। एक मॉनिटरिंग ऐप के लिए जो बैकग्राउंड में चलता है, आप अधिक स्पष्ट नियंत्रण चाह सकते हैं।
एक महत्वपूर्ण चीज जो हमने इस उदाहरण में नहीं दिखाई है वह है रनटाइम अनुमति हैंडलिंग। याद रखें वे अनुमतियाँ जिन्हें हमने मेनिफेस्ट में घोषित किया था? एंड्रॉइड 6.0 और ऊपर पर, आप उन्हें केवल घोषित नहीं कर सकते, आपको वास्तव में ActivityCompat.requestPermissions() का उपयोग करके रनटाइम पर उपयोगकर्ता से उनका अनुरोध करना होगा। startBleScan() कॉल करने से पहले, आपको जांचना चाहिए कि आपके पास आवश्यक अनुमतियाँ हैं या नहीं और, यदि नहीं, तो उन्हें अनुरोध करें। यदि आप उचित अनुमतियों के बिना स्कैन शुरू करने का प्रयास करते हैं, तो यह साइलेंटली (या जोर से, एंड्रॉइड संस्करण के आधार पर) विफल हो जाएगा, और आप अपना सिर खुजाते रह जाएंगे कि कुछ भी काम क्यों नहीं कर रहा है।
और बस! आपने अभी अपना पहला AOSP 16 पैसिव ब्लूटूथ स्कैनर बनाया है। यह दुबला है, यह मतलबी है, और यह अविश्वसनीय रूप से पावर-कुशल है। स्कैनर BLE विज्ञापनों के लिए चुपचाप सुनता है, उन्हें आपके कॉलबैक के माध्यम से रिपोर्ट करता है, और जब इसकी आवश्यकता नहीं होती तो ग्रेसफुली रुक जाता है।
अब, आइए अपने अगले विषय पर चलते हैं: जब चीजें गलत हो जाएं तो क्या करें। ब्रेकअप के बारे में बात करने का समय है... ब्लूटूथ बॉन्ड ब्रेकअप, यानी।
डीप डाइव #2: ब्लूटूथ बॉन्ड लॉस कारण
अरे, ब्लूटूथ बॉन्ड। यह एक सुंदर, पवित्र चीज है। यह दोस्ती के कंगन का आदान-प्रदान करने का डिजिटल समकक्ष है। जब आप अपने फोन को अपने हेडफ़ोन के साथ बॉन्ड करते हैं, तो आप एक दीर्घकालिक, विश्वसनीय संबंध बना रहे होते हैं। वे गुप्त कुंजियों को साझा करते हैं, वे एक-दूसरे को याद रखते हैं, और वे हर बार पेयरिंग की परेशानी से बचने के लिए स्वचालित रूप से कनेक्ट होने का वादा करते हैं। यह एक सुंदर रोमांस है।
जब तक यह नहीं है।
अचानक, एक दिन, वे बस... एक-दूसरे को भूल जाते हैं। कनेक्शन चला गया। भरोसा टूट गया। और आपका ऐप बीच में फंसा हुआ है, चिकित्सक खेलने की कोशिश कर रहा है, कोई अंदाजा नहीं कि क्या गलत हुआ। आपको भूतिया बना दिया गया है। और अब तक, एंड्रॉइड कोई मदद नहीं कर रहा था। आपको एक नोटिफिकेशन मिलेगा कि बॉन्ड स्टेट अब BOND_NONE है, लेकिन बस। कोई स्पष्टीकरण नहीं। कोई समापन नहीं। बस एक असफल कनेक्शन की ठंडी, कठोर चुप्पी।
आखिरकार, कुछ समापन!
लेकिन एंड्रॉइड टीम में हमारे दोस्त स्पष्ट रूप से कुछ कठिन ब्रेकअप से गुजरे हैं, क्योंकि AOSP 16 में, उन्होंने हमें समापन का उपहार दिया है। BluetoothDevice.EXTRA_BOND_LOSS_REASON पेश कर रहे हैं। यह एक नया एक्स्ट्रा है जो ACTION_BOND_STATE_CHANGED ब्रॉडकास्ट के साथ आता है, और यह आपको बताने के लिए यहाँ है कि बॉन्ड क्यों खो गया था। यह एक ब्रेकअप टेक्स्ट प्राप्त करने जैसा है जो वास्तव में बताता है कि क्या हुआ!
अब, जब एक बॉन्ड टूट जाता है, तो आप एक विशिष्ट कारण कोड प्राप्त कर सकते हैं। उन्हें ब्लूटूथ के लिए क्लासिक ब्रेकअप बहाने के रूप में सोचें:
| कारण कोड (चित्रात्मक) | इसका वास्तव में क्या अर्थ है |
|---|---|
| BOND_LOSS_REASON_BREDR_AUTH_FAILURE | इंगित करता है कि बॉन्ड लॉस का कारण BREDR प्रमाणीकरण विफलता है। |
| BOND_LOSS_REASON_BREDR_INCOMING_PAIRING | इंगित करता है कि बॉन्ड लॉस का कारण BREDR पेयरिंग विफलता है। |
| BOND_LOSS_REASON_LE_ENCRYPT_FAILURE | इंगित करता है कि बॉन्ड लॉस का कारण LE एन्क्रिप्शन विफलता है। |
| BOND_LOSS_REASON_LE_INCOMING_PAIRING | इंगित करता है कि बॉन्ड लॉस का कारण LE पेयरिंग विफलता है। |
कोड: जासूस खेलना
तो, हम इस रसीले गपशप को कैसे प्राप्त करते हैं? हमें बॉन्ड स्टेट परिवर्तनों को सुनने के लिए एक BroadcastReceiver सेट अप करने की आवश्यकता है।
// बॉन्ड स्टेट परिवर्तनों को सुनने के लिए एक BroadcastReceiver बनाएं private val bondStateReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED) { val device: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE) val bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR) val previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, BluetoothDevice.ERROR) // जांचें कि क्या हम बॉन्डेड से नॉट बॉन्डेड पर गए if (bondState == BluetoothDevice.BOND_NONE && previousBondState == BluetoothDevice.BOND_BONDED) { Log.d("BondBreakup", "हमें ${device?.address} ने छोड़ दिया!") // अब, आइए पता करें क्यों... val reason = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_LOSS_REASON, -1) when (reason) { // नोट: वास्तविक स्थिर मान Android SDK में हैं BluetoothDevice.BOND_LOSS_REASON_REMOTE_DEVICE_REMOVED -> { Log.d("BondBreakup", "कारण: रिमोट डिवाइस ने बॉन्ड हटा दिया।") // आप यूजर को एक संदेश दिखा सकते हैं: "आपके हेडफ़ोन आपको भूल गए लगते हैं। कृपया फिर से पेयर करने का प्रयास करें।" } // ... अन्य कारणों को संभालें ... else -> { Log.d("BondBreakup", "कारण: यह जटिल है (अज्ञात कारण कोड: $reason)") } } } } } // अपनी Activity या Service में, रिसीवर रजिस्टर करें override fun onResume() { super.onResume() val filter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED) registerReceiver(bondStateReceiver, filter) } override fun onPause() { super.onPause() // अनरजिस्टर करना न भूलें! unregisterReceiver(bondStateReceiver) } ऊपर दिया गया कोड ब्लूटूथ बॉन्ड ब्रेकअप के लिए एक जासूसी प्रणाली लागू करता है, और यह पहली नज़र में जितना परिष्कृत लग सकता है उससे कहीं अधिक परिष्कृत है। आइए देखें कि यह ब्रॉडकास्ट रिसीवर पैटर्न कैसे काम करता है और यह इतना शक्तिशाली क्यों है।
सबसे पहले, हम एक BroadcastReceiver बना रहे हैं, जो आपके ऐप को सिस्टम-वाइड इवेंट सुनने देने का एंड्रॉइड का तरीका है। इसे एक नोटिफिकेशन सेवा की सदस्यता लेने के रूप में सोचें, जब भी एंड्रॉइड सिस्टम में कुछ दिलचस्प होता है (जैसे बॉन्ड स्टेट परिवर्तन), सिस्टम सभी रजिस्टर्ड श्रोताओं को एक "इंटेंट" प्रसारित करता है। हमारा रिसीवर उन श्रोताओं में से एक है।
onReceive() मेथड में, हम पहले जांचते हैं कि आने वाले इंटेंट का एक्शन ACTION_BOND_STATE_CHANGED है या नहीं। यह महत्वपूर्ण है क्योंकि ब्रॉडकास्ट रिसीवर संभावित रूप से कई अलग-अलग प्रकार के इंटेंट प्राप्त कर सकते हैं, और हमें केवल बॉन्ड स्टेट परिवर्तनों की परवाह है। एक बार जब हम पुष्टि कर लेते हैं कि यह सही प्रकार का इवेंट है, तो हम getParcelableExtra() और getIntExtra() का उपयोग करके इंटेंट से प्रासंगिक जानकारी निकालते हैं।
device ऑब्जेक्ट हमें बताता है कि यह इवेंट किस ब्लूटूथ डिवाइस के बारे में है। आखिरकार, आप कई डिवाइस (आपके हेडफ़ोन, आपकी स्मार्टवॉच, आपकी कार) के साथ बॉन्डेड हो सकते हैं, और हमें यह जानने की ज़रूरत है कि किसने अभी-अभी हमारे साथ संबंध तोड़ा है। bondState हमें वर्तमान स्थिति बताता है (क्या हम बॉन्डेड हैं, बॉन्डिंग कर रहे हैं, या बॉन्डेड नहीं हैं?), और previousBondState हमें बताता है कि इस परिवर्तन से पहले स्थिति क्या थी।
मुख्य तर्क हमारे सशर्त जांच में होता है: if (bondState == BluetoothDevice.BOND_NONE && previousBondState == BluetoothDevice.BOND_BONDED)। यह "बॉन्डेड" से "नॉट बॉन्डेड" में विशिष्ट संक्रमण की जांच कर रहा है, जो ब्रेकअप का डिजिटल समकक्ष है। हमें बॉन्डिंग प्रक्रिया में दिलचस्पी नहीं है (नन से बॉन्डिंग से बॉन्डेड तक जाना) - हमें केवल परवाह है जब एक मौजूदा बॉन्ड खो जाता है।
एक बार जब हम ब्रेकअप का पता लगा लेते हैं, तो हम इंटेंट से नया EXTRA_BOND_LOSS_REASON निकालते हैं। यह AOSP 16 की स्टार फीचर है जो अंततः हमें समापन देती है। कारण कोड हमें सटीक रूप से बताता है कि बॉन्ड क्यों खो गया था - क्या यह रिमोट डिवाइस था जिसने चीजों को समाप्त किया? क्या उपयोगकर्ता ने मैन्युअल रूप से डिवाइस को भुला दिया? क्या प्रमाणीकरण विफल रहा? प्रत्येक कारण कोड एक अलग परिदृश्य से मेल खाता है, और आप प्रत्येक को उचित तरीके से संभाल सकते हैं।
ऊपर दिए गए उदाहरण में, हम विभिन्न कारण कोड को संभालने के लिए when एक्सप्रेशन का उपयोग कर रहे हैं। BOND_LOSS_REASON_BREDR_INCOMING_PAIRING के लिए, हम जानते हैं कि दूसरे डिवाइस ने ब्रेकअप शुरू किया, इसलिए हम "आपके हेडफ़ोन आपको भूल गए लगते हैं। कृपया फिर से पेयर करने का प्रयास करें।" जैसा एक सहायक संदेश दिखा सकते हैं। अन्य कारणों के लिए, आप उन्हें विशेष रूप से संभालने के लिए और शाखाएँ जोड़ेंगे।
अब, नीचे जीवनचक्र प्रबंधन पर ध्यान दें। हम onResume() में अपना रिसीवर रजिस्टर करते हैं और इसे onPause() में अनरजिस्टर करते हैं। यह महत्वपूर्ण है: यदि आप ब्रॉडकास्ट रिसीवर को अनरजिस्टर करना भूल जाते हैं, तो यह आपके Activity के नष्ट होने के बाद भी प्रसारण प्राप्त करता रहेगा, जिससे मेमोरी लीक और क्रैश हो सकते हैं। onResume() में रजिस्टर करने और onPause() में अनरजिस्टर करने का पैटर्न सुनिश्चित करता है कि हम केवल तभी बॉन्ड परिवर्तनों को सुनते हैं जब हमारा Activity विजिबल और सक्रिय हो।
यह डीबगिंग और यूजर अनुभव के लिए एक बड़ा कदम आगे है। केवल यूजर को "कनेक्शन विफल" बताने के बजाय, अब आप उन्हें बॉन्ड खोने के विशिष्ट कारण के आधार पर कार्रवाई योग्य सलाह दे सकते हैं। यह एक भ्रमित दर्शक की तरह कंधे उचकाकर "मुझे नहीं पता क्या हुआ" कहने के बजाय एक सहायक, सूचित संबंध परामर्शदाता होने जैसा है।
अब जब हमने ब्रेकअप के भावनात्मक सामान को निपटा लिया है, तो आइए कुछ अधिक हल्के-फुल्के पर चलते हैं: ब्लूटूथ डिवाइस के लिए स्पीड डेटिंग।
डीप डाइव #3: विज्ञापनों से सर्विस UUID
आइए अपने ऐप के लिए एक संगत साथी खोजने के बारे में बात करते हैं। BLE की दुनिया में, सभी डिवाइस समान नहीं बनाए गए हैं। एक हार्ट रेट मॉनिटर एक स्मार्ट लाइटबल्ब से बहुत अलग है। तो आपका ऐप कैसे जानता है कि यह सही प्रकार के डिवाइस से बात कर रहा है? उत्तर सर्विस UUID है।
सर्विस UUID दुनिया में क्या है?
एक सर्विस UUID (यूनिवर्सली यूनिक आइडेंटिफायर) एक डिवाइस के जॉब शीर्षक की तरह है। यह एक अद्वितीय, 128-बिट संख्या है जो कहती है, "मैं एक ऐसा डिवाइस हूँ जो हार्ट रेट सर्विस प्रदान करता है" या "मैं एक ऐसा डिवाइस हूँ जो बैटरी सर्विस प्रदान करता है।" यह यह निर्धारित करने के लिए एकल सबसे महत्वपूर्ण जानकारी है कि एक डिवाइस क्या कर सकता है।
पुराना तरीका: अजीब पहली डेट
पारंपरिक रूप से, एक डिवाइस की सेवाओं का पता लगाना एक पूरी परीक्षा थी। यह केवल दूसरे व्यक्ति की नौकरी का पता लगाने के लिए एक पूर्ण, तीन-कोर्स डिनर डेट पर जाने जैसा था। प्रक्रिया कुछ इस तरह थी:
स्कैन: डिवाइस ढूंढें।
कनेक्ट: एक कनेक्शन स्थापित करें (एक धीमी और पावर-भूखी प्रक्रिया)।
सेवाएँ खोजें: डिवाइस से पूछें, "तो... तुम जीविका के लिए क्या करते हो?" और इसकी सभी सेवाओं को सूचीबद्ध करने के लिए प्रतीक्षा करें।
मूल्यांकन: जांचें कि सेवाओं की सूची में वह शामिल है जिसमें आप रुचि रखते हैं।
डिस्कनेक्ट (या कनेक्टेड रहें): यदि यह सही डिवाइस नहीं है, तो आपको ब्रेकअप (डिस्कनेक्ट) करना होगा और आगे बढ़ना होगा। कितना समय और ऊर्जा की बर्बादी!
यह अविश्वसनीय रूप से अक्षम है, खासकर यदि आप दर्जनों BLE डिवाइस से भरे कमरे में हैं और केवल एक विशिष्ट प्रकार की तलाश कर रहे हैं।
नया तरीका: शानदार नेम टैग
क्या यह बहुत अच्छा नहीं होगा अगर पार्टी में हर कोई अपने जॉब शीर्षक के साथ एक नेम टैग पहने? एंड्रॉइड 16 ने BluetoothDevice.EXTRA_UUID_LE के साथ हमें ठीक यही दिया है। कई BLE डिवाइस अपने विज्ञापन पैकेट में अपने प्राथमिक सर्विस UUID को शामिल करने के लिए पहले से ही काफी विनम्र हैं। यह पूरे कमरे में चिल्लाने का उनका तरीका है, "मैं एक हार्ट रेट मॉनिटर हूँ!"
AOSP 16 से पहले, इस जानकारी को विज्ञापन पैकेट से बाहर निकालना स्कैन रिकॉर्ड के कच्चे बाइट एरे को मैन्युअल रूप से पार्स करने की एक गन्दी, मैन्युअल प्रक्रिया थी। यह करने योग्य था, लेकिन यह उस तरह का कोड था जिसे आप एक बार लिखते थे, प्रार्थना करते थे कि यह काम करे, और फिर कभी छूते नहीं थे।
अब, एंड्रॉइड हमारे लिए गंदा काम करता है! सिस्टम स्वचालित रूप से विज्ञापन डेटा को पार्स करता है और, यदि यह कोई सर्विस UUID ढूंढता है, तो यह उन्हें ScanResult में आपको आसानी से सौंप देता है।
कोड: नेम टैग पढ़ना
यह नई सुविधा हमारे ScanCallback को और भी शक्तिशाली बनाती है। अब हम कनेक्ट किए बिना, डिवाइस की खोज के क्षण में उसके जॉब शीर्षक की जांच कर सकते हैं।
private val scanCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { Log.d("BleSpeedDating", "डिवाइस मिला: ${result.device.address}") // आइए उनका नेम टैग जांचें! val serviceUuids = result.scanRecord?.serviceUuids if (serviceUuids.isNullOrEmpty()) { Log.d("BleSpeedDating", "यह रहस्यमय है। विज्ञापन में कोई सर्विस UUID नहीं।") return } // वह UUID परिभाषित करें जिसे हम ढूंढ रहे हैं (उदाहरण के लिए, मानक हार्ट रेट सर्विस UUID) val heartRateServiceUuid = ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB") if (serviceUuids.contains(heartRateServiceUuid)) { Log.d("BleSpeedDating", "यह एक मैच है! यह एक हार्ट रेट मॉनिटर है। आइए कनेक्ट करें!") // अब आप result.device से कनेक्ट करने के लिए आगे बढ़ सकते हैं, यह जानते हुए कि यह सही है। stopBleScan() // हमें वह मिल गया जिसकी हम तलाश कर रहे थे // connectToDevice(result.device) } else { Log.d("BleSpeedDating", "मैच नहीं। आगे बढ़ते हैं।") } } // ... onScanFailed ... } ऊपर दिया गया कोड विज्ञापन डेटा से सीधे सर्विस UUID पढ़ने की शक्ति को प्रदर्शित करता है, और यह डिवाइस खोज के लिए एक गेम-चेंजर है। आइए ठीक से देखें कि क्या हो रहा है और यह इतना महत्वपूर्ण सुधार क्यों है।
जब हम अपने कॉलबैक में एक स्कैन परिणाम प्राप्त करते हैं, तो result ऑब्जेक्ट में एक scanRecord प्रॉपर्टी होती है। यह स्कैन रिकॉर्ड अनिवार्य रूप से वह कच्चा विज्ञापन पैकेट है जो BLE डिवाइस ने हवा में प्रसारित किया।
AOSP 16 से पहले, यदि आप इस डेटा से सर्विस UUID निकालना चाहते थे, तो आपको मैन्युअल रूप से बाइट एरे को पार्स करना होगा, BLE विज्ञापन प्रारूप को समझना होगा, विभिन्न डेटा प्रकारों को संभालना होगा, और प्रार्थना करनी होगी कि आपने ऑफ-बाय-वन त्रुटि नहीं की। यह उस तरह का कोड था जो एक बार काम करता था और फिर आप इसे डर के मारे फिर कभी नहीं छूते थे।
अब, AOSP 16 में सुधार के साथ, एंड्रॉइड हमारे लिए वह सारी गन्दी पार्सिंग करता है। हम बस result.scanRecord?.serviceUuids कॉल कर सकते हैं और ParcelUuid ऑब्जेक्ट की एक अच्छी, साफ सूची वापस पा सकते हैं। सेफ कॉल ऑपरेटर (?.) यहाँ महत्वपूर्ण है क्योंकि सभी डिवाइस अपने परिणामों में स्कैन रिकॉर्ड शामिल नहीं करते हैं, और हमें इसे ग्रेसफुली संभालने की आवश्यकता है।
सर्विस UUID प्राप्त करने के बाद, हम जांचते हैं कि सूची नल या खाली है या नहीं। कुछ डिवाइस अपने विज्ञापनों में सर्विस UUID शामिल नहीं करते हैं। वे एक मालिकाना प्रारूप का उपयोग कर रहे होंगे, या वे बस खराब तरीके से कॉन्फ़िगर हो सकते हैं। यदि कोई UUID नहीं हैं, तो हम एक संदेश लॉग करते हैं और जल्दी लौटते हैं। यदि हम यह पहचान नहीं सकते कि डिवाइस क्या करता है तो जारी रखने का कोई मतलब नहीं है।
अगला, हम वह UUID परिभाषित करते हैं जिसे हम ढूंढ रहे हैं। इस उदाहरण में, हम हार्ट रेट मॉनिटर खोज रहे हैं, इसलिए हम मानक हार्ट रेट सर्विस UUID का उपयोग करते हैं: 0000180D-0000-1000-8000-00805F9B34FB। यह ब्लूटूथ SIG (स्पेशल इंटरेस्ट ग्रुप) द्वारा परिभाषित एक UUID है, और कोई भी अनुपालन हार्ट रेट मॉनिटर इस UUID का विज्ञापन करेगा। आप ब्लूटूथ विनिर्देशों में मानक सर्विस UUID की पूरी सूची पा सकते हैं, या यदि आप अपने स्वयं के BLE पेरिफेरल बना रहे हैं तो कस्टम UUID का उपयोग कर सकते हैं।
जादू if (serviceUuids.contains(heartRateServiceUuid)) जांच में होता है। यहीं हम अपनी स्पीड डेटिंग कर रहे हैं: हम यह देखने के लिए डिवाइस के "नेम टैग" की जांच कर रहे हैं कि क्या यह उससे मेल खाता है जिसकी हम तलाश कर रहे हैं।
यदि यह करता है, तो हमें अपना मैच मिल गया! हम तुरंत स्कैनिंग रोक सकते हैं (क्योंकि जब हमें वह मिल गया जिसकी हमें ज़रूरत है तो खोजते रहें?) और डिवाइस से कनेक्ट करने के लिए आगे बढ़ें। हम निश्चितता के साथ जानते हैं कि यह डिवाइस एक हार्ट रेट मॉनिटर है, इसलिए हम रैंडम डिवाइस से कनेक्ट करके समय और बैटरी बर्बाद नहीं करेंगे केवल यह पता लगाने के लिए कि वे वही नहीं हैं जिनकी हमें ज़रूरत है।
यदि UUID मेल नहीं खाता, तो हम बस "मैच नहीं" लॉग करते हैं और आगे बढ़ते हैं। अगला डिवाइस मिलने पर कॉलबैक को फिर से बुलाया जाएगा, और हम इस प्रक्रिया को तब तक दोहराएंगे जब तक हम अपना हार्ट रेट मॉनिटर नहीं ढूंढ लेते या उपयोगकर्ता स्कैन रोक नहीं देता।
यह पुराने दृष्टिकोण पर एक विशाल प्रदर्शन सुधार है। पहले, आपको आपके द्वारा खोजे गए हर डिवाइस से कनेक्ट करना पड़ता था, सर्विस डिस्कवरी करना पड़ता था (जिसमें डिवाइस के साथ कई राउंड-ट्रिप संचार शामिल होते हैं), जांचना पड़ता था कि क्या इसमें आपके लिए आवश्यक सेवाएं हैं, और फिर यदि नहीं तो डिस्कनेक्ट करना पड़ता था। प्रत्येक कनेक्शन प्रयास में समय लगता है, बैटरी का उपयोग होता है, और अनावश्यक रेडियो ट्रैफिक बनाता है।
अब, आप स्कैनिंग चरण में बिजली की गति से डिवाइस को फ़िल्टर और पहचान सकते हैं। कोई और अजीब पहली डेट नहीं जहां आप एक स्मार्ट लाइटबल्ब से कनेक्ट होते हैं यह सोचकर कि यह फिटनेस ट्रैकर हो सकता है। बस कुशल, लक्षित कनेक्शन।
यह उन ऐप्स के लिए विशेष रूप से उपयोगी है जिन्हें अप्रासंगिक डिवाइस के समुद्र में एक विशिष्ट प्रकार के सेंसर या पेरिफेरल को खोजने की आवश्यकता होती है। कल्पना करें कि आप सैकड़ों BLE-सक्षम चिकित्सा डिवाइस वाले अस्पताल में हैं, या दर्जनों सेंसर और एक्चुएटर वाले स्मार्ट होम में। इसके विज्ञापन से सही डिवाइस को तुरंत पहचानने में सक्षम होना एक रिस्पॉन्सिव, प्रोफेशनल ऐप और एक सुस्त और अविश्वसनीय ऐप के बीच का अंतर है।
हम अब अपने तीनों ब्लूटूथ मस्किटियर्स से मिल चुके हैं: बैटरी दक्षता के लिए पैसिव स्कैनिंग, बेहतर डीबगिंग के लिए बॉन्ड लॉस कारण, और तेज़ डिवाइस पहचान के लिए विज्ञापनों से सर्विस UUID। लेकिन हमारी यात्रा खत्म नहीं हुई है। उन्नत स्कैनिंग तकनीकों के गहरे जंगल में उद्यम करने का समय आ गया है।
उन्नत विषय: फ़िल्टरिंग, बैचिंग, और अन्य जादू
ठीक है, आपने बुनियादी बातों में महारत हासिल कर ली है। आप पैसिवली स्कैन कर सकते हैं, आप अपने कनेक्शन ब्रेकअप पर समापन प्राप्त कर सकते हैं, और आप एक समर्थक की तरह डिवाइस को स्पीड-डेट कर सकते हैं। आप अब ब्लूटूथ पदवान नहीं हैं। जेडी मास्टर बनने का समय आ गया है।
आइए फ़िल्टरिंग, बैचिंग, और अन्य ऑप्टिमाइज़ेशन जादू की उन्नत कलाओं में गोता लगाएँ जो आपके ऐप को एक सच्चा बैटरी-बचाने वाला चैंपियन बना देंगे।
हार्डवेयर फ़िल्टरिंग: आपका निजी सहायक
कल्पना कीजिए कि आप एक सेलिब्रिटी हैं, और आपने एक निजी सहायक रखा है। आप नहीं चाहते कि हर एक व्यक्ति जो ऑटोग्राफ चाहता है आपको परेशान करे। तो, आप अपने सहायक को एक सूची देते हैं: "मुझे केवल बताना अगर तुम मेरे एजेंट या मेरी माँ को देखो।" आपका सहायक फिर दरवाजे पर खड़ा होता है और केवल आपको परेशान करता है जब सूची में कोई दिखाई देता है।
यही हार्डवेयर फ़िल्टरिंग करता है। आपके ऐप के कोड (सेलिब्रिटी) को रेडियो द्वारा देखे जाने वाले हर एक ब्लूटूथ डिवाइस के लिए जगाने के बजाय, आप फ़िल्टरिंग तर्क को ब्लूटूथ कंट्रोलर (निजी सहायक) पर ऑफलोड कर सकते हैं। यह एक ऐसी सुविधा है जो एंड्रॉइड 6.0 के आसपास रही है, लेकिन यह पहले से कहीं अधिक महत्वपूर्ण है।
यह इतना बढ़िया क्यों है? क्योंकि आपके ऐप का कोड सोया रह सकता है। मुख्य प्रोसेसर (AP) को हर बार जागने की ज़रूरत नहीं है जब एक रैंडम ब्लूटूथ टूथब्रश अपना विज्ञापन करता है। ब्लूटूथ कंट्रोलर, जो बहुत अधिक पावर-कुशल है, फ़िल्टरिंग को संभालता है। AP केवल तभी जागता है जब कंट्रोलर एक डिवाइस ढूंढता है जो आपके मानदंड से मेल खाता है।
कोड: अपनी VIP सूची बनाना
आप इसे ScanFilter का उपयोग करके लागू करते हैं। आप डिवाइस के नाम, उसके MAC एड्रेस, या, सबसे उपयोगी रूप से, उस सर्विस UUID द्वारा फ़िल्टर कर सकते हैं जिसका वह विज्ञापन कर रहा है।
// हम केवल तभी परेशान होना चाहते हैं जब हम एक हार्ट रेट मॉनिटर देखें। val heartRateServiceUuid = ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB") val filter = ScanFilter.Builder() .setServiceUuid(heartRateServiceUuid) .build() val scanFilters: List<ScanFilter> = listOf(filter) // अब, जब आप अपना स्कैन शुरू करते हैं, तो इस सूची को पास करें bleScanner.startScan(scanFilters, scanSettings, scanCallback) ऊपर दिया गया कोड दिखाता है कि हार्डवेयर-लेवल फ़िल्टर कैसे बनाया जाता है जो बैटरी लाइफ और ऐप प्रदर्शन दोनों में नाटकीय रूप से सुधार करता है। आइए गहराई से देखें कि यहाँ क्या हो रहा है और यह इतनी शक्तिशाली तकनीक क्यों है।
हम उस सर्विस UUID को परिभाषित करके शुरू करते हैं जिसमें हम रुचि रखते हैं - इस मामले में, मानक हार्ट रेट सर्विस UUID। यह वही UUID है जिसका हमने पिछले उदाहरण में उपयोग किया था, लेकिन अब हम इसका मौलिक रूप से अलग तरीके से उपयोग कर रहे हैं। स्कैन परिणाम प्राप्त करने के बाद अपने ऐप के कोड में UUID की जांच करने के बजाय, हम ब्लूटूथ हार्डवेयर को बता रहे हैं कि केवल उन डिवाइस को रिपोर्ट करें जो इस UUID से मेल खाते हैं।
ScanFilter.Builder() इस फ़िल्टर को बनाने के लिए हमारा टूल है। यह एक बिल्डर पैटर्न है, जिसका अर्थ है कि हम कई मेथड को एक साथ चेन कर सकते हैं ताकि हम ठीक से कॉन्फ़िगर कर सकें कि हम क्या ढूंढ रहे हैं। इस उदाहरण में, हम setServiceUuid(heartRateServiceUuid) कॉल कर रहे हैं, जो फ़िल्टर को केवल उन डिवाइस से मेल खाने के लिए कहता है जो इस विशिष्ट सर्विस का विज्ञापन करते हैं।
लेकिन बिल्डर में कई अन्य विकल्प हैं जिनका आप उपयोग कर सकते हैं:
setDeviceName()- विशिष्ट नाम वाले डिवाइस से मेल खाएं (जैसे "मेरा हार्ट मॉनिटर")setDeviceAddress()- MAC एड्रेस द्वारा एक विशिष्ट डिवाइस से मेल खाएं (यदि आप पहले से ही एक डिवाइस के साथ पेयर कर चुके हैं और इसे फिर से ढूंढना चाहते हैं तो उपयोगी)setManufacturerData()- उनके विज्ञापनों में निर्माता-विशिष्ट डेटा के आधार पर डिवाइस से मेल खाएंsetServiceData()- विज्ञापन में शामिल सर्विस डेटा के आधार पर मेल खाएं
आप एक ही फ़िल्टर में कई मानदंडों को भी जोड़ सकते हैं। उदाहरण के लिए, आप एक फ़िल्टर बना सकते हैं जो एक विशिष्ट सर्विस UUID और एक विशिष्ट निर्माता ID वाले डिवाइस से मेल खाता है। आपका फ़िल्टर जितना अधिक विशिष्ट होगा, उतनी ही कम झूठी सकारात्मकता आपको मिलेगी।
अपना फ़िल्टर बनाने के बाद, हम इसे含む एक सूची बनाते हैं। सूची क्यों? क्योंकि आपके पास कई फ़िल्टर हो सकते हैं, और एक डिवाइस मेल खाएगा यदि यह सूची में किसी भी फ़िल्टर को संतुष्ट करता है। उदाहरण के लिए, आप हार्ट रेट मॉनिटर के लिए एक फ़िल्टर और ब्लड प्रेशर मॉनिटर के लिए दूसरा बना सकते हैं, और आपका स्कैन उन डिवाइस को रिपोर्ट करेगा जो उनमें से किसी एक से मेल खाते हैं। यह एक OR ऑपरेशन है: डिवाइस को सभी फ़िल्टर से मेल खाने की आवश्यकता नहीं है, बस उनमें से एक से।
अंत में, हम इस फ़िल्टर सूची को अपने स्कैन सेटिंग्स और कॉलबैक के साथ startScan() में पास करते हैं। यहीं जादू होता है। जब आप फ़िल्टर प्रदान करते हैं, तो एंड्रॉइड केवल आपके ऐप के कोड में परिणामों को फ़िल्टर नहीं करता है। यह इन फ़िल्टर को ब्लूटूथ कंट्रोलर हार्डवेयर पर ही पुश कर देता है। इसका अर्थ है कि फ़िल्टरिंग सबसे निचले स्तर पर होती है, इससे पहले कि आपके ऐप को अधिसूचित भी किया जाए।
यह इतना शक्तिशाली क्यों है: फ़िल्टर के बिना, हर बार जब ब्लूटूथ रेडियो किसी भी डिवाइस (आपके पड़ोसी के स्मार्ट टोस्टर, गुजरते किसी के फिटनेस ट्रैकर, तीन कमरे दूर ब्लूटूथ स्पीकर) से एक विज्ञापन सुनता है, तो उसे आपके ऐप की प्रक्रिया को जगाना पड़ता है, स्कैन परिणाम वितरित करना पड़ता है, और आपके कोड को यह तय करने देना पड़ता है कि क्या यह इस डिवाइस की परवाह करता है। इनमें से प्रत्येक वेक-अप की बैटरी और प्रोसेसिंग समय की कीमत होती है।
हार्डवेयर फ़िल्टर के साथ, ब्लूटूथ कंट्रोलर चुपचाप उन सभी डिवाइस को नजरअंदाज कर देता है जो आपके मानदंड से मेल नहीं खाते। आपका ऐप सोया रहता है। मुख्य प्रोसेसर सोया रहता है। केवल जब एक हार्ट रेट मॉनिटर का पता चलता है तो हार्डवेयर आपके ऐप को जगाता है और परिणाम वितरित करता है। यह एक क्लब में बाउंसर होने जैसा है जो केवल VIP सूची के लोगों को अंदर जाने देता है। बाकी सभी को दरवाजे पर ही वापस कर दिया जाता है, और आप कभी नहीं जान पाते कि वे वहाँ थे।
ScanFilter का उपयोग करके, आप हार्डवेयर को बता रहे हैं, "मुझे तब तक मत जगाओ जब तक तुम एक हार्ट रेट मॉनिटर न देखो।" यह बैकग्राउंड स्कैनिंग के लिए परम पावर-सेविंग चाल है। पैसिव स्कैनिंग और बैच रिपोर्टिंग के साथ संयुक्त, आप एक ब्लूटूथ स्कैनिंग सिस्टम बना सकते हैं जो न्यूनतम बैटरी प्रभाव के साथ घंटों या दिनों तक चलता है। इसी तरह प्रो-ग्रेड ऐप्स बैटरी लाइफ को नष्ट किए बिना दीर्घकालिक डिवाइस मॉनिटरिंग को संभालते हैं।
बैच स्कैनिंग: दैनिक रिपोर्ट
आइए हमारे सेलिब्रिटी अनुमान पर वापस चलते हैं। कभी-कभी, आपको उस क्षण बाधित होने की आवश्यकता नहीं होती जब आपकी माँ दिखाई देती है। आप दिन के अंत में एक रिपोर्ट पसंद करेंगे: "आज, आपकी माँ दो बार आई, और आपके एजेंट ने एक बार फोन किया।" यह बैच स्कैनिंग है।
स्कैन परिणामों को आपके ऐप को रियल-टाइम में वितरित करने के बजाय, ब्लूटूथ कंट्रोलर उन्हें एकत्र कर सकता है और उन्हें एक बड़े बैच में वितरित कर सकता है। यह एक और अविश्वसनीय पावर-सेविंग सुविधा है। आपका ऐप लंबी अवधि के लिए सो सकता है, फिर जाग सकता है, एक बार में परिणामों का एक पूरा गुच्छा प्रोसेस कर सकता है, और वापस सो सकता है।
आप इसे अपने ScanSettings में setReportDelay() मेथड से सक्षम करते हैं।
val scanSettings = ScanSettings.Builder() // ... अन्य सेटिंग्स ... // परिणाम हर 5 सेकंड (5000 मिलीसेकंड) में वितरित करें .setReportDelay(5000) .build() जब आप रिपोर्ट देरी का उपयोग करते हैं, तो आपका onScanResult कॉलबैक onBatchScanResults द्वारा प्रतिस्थापित हो जाएगा, जो आपको एक List<ScanResult> देता है।
private val scanCallback = object : ScanCallback() { override fun onBatchScanResults(results: List<ScanResult>) { Log.d("BatchScanner", "यह रही आपकी दैनिक रिपोर्ट! ${results.size} डिवाइस मिले।") for (result in results) { // प्रत्येक परिणाम को प्रोसेस करें } } // ... onScanFailed ... } ऊपर दिखाया गया बैच स्कैनिंग तंत्र एंड्रॉइड ब्लूटूथ में सबसे कम उपयोग की जाने वाली पावर-सेविंग सुविधाओं में से एक है, और यह समझना कि यह कैसे काम करता है, आपके ऐप के बैटरी प्रोफाइल को बदल सकता है। आइए ठीक से देखें कि हुड के नीचे क्या हो रहा है और आपको इस तकनीक का उपयोग कब करना चाहिए।
जब आप ऊपर दिए गए कोड में 5000 मिलीसेकंड (5 सेकंड) की रिपोर्ट देरी सेट करते हैं, तो आप स्कैनिंग पाइपलाइन के काम करने के तरीके को मौलिक रूप से बदल रहे हैं। ब्लूटूथ कंट्रोलर द्वारा हर बार डिवाइस देखने पर तुरंत आपके ऐप को जगाने के बजाय, यह एक मेहनती सहायक की तरह काम करता है जो नोट्स लेता है। उन 5 सेकंड के लिए, कंट्रोलर चुपचाप हर स्कैन परिणाम को एकत्र करता है जो उसका सामना करता है, उन्हें अपने आंतरिक बफर में संग्रहीत करता है। आपका ऐप इस समय पूरी तरह से सोया रहता है - कोई CPU साइकिल बर्बाद नहीं होती, कॉन्टेक्स्ट स्विच या प्रोसेस वेक-अप द्वारा कोई बैटरी खत्म नहीं होती।
5 सेकंड की देरी समाप्त होने के बाद, कंट्रोलर एक बैच में सभी संचित परिणामों को आपके onBatchScanResults() कॉलबैक में वितरित करता है। यहीं से पावर सेविंग आती है: अगर 50 डिवाइस का पता चला तो आपके ऐप को 50 बार जगाने के बजाय, यह एक बार जागता है और आपको एक ही बार में सभी 50 परिणाम सौंप देता है। आपका ऐप तब इस बैच को कुशलतापूर्वक प्रोसेस कर सकता है - शायद UI सूची अपडेट करना, डेटा लॉग करना, या विशिष्ट डिवाइस की जांच करना - और फिर अगले बैच के आने तक वापस सो सकता है।
onBatchScanResults() में results पैरामीटर एक List<ScanResult> है, और सूची में प्रत्येक ScanResult बैचिंग अवधि के दौरान सुने गए एकल विज्ञापन का प्रतिनिधित्व करता है। यह ध्यान रखना महत्वपूर्ण है कि यदि वही डिवाइस देरी अवधि के दौरान कई बार विज्ञापन करता है, तो आपको उस डिवाइस के लिए बैच में कई परिणाम प्राप्त हो सकते हैं। सूची स्वचालित रूप से डुप्लीकेट नहीं होती - यदि आपको इसकी आवश्यकता है तो यह आपका काम है।
ऊपर दिए गए उदाहरण में, हम केवल मिले डिवाइस की संख्या लॉग कर रहे हैं और फिर प्रत्येक परिणाम के माध्यम से पुनरावृति कर रहे हैं। एक वास्तविक एप्लिकेशन में, आप अधिक परिष्कृत प्रोसेसिंग करना चाह सकते हैं। उदाहरण के लिए, आप MAC एड्रेस द्वारा कुंजीबद्ध डिवाइस का एक मैप बना सकते हैं ताकि यह ट्रैक किया जा सके कि प्रत्येक डिवाइस ने कितनी बार विज्ञापन किया, दूरी का अनुमान लगाने के लिए औसत RSSI मानों की गणना कर सकते हैं, या केवल उन डिवाइस को प्रोसेस करने के लिए बैच को फ़िल्टर कर सकते हैं जो कुछ मानदंडों को पूरा करते हैं।
चेतावनी: बैच स्कैनिंग एक शक्तिशाली उपकरण है, लेकिन यह हर स्थिति के लिए नहीं है। यदि आपको डिवाइस की उपस्थिति पर तुरंत प्रतिक्रिया करने की आवश्यकता है (उदाहरण के लिए, यदि आप एक "मेरी चाबियाँ ढूंढो" ऐप बना रहे हैं जहां उपयोगकर्ता सक्रिय रूप से खोज रहा है), तो रिपोर्ट देरी आपका दोस्त नहीं है। उपयोगकर्ता परिणाम देखने के लिए 5 सेकंड इंतजार नहीं करना चाहता - वे तत्काल प्रतिक्रिया चाहते हैं। इन मामलों में, तत्काल रिपोर्टिंग के लिए setReportDelay(0) सेट करें।
लेकिन दीर्घकालिक मॉनिटरिंग या डेटा संग्रह परिदृश्यों के लिए, बैच स्कैनिंग बैटरी का सबसे अच्छा दोस्त है। इन उपयोग के मामलों पर विचार करें:
बैकग्राउंड उपस्थिति मॉनिटरिंग: आपका ऐप हर मिनट जांचता है कि उपयोगकर्ता की स्मार्टवॉच अभी भी रेंज में है या नहीं, लेकिन इसे सेकंड-दर-सेकंड अपडेट की आवश्यकता नहीं है।
पर्यावरण संवेदन: आप एक इमारत भर में तापमान सेंसर से डेटा एकत्र कर रहे हैं और केवल हर 30 सेकंड में अपने डैशबोर्ड को अपडेट करने की आवश्यकता है।
बीकन विश्लेषण: आप अपने फोन के BLE विज्ञापनों के आधार पर एक खुदरा स्थान से गुजरने वाले लोगों को ट्रैक कर रहे हैं, और आप हर 10 सेकंड में डेटा एकत्र करते हैं।
रिपोर्ट देरी के लिए स्वीट स्पॉट आपके उपयोग के मामले पर निर्भर करता है। बहुत छोटा (जैसे 1 सेकंड), और आपको ज्यादा लाभ नहीं मिल रहा है, आप अभी भी बार-बार जाग रहे हैं। बहुत लंबा (जैसे 60 सेकंड), और आपका ऐप अनुत्तरदायी महसूस कर सकता है या समय-संवेदनशील घटनाओं को याद कर सकता है। अधिकांश बैकग्राउंड मॉनिटरिंग कार्यों के लिए, 5 से 30 सेकंड के बीच की देरी अच्छी तरह से काम करती है।
एक और बात ध्यान रखने योग्य: बैच स्कैनिंग की सीमाएँ हैं। ब्लूटूथ कंट्रोलर के पास स्कैन परिणामों को संग्रहीत करने के लिए एक सीमित बफर है। यदि आप बहुत लंबी देरी सेट करते हैं और आप सैकड़ों BLE डिवाइस वाले वातावरण में हैं, तो बफर देरी समाप्त होने से पहले भर सकता है। जब ऐसा होता है, तो सबसे पुराने परिणाम हटा दिए जाते हैं। एंड्रॉइड आपको चेतावनी नहीं देता जब ऐसा होता है, इसलिए यदि आप डेटा गायब पाते हैं, तो अपनी रिपोर्ट देरी कम करने या एकत्र किए जा रहे परिणामों की संख्या को कम करने के लिए अधिक आक्रामक फ़िल्टर का उपयोग करने पर विचार करें।
OnFound/OnLost: उपस्थिति का नाटक
एंड्रॉइड 8.0 के बाद से, स्कैनिंग और भी नाटकीय हो गई है। अब आप हार्डवेयर से न केवल यह पूछ सकते हैं कि यह कब एक डिवाइस ढूंढता है, बल्कि यह भी कि यह कब एक खो देता है। यह आपके ScanSettings में CALLBACK_TYPE_FIRST_MATCH और CALLBACK_TYPE_MATCH_LOST फ्लैग का उपयोग करके किया जाता है।
val scanSettings = ScanSettings.Builder() .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH or ScanSettings.CALLBACK_TYPE_MATCH_LOST) .build() अब, आपके ScanCallback में, onScanResult में callbackType पैरामीटर आपको बताएगा कि क्या हुआ।
override fun onScanResult(callbackType: Int, result: ScanResult) { when (callbackType) { ScanSettings.CALLBACK_TYPE_FIRST_MATCH -> { Log.d("PresenceDetector", "उन्हें ढूंढ लिया! ${result.device.address} इमारत में प्रवेश कर चुका है।") } ScanSettings.CALLBACK_TYPE_MATCH_LOST -> { Log.d("PresenceDetector", "वे चले गए! ${result.device.address} इमारत छोड़ चुका है।") } } } ऊपर दिखाया गया उपस्थिति पहचान तंत्र इस बात में एक मौलिक बदलाव का प्रतिनिधित्व करता है कि हम ब्लूटूथ स्कैनिंग के बारे में कैसे सोचते हैं। स्कैनिंग को "यहाँ अभी मैं क्या देख रहा हूँ" की एक निरंतर धारा के रूप में मानने के बजाय, हम अब घटनाओं के साथ काम कर रहे हैं: "यह डिवाइस प्रकट हुआ" और "यह डिवाइस गायब हो गया।" आइए गहराई से देखें कि यह कैसे काम करता है और यह इतना शक्तिशाली क्यों है।
जब आप बिटवाइज OR ऑपरेटर (कोटलिन में or, जावा में |) का उपयोग करके कॉलबैक प्रकार सेट करते हैं, तो आप ब्लूटूथ हार्डवेयर को डिवाइस की उपस्थिति स्थिति को समय के साथ ट्रैक करने के लिए कह रहे हैं। कोड CALLBACK_TYPE_FIRST_MATCH or CALLBACK_TYPE_MATCH_LOST दोनों फ्लैग को जोड़ता है, जिसका अर्थ है कि आप डिवाइस के पहली बार प्रकट होने और गायब होने दोनों पर अधिसूचित होना चाहते हैं। यदि आप केवल एक प्रकार के इवेंट की परवाह करते हैं तो आप इन फ्लैग का व्यक्तिगत रूप से उपयोग कर सकते हैं, लेकिन दोनों को एक साथ उपयोग करने से आपको पूर्ण उपस्थिति जागरूकता मिलती है।
आइए समझते हैं कि "फर्स्ट मैच" और "मैच लॉस्ट" का वास्तव में क्या अर्थ है। जब ब्लूटूथ कंट्रोलर आपके फ़िल्टर से मेल खाने वाले डिवाइस से पहली बार विज्ञापन सुनता है, तो यह एक CALLBACK_TYPE_FIRST_MATCH इवेंट ट्रिगर करता है। यह CALLBACK_TYPE_ALL_MATCHES (डिफ़ॉल्ट) से अलग है, जो हर बार डिवाइस विज्ञापन करने पर ट्रिगर होगा। एक डिवाइस प्रति सेकंड कई बार विज्ञापन कर सकता है, इसलिए अंतर महत्वपूर्ण है। FIRST_MATCH के साथ, आपको एक नोटिफिकेशन मिलता है जब डिवाइस आपकी स्कैनिंग रेंज में प्रवेश करता है, न कि उसके विज्ञापन जारी रखने पर नोटिफिकेशन की बाढ़।
CALLBACK_TYPE_MATCH_LOST इवेंट और भी दिलचस्प है। ब्लूटूथ कंट्रोलर ट्रैक रखता है कि उसने प्रत्येक डिवाइस को आखिरी बार कब सुना था। यदि एक डिवाइस विज्ञापन करना बंद कर देता है (क्योंकि यह रेंज से बाहर चला गया, बंद कर दिया गया, या इसकी बैटरी मर गई), तो कंट्रोलर अनुपस्थिति को नोटिस करता है और एक MATCH_LOST इवेंट ट्रिगर करता है। यह स्वचालित रूप से होता है: आपको अपने ऐप में मैन्युअल रूप से टाइमस्टैम्प ट्रैक करने या टाइमआउट तर्क लागू करने की आवश्यकता नहीं है। हार्डवेयर आपके लिए यह करता है।
लेकिन हार्डवेयर को कैसे पता चलता है कि डिवाइस "खो गया" है? यह एक आंतरिक टाइमआउट का उपयोग करता है। यदि कंट्रोलर ने एक निश्चित अवधि (आमतौर पर कुछ सेकंड, हालांकि सटीक अवधि कार्यान्वयन-निर्भर है और ऐप्स के लिए उजागर नहीं की गई है) के लिए डिवाइस से नहीं सुना है, तो यह डिवाइस को खोया हुआ मानता है। इसका अर्थ है कि डिवाइस के वास्तव में रेंज छोड़ने और जब आपको MATCH_LOST कॉलबैक मिलता है के बीच थोड़ी देरी होती है, लेकिन यह देरी आमतौर पर उपस्थिति पहचान उपयोग के मामलों के लिए स्वीकार्य होती है।
ऊपर दिए गए कोड उदाहरण में, हम विभिन्न कॉलबैक प्रकारों को संभालने के लिए एक when एक्सप्रेशन का उपयोग कर रहे हैं। जब हमें एक FIRST_MATCH प्राप्त होता है, तो हम जानते हैं कि डिवाइस अभी हमारी स्कैनिंग रेंज में प्रवेश कर चुका है, इसलिए हम "उन्हें ढूंढ लिया!" लॉग करते हैं। यह आपके फोन के पास आने पर दरवाजा अनलॉक करने, या आपके फिटनेस ट्रैक का पता चलने पर डेटा सिंक शुरू करने जैसी कार्रवाइयों को ट्रिगर करने के लिए एकदम सही है।
जब हमें एक MATCH_LOST प्राप्त होता है, तो हम जानते हैं कि डिवाइस हमारी स्कैनिंग रेंज छोड़ चुका है या विज्ञापन करना बंद कर दिया है, इसलिए हम "वे चले गए!" लॉग करते हैं। यह आपके फोन के जाने पर दरवाजा लॉक करने, या आपके ट्रैकर के डिस्कनेक्ट होने पर डेटा सिंक रोकने जैसी सफाई कार्रवाइयों को ट्रिगर करने के लिए आदर्श है।
यह उपस्थिति पहचान परिदृश्यों के लिए अविश्वसनीय रूप से उपयोगी है। क्या आपका स्मार्ट लॉक रेंज में है? क्या आपका फिटनेस ट्रैकर अभी भी कनेक्टेड है? क्या उपयोगकर्ता का फोन पास में है? अब आप हार्डवेयर-स्तर की निश्चितता के साथ जान सकते हैं, और आप उपस्थिति में परिवर्तनों पर प्रतिक्रिया कर सकते हैं बिना लगातार पोलिंग किए या अपने ऐप कोड में जटिल स्टेट मशीन बनाए।
यहाँ एक व्यावहारिक उदाहरण है कि आप इसका स्मार्ट होम ऐप में कैसे उपयोग कर सकते हैं:
private val presenceCallback = object : ScanCallback() { override fun onScanResult(callbackType: Int, result: ScanResult) { when (callbackType) { ScanSettings.CALLBACK_TYPE_FIRST_MATCH -> { // उपयोगकर्ता का फोन पता चला - वे घर पर हैं! Log.d("SmartHome", "घर में आपका स्वागत है! दरवाजा अनलॉक हो रहा है और लाइट्स चालू हो रही हैं।") unlockFrontDoor() turnOnLights() adjustThermostat(COMFORTABLE_TEMP) } ScanSettings.CALLBACK_TYPE_MATCH_LOST -> { // उपयोगकर्ता का फोन चला गया - वे चले गए! Log.d("SmartHome", "अलविदा! दरवाजा लॉक हो रहा है और अवे मोड में प्रवेश कर रहा है।") lockFrontDoor() turnOffLights() adjustThermostat(ENERGY_SAVING_TEMP) armSecuritySystem() } } } override fun onScanFailed(errorCode: Int) { Log.e("SmartHome", "उपस्थिति पहचान विफल: $errorCode") } } एक महत्वपूर्ण विचार: FIRST_MATCH और MATCH_LOST, CALLBACK_TYPE_ALL_MATCHES के साथ परस्पर अनन्य हैं। यदि आप उन्हें ALL_MATCHES के साथ जोड़ते हैं, तो व्यवहार अपरिभाषित हो जाता है और डिवाइस के अनुसार भिन्न होता है। या तो निरंतर रिपोर्टिंग के लिए ALL_MATCHES पर टिके रहें, या उपस्थिति पहचान के लिए FIRST_MATCH/MATCH_LOST - दोनों को एक साथ उपयोग करने का प्रयास न करें।
साथ ही, ध्यान रखें कि उपस्थिति पहचान हार्डवेयर फ़िल्टरिंग के साथ संयुक्त होने पर सबसे अच्छा काम करती है। यदि आप फ़िल्टर के बिना सभी डिवाइस के लिए स्कैन कर रहे हैं, तो कंट्रोलर को रेंज में हर एक BLE डिवाइस की उपस्थिति स्थिति को ट्रैक करना होगा, जो इसकी आंतरिक ट्रैकिंग टेबल को अभिभूत कर सकता है। उपस्थिति पहचान का उपयोग करते समय आप जिन डिवाइस की परवाह करते हैं उन्हें सीमित करने के लिए हमेशा ScanFilter का उपयोग करें।
इन उन्नत तकनीकों को संयोजित करके - हार्डवेयर फ़िल्टरिंग, बैच स्कैनिंग, और उपस्थिति पहचान - आप अविश्वसनीय रूप से परिष्कृत और पावर-कुशल ब्लूटूथ एप्लिकेशन बना सकते हैं। आप अब केवल एक डेवलपर नहीं हैं। आप एक ब्लूटूथ जादूगर हैं, जो ऐसे ऐप्स बनाने की शक्ति रखता है जो अपने परिवेश से अवगत हैं, परिवर्तनों के प्रति रिस्पॉन्सिव हैं, और बैटरी लाइफ का सम्मान करते हैं।
अब, आइए देखें कि हम इन जादुई शक्तियों को वास्तविक दुनिया में कहाँ लागू कर सकते हैं।
वास्तविक दुनिया के उपयोग के मामले: जहां ब्लूटूथ सड़क से टकराता है
ठीक है, हमने बहुत सारे अच्छे नए गुर सीख लिए हैं। हम मूल रूप से इस बिंदु पर ब्लूटूथ ब्लैक बेल्ट हैं। लेकिन अगर हम इसका उपयोग अच्छे के लिए (या कम से कम एक कूल ऐप के लिए) नहीं करते हैं तो इस सारी शक्ति का क्या फायदा? आइए कुछ वास्तविक दुनिया के परिदृश्यों का पता लगाएं जहां AOSP 16 में नई सुविधाएँ एक अच्छे ऐप को एक महान ऐप में बदल सकती हैं।
1. "मेरा सब कुछ ढूंढो" ऐप
हम सब वहाँ रहे हैं। आप काम के लिए लेट हो रहे हैं, और आपकी चाबियों ने दूसरे आयाम में आपके सोफे के कुशन के अंदर छुपा-छुपी खेलने का फैसला किया है। यह BLE ट्रैकर के लिए क्लासिक उपयोग का मामला है।
पुराना तरीका: आपका ऐप लगातार एक्टिव स्कैन कर रहा होता, बैटरी खत्म करते हुए जब आप बेतहाशा खोजते हैं। यह आपके घर में हर ट्रैकर से कनेक्ट होगा सिर्फ यह देखने के लिए कि यह सही है या नहीं।
AOSP 16 तरीका: आपका ऐप बैकग्राउंड में आपके ट्रैकर के विशिष्ट सर्विस UUID के लिए हार्डवेयर फ़िल्टर के साथ एक पैसिव स्कैन चलाता है। बैटरी प्रभाव न्यूनतम है। जब आप अपनी चाबियाँ ढूंढने के लिए ऐप खोलते हैं, तो यह पहले से ही जानता है कि वे घर में हैं क्योंकि यह चुपचाप सुन रहा है। आप "ढूंढो" बटन दबाते हैं, ऐप कनेक्ट होता है, और आपकी चाबियाँ सोफे के कुशन के अंदर से चिल्लाना शुरू कर देती हैं। और अगर कनेक्शन विफल होता है? बॉन्ड लॉस कारण आपको बताता है कि ट्रैकर की बैटरी मर गई, इसलिए आप एक मृत डिवाइस की तलाश नहीं कर रहे हैं।
2. स्मार्ट सुपरमार्केट
एक ऐसे ऐप की कल्पना करें जो आपको उत्पादों के लिए कूपन देता है जैसे ही आप स्टोर में उनके पास से गुजरते हैं। यह प्रॉक्सिमिटी मार्केटिंग का सपना है, एक सपना जो ऐतिहासिक रूप से, आपने अनुमान लगाया, बैटरी ड्रेन द्वारा विफल किया गया है।
पुराना तरीका: ऐप को लगातार बीकन के लिए स्कैन करने की आवश्यकता होगी, जिससे यूजर का फोन एक गर्म आलू और चेकआउट लाइन तक पहुंचने तक एक मृत बैटरी में बदल जाएगा।
AOSP 16 तरीका: सुपरमार्केट प्रत्येक गलियारे में BLE बीकन रखता है। आपका ऐप एक पैसिव, बैच्ड स्कैन का उपयोग करता है। यह हर मिनट या तो जागता है, उन सभी बीकन की सूची प्राप्त करता है जिन्हें उसने देखा है, और वापस सो जाता है। जब यह देखता है कि आप कुकी गलियारे में पांच मिनट से घूम रहे हैं (यह जानता है, यह हमेशा जानता है), तो यह विज्ञापन से सर्विस UUID का उपयोग करके "कुकी गलियारा बीकन" की पहचान करता है और आपको ओरियो के लिए एक कूपन भेजता है। यह लक्षित है, यह कुशल है, और यह भुगतान करने से पहले आपकी बैटरी को नहीं मारता।
3. अत्यधिक आसक्त स्मार्ट होम
आपका स्मार्ट होम होना चाहिए, ठीक है, स्मार्ट। इसे पता होना चाहिए कि आप कब घर पर हैं और कब आप चले गए हैं। इसे आपके पीछे दरवाजा लॉक करना चाहिए और जब आप पहुंचें तो लाइट चालू करनी चाहिए।
पुराना तरीका: आपको GPS (एक कुख्यात बैटरी हॉग) या वाई-फाई कनेक्शन पर भरोसा करना होगा, जो अविश्वसनीय हो सकते हैं। BLE एक विकल्प था, लेकिन लगातार स्कैनिंग एक समस्या थी।
AOSP 16 तरीका: आपका फोन चाबी है। आपका स्मार्ट हब (सेंट्रल डिवाइस के रूप में कार्य करता हुआ) एक निरंतर, कम-पावर पैसिव स्कैन चलाता है। जब यह आपके फोन का BLE विज्ञापन देखता है, तो यह जानता है कि आप घर पर हैं। लेकिन क्या होगा यदि आप बस घर के पास से गुजरते हैं? यहीं OnFound/OnLost फीचर काम आता है। हब को केवल "वेलकम होम" सीक्वेंस ट्रिगर करने के लिए कॉन्फ़िगर किया जा सकता है जब उसने आपके डिवाइस को लगातार एक मिनट तक देखा हो (OnFound), और "गुडबाय" सीक्वेंस केवल तभी ट्रिगर करने के लिए जब उसने आपको पांच मिनट तक नहीं देखा हो (OnLost)। यह एक होशियार, अधिक विश्वसनीय उपस्थिति पहचान प्रणाली है जो अंततः स्मार्ट होम को... स्मार्ट महसूस कराती है।
4. कॉर्पोरेट एसेट ट्रैकर
एक बड़े अस्पताल या गोदाम में, महंगे, मोबाइल उपकरण (जैसे IV पंप या फोर्कलिफ्ट) पर नज़र रखना एक बड़ी चुनौती है। BLE टैग समाधान हैं।
पुराना तरीका: कर्मचारियों को इन्वेंट्री लेने के लिए टैबलेट के साथ घूमना पड़ता था, एक्टिव स्कैन करते हुए। यह धीमा, मैनुअल, और अक्षम है।
AOSP 16 तरीका: इमारत भर में फिक्स्ड BLE गेटवे का एक नेटवर्क स्थापित किया गया है। प्रत्येक गेटवे एक साधारण डिवाइस (जैसे रास्पबेरी पाई) है जो एक निरंतर पैसिव स्कैन चलाता है। वे एसेट टैग से सभी विज्ञापन डेटा एकत्र करते हैं और इसे एक केंद्रीय सर्वर पर भेजते हैं। सर्वर अब वास्तविक समय में देख सकता है कि IV पंप #34 कमरा 201 में है, और फोर्कलिफ्ट #3 वर्तमान में लोडिंग बे में है। कोई मैनुअल स्कैनिंग आवश्यक नहीं। यह एक कम लागत, कम पावर, रियल-टाइम लोकेशन सिस्टम है, पैसिव स्कैनिंग की दक्षता के लिए धन्यवाद।
ये सिर्फ कुछ उदाहरण हैं। फिटनेस ट्रैकर से औद्योगिक सेंसर तक, AOSP 16 में नई ब्लूटूथ सुविधाएँ उन ऐप्स के निर्माण के लिए संभावनाओं की एक दुनिया खोलती हैं जो न केवल शक्तिशाली हैं बल्कि आपके यूजर की बैटरी के प्रति विनम्र भी हैं। अब, आइए बात करते हैं कि कैसे सुनिश्चित करें कि हमारा चमकदार नया ऐप न केवल नए डिवाइस पर बल्कि सभी डिवाइस पर काम करता है।
API संस्करण जांच: अपने ऐप को क्रैश न होने देना
तो, आपने AOSP 16 के Q4 रिलीज़ की सभी नई हॉटनेस का उपयोग करके एक सुंदर, बैटरी-सिपिंग ऐप बनाया है। आप इसे शिप करने, करोड़पति बनने, और एक निजी द्वीप पर सेवानिवृत्त होने के लिए तैयार हैं। लेकिन फिर, एक बग रिपोर्ट आती है। आपका ऐप एकदम नए एंड्रॉइड 16 डिवाइस पर क्रैश हो रहा है। क्या हुआ?!
मेरे दोस्त, API संस्करण जांच की अद्भुत दुनिया में आपका स्वागत है। एंड्रॉइड के नए रिलीज़ शेड्यूल के साथ, यह पहले से कहीं अधिक महत्वपूर्ण (और थोड़ा अधिक जटिल) हो गया है।
समस्या: दो एंड्रॉइड 16 की कहानी
जैसा कि हमने चर्चा की, 2025 ने हमें दो एंड्रॉइड 16 रिलीज़ दीं:
Q2 रिलीज़: मुख्य "बकलावा" रिलीज़। आइए इसे API लेवल 36.0 कहते हैं।
Q4 रिलीज़: माइनर, फीचर-ड्रॉप रिलीज़। यहीं हमारे नए ब्लूटूथ खिलौने रहते हैं। आइए इसे API लेवल 36.1 कहते हैं।
हमारा नया पैसिव स्कैनिंग API, setScanType(), केवल 36.1 और बाद के संस्करणों पर मौजूद है। यदि आप इसे किसी ऐसे डिवाइस पर कॉल करने का प्रयास करते हैं जो प्रारंभिक Q2 रिलीज़ (36.0) चला रहा है, तो आपका ऐप NoSuchMethodError के साथ क्रैश हो जाएगा। यह उस मेनू आइटम के लिए पूछने के डिजिटल समकक्ष है जिसे केवल कल रात जोड़ा गया था। शेफ (आपका ऐप) बस भ्रमित हो जाता है और मंदी का शिकार हो जाता है।
पुराने रक्षक: SDK_INT
सालों से, API लेवल की जांच के लिए हमारे भरोसेमंद दोस्त Build.VERSION.SDK_INT रहे हैं। यह सरल और प्रभावी है।
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12 (S) या उच्चतर से एक API का उपयोग करें } लेकिन SDK_INT केवल प्रमुख रिलीज़ के बारे में जानता है। एंड्रॉइड 16 Q2 और Q4 दोनों के लिए, SDK_INT केवल 36 रिपोर्ट करेगा। इसे माइनर संस्करण के बारे में कोई जानकारी नहीं है। यह किसी से उनकी उम्र पूछने जैसा है, और वे सिर्फ "तीस के दशक" कहते हैं। बहुत विशिष्ट नहीं।
नई हॉटनेस: SDK_INT_FULL
इसे हल करने के लिए, एंड्रॉइड टीम ने हमें एक नया, अधिक सटीक उपकरण दिया है: Build.VERSION.SDK_INT_FULL। यह स्थिरांक प्रमुख और माइनर दोनों संस्करण संख्याओं के बारे में जानता है। और इसके साथ जाने के लिए, हमारे पास संस्करण कोड का एक नया सेट है: Build.VERSION_CODES_FULL।
तो, हमारे नए पैसिव स्कैनिंग API को सुरक्षित रूप से कॉल करने के लिए, हमें एक अधिक विशिष्ट जांच करने की आवश्यकता है:
// आइए अपना ScanSettings बनाएं val scanSettingsBuilder = ScanSettings.Builder() .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) // अब, आइए जांचें कि क्या हम पैसिव जा सकते हैं if (Build.VERSION.SDK_INT_FULL >= Build.VERSION_CODES_FULL.BAKLAVA_1) { Log.d("ApiCheck", "यह डिवाइस कूल है। पैसिव जा रहे हैं।") // यह Q4 रिलीज़ (36.1) का नया API है scanSettingsBuilder.setScanType(ScanSettings.SCAN_TYPE_PASSIVE) } else { Log.d("ApiCheck", "यह डिवाइस ओल्ड स्कूल है। एक्टिव स्कैनिंग पर टिके रहें।") // उन डिवाइस के लिए फॉलबैक जिनमें नया API नहीं है // हमें यहां कुछ भी करने की ज़रूरत नहीं है, क्योंकि एक्टिव डिफ़ॉल्ट है } val scanSettings = scanSettingsBuilder.build() ग्रेसफुल डिग्रेडेशन: स्टाइल के साथ गिरने की कला
यह हमें एक महत्वपूर्ण अवधारणा पर लाता है: ग्रेसफुल डिग्रेडेशन। इसका अर्थ है कि आपका ऐप पुराने डिवाइस पर भी काम करना चाहिए, भले ही यह नवीनतम और महानतम सुविधाओं का उपयोग न कर सके। इसे ग्रेसफुली फॉलबैक करना चाहिए।
ऊपर हमारे उदाहरण में, यदि setScanType मेथड उपलब्ध नहीं है, तो हम बस... इसे कॉल नहीं करते। ऐप एक सामान्य, एक्टिव स्कैन पर डिफ़ॉल्ट हो जाएगा। यह उतना बैटरी-कुशल नहीं होगा, लेकिन यह अभी भी काम करेगा। पुराने डिवाइस पर यूजर को एक कार्यात्मक ऐप मिलता है, और नए डिवाइस पर यूजर को एक अधिक अनुकूलित अनुभव मिलता है। हर कोई जीतता है।
यहाँ एक टेबल है जो आपको यह याद रखने में मदद करेगी कि किस जांच का उपयोग कब करना है:
| यदि आप किसी API का उपयोग कर रहे हैं... | इस जांच का उपयोग करें... |
|---|---|
| एक प्रमुख एंड्रॉइड रिलीज़ से (उदाहरण के लिए, एंड्रॉइड 16 Q2) | if (SDK_INT >= VERSION_CODES.BAKLAVA) |
| एक माइनर, फीचर-ड्रॉप रिलीज़ से (उदाहरण के लिए, एंड्रॉइड 16 Q4) | if (SDK_INT_FULL >= VERSION_CODES_FULL.BAKLAVA_1) |
इस नई API जांच में महारत हासिल करना गैर-परक्राम्य है। यह आधुनिक एंड्रॉइड ऐप लिखने की कुंजी है जो नवीन और स्थिर दोनों हैं। अब जब हम जानते हैं कि एक मजबूत ऐप कैसे बनाया जाता है, तो आइए बात करते हैं कि जब यह अनिवार्य रूप से टूटता है तो इसे कैसे ठीक किया जाए।
परीक्षण और डीबगिंग: मजेदार हिस्सा (किसी ने कभी नहीं कहा)
सॉफ्टवेयर विकास में दो सार्वभौमिक सत्य हैं:
यह मेरी मशीन पर काम करता है, और
यह लाइव डेमो के दौरान सबसे शानदार तरीके से टूट जाएगा।
ब्लूटूथ विकास, विशेष रूप से, इस दूसरे सत्य में आनंद लेता प्रतीत होता है। यह एक अस्थिर, अदृश्य शक्ति है जिसका डेवलपर्स के खिलाफ व्यक्तिगत प्रतिशोध प्रतीत होता है।
तो, हम कैसे वापस लड़ते हैं? एक ठोस परीक्षण और डीबगिंग रणनीति के साथ। यह ग्लैमरस नहीं है, लेकिन पागल रहने का यही एकमात्र तरीका है।
एमुलेटर: मेक-बिलीव की भूमि
एंड्रॉइड स्टूडियो का एमुलेटर एक शानदार उपकरण है। यह तेज़ है, यह सुविधाजनक है, और यह सभी प्रकार के डिवाइस का अनुकरण कर सकता है। और ब्लूटूथ के लिए? यह... थोड़ा मदद कर सकता है। एमुलेटर में वर्चुअल ब्लूटूथ सपोर्ट है। आप इसे सक्षम कर सकते हैं, और आपका ऐप सोचेगा कि उसके पास एक ब्लूटूथ एडाप्टर है। यह आपके UI का परीक्षण करने और यह सुनिश्चित करने के लिए बहुत अच्छा है कि जब आपका ऐप BluetoothLeScanner प्राप्त करने का प्रयास करता है तो यह क्रैश न हो।
लेकिन यहाँ पकड़ है: यह असली नहीं है। एमुलेटर वास्तव में आपके कमरे में रेडियो तरंगों के साथ इंटरैक्ट नहीं कर सकता। आप इसका उपयोग अपने वास्तविक जीवन के BLE हेडफ़ोन खोजने के लिए नहीं कर सकते। उसके लिए, आपको वास्तविक दुनिया में उद्यम करने की आवश्यकता है।
वास्तविक दुनिया: जहां बग रहते हैं
वास्तविक, भौतिक डिवाइस पर परीक्षण का कोई विकल्प नहीं है। हर फोन निर्माता का अपना ब्लूटूथ स्टैक का विशेष स्वाद होता है, इसका अपना विचित्र एंटीना डिज़ाइन, और आपके जीवन को कठिन बनाने का अपना अनूठा तरीका। एक स्कैन जो Google Pixel पर पूरी तरह से काम करता है, दूसरे ब्रांड पर बुरी तरह विफल हो सकता है। जानने का एकमात्र तरीका परीक्षण करना है।
आपके परीक्षण शस्त्रागार में शामिल होना चाहिए:
विभिन्न फोन: विभिन्न ब्रांड, विभिन्न एंड्रॉइड संस्करण। जितना अधिक, उतना बेहतर।
विभिन्न BLE पेरिफेरल: केवल एक प्रकार के डिवाइस के साथ परीक्षण न करें। कुछ अलग बीकन, सेंसर, या वियरेबल प्राप्त करें। आप चकित होंगे कि वे कितने अलग व्यवहार करते हैं।
सामान्य त्रुटियाँ: संदिग्ध हमेशा की तरह
जब आपका स्कैन अनिवार्य रूप से विफल होता है, तो यह आपको एक त्रुटि कोड देगा। यहाँ कुछ सबसे आम दोषी हैं:
| त्रुटि कोड | समस्या | इसे कैसे ठीक करें |
|---|---|---|
| SCAN_FAILED_ALREADY_STARTED | आपने एक स्कैन शुरू करने का प्रयास किया जो पहले से चल रहा था। | आप बहुत उत्साहित हो गए। सुनिश्चित करें कि आप stopScan() को बीच में कॉल किए बिना startScan() को कई बार कॉल नहीं कर रहे हैं। |
| SCAN_FAILED_APPLICATION_REGISTRATION_FAILED | आपके ऐप के सेटअप में मौलिक रूप से कुछ गलत है। | यह एक अस्पष्ट और अनुपयोगी त्रुटि है। आमतौर पर इसका मतलब है कि आपको अपनी अनुमतियों के साथ कोई समस्या है या सिस्टम बस बुरा दिन बिता रहा है। ब्लूटूथ को पुनः आरंभ करने का प्रयास करें। |
| SCAN_FAILED_INTERNAL_ERROR | ब्लूटूथ स्टैक को पैनिक अटैक आया। | यह क्लासिक "यह तुम नहीं हो, यह मैं हूँ" त्रुटि है। यह डिवाइस के ब्लूटूथ कंट्रोलर के साथ एक आंतरिक समस्या है। आप बाद में फिर से प्रयास करने के अलावा ज्यादा कुछ नहीं कर सकते। |
| SCAN_FAILED_FEATURE_UNSUPPORTED | आपने एक ऐसी सुविधा का उपयोग करने का प्रयास किया जिसका हार्डवेयर समर्थन नहीं करता। | आप किसी ऐसे डिवाइस पर बैच स्कैनिंग का उपयोग करने का प्रयास कर रहे होंगे जो इसका समर्थन नहीं करता। अपने API संस्करण जांच का उपयोग करें! |
डीबगिंग उपकरण: आपका भूत-शिकार किट
जब चीजें गलत होती हैं, तो आपको अदृश्य ब्लूटूथ की दुनिया में क्या हो रहा है यह देखने के लिए सही उपकरणों की आवश्यकता होती है।
logcat: यह आपका सबसे अच्छा दोस्त है। अपने लॉग स्टेटमेंट के साथ उदार रहें। जब आप स्कैन शुरू करते हैं, जब आप स्कैन रोकते हैं, जब आप एक डिवाइस ढूंढते हैं, और जब स्कैन विफल होता है तो लॉग करें। अपने ऐप के टैग के लिए एक फ़िल्टर बनाएं ताकि आप शोर के माध्यम से सिग्नल देख सकें।
एंड्रॉइड का ब्लूटूथ HCI स्नूप लॉग: यह ब्लूटूथ डीबगिंग का पवित्र कंघी है। यह एक डेवलपर विकल्प है जो आपके डिवाइस में आने या जाने वाले हर एक ब्लूटूथ पैकेट को रिकॉर्ड करता है। यह अविश्वसनीय रूप से विस्तृत और भारी हो सकता है, लेकिन यह अंतिम सत्य का स्रोत है। आप Wireshark जैसे टूल में जनरेटेड लॉग फ़ाइल खोल सकते हैं ताकि आपके फोन और BLE डिवाइस के बीच कच्ची, अनफ़िल्टर्ड बातचीत देख सकें। यह रेडियो तरंगों पर वायरटैप होने जैसा है।
मोबाइल के लिए nRF Connect: यह नॉर्डिक सेमीकंडक्टर का एक मुफ्त ऐप है, और यह किसी भी BLE डेवलपर के लिए एक आवश्यक उपकरण है। यह आपको डिवाइस के लिए स्कैन करने, उनके विज्ञापन डेटा देखने, उनसे कनेक्ट होने, और उनकी GATT सेवाओं का पता लगाने देता है। अगर आपका ऐप कोई डिवाइस नहीं ढूंढ सकता, तो सबसे पहले आपको यह देखना चाहिए कि क्या nRF Connect कर सकता है। अगर यह नहीं कर सकता, तो समस्या संभवतः पेरिफेरल के साथ है, आपके ऐप के साथ नहीं।
ब्लूटूथ का परीक्षण और डीबगिंग एक मैराथन है, स्प्रिंट नहीं। इसके लिए धैर्य, एक व्यवस्थित दृष्टिकोण, और आत्म-हीन हास्य की एक स्वस्थ खुराक की आवश्यकता होती है। लेकिन सही उपकरणों और तकनीकों के साथ, आप जानवर को वश में कर सकते हैं।
अब, आइए बात करते हैं कि कैसे सुनिश्चित करें कि हमारा अच्छा व्यवहार वाला ऐप प्रदर्शन के मामले में भी एक अच्छा नागरिक है।
प्रदर्शन और सर्वोत्तम अभ्यास: एक अच्छा ब्लूटूथ नागरिक कैसे बनें
काम करने वाला कोड लिखना एक बात है। ऐसा कोड लिखना जो अच्छी तरह से काम करे, कुशल हो, और आपके यूजर्स को अपना फोन दीवार पर फेंकने पर मजबूर न करे, बिल्कुल दूसरी बात है। जब ब्लूटूथ की बात आती है, तो एक अच्छा नागरिक होने का अर्थ एक चीज है: बैटरी, बैटरी, बैटरी।
ब्लूटूथ रेडियो हार्डवेयर का एक शक्तिशाली टुकड़ा है, लेकिन यह प्यासा भी है। हर पल जब यह सक्रिय होता है, यह पावर पी रहा है। आपका काम यह सुनिश्चित करना है कि यह केवल तभी पी रहा है जब बिल्कुल आवश्यक हो। यहाँ एक अच्छे ब्लूटूथ नागरिक होने के सुनहरे नियम हैं।
1. स्कैन न करें यदि आपको नहीं करना है
यह स्पष्ट लगता है, लेकिन यह सबसे आम गलती है। स्कैन शुरू करने के बारे में सोचने से पहले, अपने आप से पूछें: "क्या मुझे वास्तव में इसे अभी करने की आवश्यकता है?" यदि उपयोगकर्ता उस स्क्रीन पर नहीं है जिसे स्कैन परिणामों की आवश्यकता है, तो स्कैन न करें। यदि ऐप बैकग्राउंड में है, तो अतिरिक्त आलोचनात्मक बनें। बैकग्राउंड स्कैनिंग बैटरी पर एक बड़ा खर्च है और एंड्रॉइड द्वारा इसी कारण से भारी रूप से प्रतिबंधित है।
2. अपना स्कैन रोकें!
मैं इसे फिर से कहने जा रहा हूँ क्योंकि यह इतना महत्वपूर्ण है: जब आपका काम हो जाए तो हमेशा अपना स्कैन रोकें। एक स्कैन जो चलता रहता है आपकी बैटरी के लिए एक टपकते नल की तरह है। यह तब तक खत्म करेगा जब तक कुछ भी नहीं बचेगा। सर्वोत्तम अभ्यास अपने स्कैन जीवनचक्र को अपने UI जीवनचक्र से जोड़ना है।
override fun onPause() { super.onPause() // उपयोगकर्ता स्क्रीन नहीं देख सकता, इसलिए उन्हें परिणामों की आवश्यकता नहीं है। stopBleScan() } override fun onResume() { super.onResume() // उपयोगकर्ता स्क्रीन पर वापस आ गया है, आइए फिर से स्कैन शुरू करें। startBleScan() } यदि आप वह डिवाइस ढूंढ लेते हैं जिसकी आप तलाश कर रहे हैं, तो तुरंत स्कैन रोकें। खोजते रहने की कोई आवश्यकता नहीं है।
3. सही स्कैन मोड चुनें
ScanSettings आपको कुछ अलग मोड देता है। बुद्धिमानी से चुनें।
SCAN_MODE_LOW_POWER: यह आपका डिफ़ॉल्ट, रोजमर्रा का मोड है। यह अंतराल में स्कैन करता है, खोज गति और बैटरी लाइफ को संतुलित करता है। अधिकांश फोरग्राउंड स्कैनिंग के लिए इसका उपयोग करें।
SCAN_MODE_BALANCED: एक मध्य मैदान। यह लो पावर मोड से अधिक बार स्कैन करता है।
SCAN_MODE_LOW_LATENCY: यह "मुझे इसे अभी ढूंढना है" मोड है। यह लगातार स्कैन करता है। यह डिवाइस को सबसे तेज़ी से ढूंढेगा, लेकिन यह आपकी बैटरी को भी सबसे तेज़ी से खत्म करेगा। केवल छोटे, महत्वपूर्ण कार्यों के लिए इसका उपयोग करें।
SCAN_MODE_OPPORTUNISTIC: यह परम पैसिव मोड है। आपका ऐप बिल्कुल भी स्कैन ट्रिगर नहीं करता। यह केवल परिणाम प्राप्त करता है यदि कोई अन्य ऐप स्कैन कर रहा होता है। यह शून्य अतिरिक्त बैटरी का उपयोग करता है, लेकिन आपको परिणाम मिलने की कोई गारंटी नहीं है। गैर-महत्वपूर्ण बैकग्राउंड अपडेट के लिए इसका उपयोग करें।
और निश्चित रूप से, यदि आप AOSP 16 QPR2 या बाद के संस्करण पर हैं, तो जब भी आपको स्कैन रिस्पांस डेटा की आवश्यकता न हो, setScanType(SCAN_TYPE_PASSIVE) का उपयोग करें। यह पावर दक्षता का नया राजा है।
4. हार्डवेयर फ़िल्टरिंग और बैचिंग का उपयोग करें
हमने इसे उन्नत सेक्शन में कवर किया, लेकिन यह एक सर्वोत्तम अभ्यास है जिसे दोहराने लायक है। यदि आप एक विशिष्ट डिवाइस की तलाश कर रहे हैं, तो ScanFilter का उपयोग करें। यदि आप एक लंबे समय तक चलने वाला स्कैन कर रहे हैं, तो अपने परिणामों को बैच करने के लिए setReportDelay() का उपयोग करें। ये दो तकनीकें काम को पावर-कुशल ब्लूटूथ कंट्रोलर पर ऑफलोड करती हैं और आपके ऐप के कोड को सोने देती हैं, जो बैटरी बचाने का नंबर एक तरीका है।
5. मेमोरी का ध्यान रखें
आपके ऐप द्वारा प्राप्त प्रत्येक ScanResult ऑब्जेक्ट मेमोरी लेता है। यदि आप सैकड़ों BLE डिवाइस वाले भीड़-भाड़ वाले क्षेत्र में हैं, और आप फ़िल्टर का उपयोग नहीं कर रहे हैं, तो आपका ऐप जल्दी से अभिभूत हो सकता है और मेमोरी से बाहर हो सकता है। यह फ़िल्टरिंग इतना महत्वपूर्ण होने का एक और कारण है। केवल वही परिणाम प्राप्त करें जिनकी आप वास्तव में परवाह करते हैं।
इन नियमों का पालन करके, आप एक ब्लूटूथ ऐप बना सकते हैं जो न केवल शक्तिशाली और फीचर-रिच है बल्कि आपके यूजर के डिवाइस का भी सम्मान करता है। आप एक सच्चे ब्लूटूथ सेंसेई होंगे। अब, आइए चीजों को समेटें और भविष्य की ओर देखें।
निष्कर्ष: भविष्य पैसिव है (और यह ठीक है)
हम काफी यात्रा पर रहे हैं, है ना? हम क्लासिक ब्लूटूथ के अंधेरे युग में समय में पीछे गए, BLE के पुनर्जागरण को देखा, और AOSP 16 की बहादुर नई दुनिया में उभरे। हमने पैसिव स्कैनिंग के साथ साइलेंट निंजा बनना सीखा, बॉन्ड लॉस कारणों के साथ जासूस खेला, और विज्ञापनों से सर्विस UUID के साथ स्पीड डेटिंग की कला में महारत हासिल की।
यदि इन सब से एक बड़ा निष्कर्ष है, तो वह यह है कि एंड्रॉइड पर ब्लूटूथ का भविष्य होशियार, अधिक कुशल, और बहुत कम निराशाजनक है। एंड्रॉइड टीम स्पष्ट रूप से डेवलपर्स के दर्द बिंदुओं को सुन रही है और हमें बेहतर, अधिक बैटरी-फ्रेंडली ऐप बनाने के लिए आवश्यक उपकरण दे रही है। पैसिव स्कैनिंग की शुरूआत केवल एक नई सुविधा नहीं है - यह दर्शन में एक बदलाव है। यह एक स्वीकृति है कि कभी-कभी, संवाद करने का सबसे अच्छा तरीका बस सुनना है।
डेवलपर्स के रूप में, ये नए उपकरण हमें सरल "कनेक्ट और स्ट्रीम" उपयोग के मामलों से आगे बढ़ने के लिए सशक्त बनाते हैं। अब हम परिष्कृत, संदर्भ-जागरूक एप्लिकेशन बना सकते हैं जो अपने परिवेश के बारे में लगातार जागरूक हैं बिना अपने यूजर्स के फोन को महंगे पेपरवेट में बदले। एक वास्तव में स्मार्ट, निर्बाध रूप से जुड़ी दुनिया का सपना थोड़ा करीब है, और यह इन पावर-कुशल तकनीकों की पीठ पर बनाया जाएगा।
तो, आगे क्या? ब्लूटूथ की दुनिया हमेशा विकसित हो रही है। हमारे पास Auracast के साथ ब्लूटूथ 5.4, मेश नेटवर्किंग, और क्षितिज पर और भी सटीक स्थान खोज है। एक चीज जिसके बारे में हम निश्चित हो सकते हैं वह यह है कि उपकरण बेहतर होते रहेंगे, और चुनौतियाँ और दिलचस्प होती रहेंगी।
अभी के लिए, एक पल के लिए हमने जो प्रगति की है उसकी सराहना करें। अगली बार जब आप ब्लूटूथ स्कैन शुरू करें और यह बस काम करे, तो उन मेहनती इंजीनियरों को धन्यवाद देने के लिए एक पल लें जिन्होंने इसे संभव बनाया। और अगली बार जब आपके ऐप का बैटरी ग्राफ एक भयानक स्की ढलान के बजाय एक सुंदर, सपाट रेखा हो, तो पैसिव स्कैनिंग की शक्ति को एक छोटा सा नमन दें।
ब्लूटूथ जानवर कभी पूरी तरह से वश में नहीं हो सकता, लेकिन AOSP 16 के साथ, हमें एक बहुत मजबूत पट्टा दिया गया है। अब जाओ और अद्भुत चीजें बनाओ। और पवित्र सभी चीजों के प्यार के लिए, अपना स्कैन रोकना याद रखो।
मुफ्त में कोड करना सीखें। freeCodeCamp के ओपन सोर्स पाठ्यक्रम ने 40,000 से अधिक लोगों को डेवलपर्स के रूप में नौकरी दिलाने में मदद की है। शुरू करें