मुझे लगता है कि हम में से कई ने फॉर्म का एक कोड लिखा है:
@Override public boolean onTouch(View view, MotionEvent event) { final float x = event.getX(); final float y = event.getY();
लेकिन, मुझे लगता है, इस पद्धति में आने से पहले प्रत्येक MotionEvent ऑब्जेक्ट किस पथ से गुजरता है, इसके बारे में बहुत से लोगों ने नहीं सोचा। ज्यादातर मामलों में, यह आवश्यक नहीं है, लेकिन फिर भी ऐसी परिस्थितियां हैं जहां मोशनईवेंट और टच प्रोसेसिंग की सुविधाओं की अज्ञानता से दु: खद परिणाम होता है।
एक साल पहले, मैं अपने दोस्तों के साथ एक एप्लिकेशन विकसित कर रहा था, जहां टच प्रोसेसिंग पर बहुत आराम किया गया था। एक बार, रिपॉजिटरी से नए स्रोतों को डाउनलोड करने और एप्लिकेशन को संकलित करने पर, मैंने पाया कि स्पर्श के ऊर्ध्वाधर समन्वय को सही ढंग से पता नहीं लगाया गया है। कमांड के अंतिम कमिट्स के माध्यम से, मैं एक दिलचस्प लाइन के पार आया, जहां अचानक 100 अंकों को y- निर्देशांक से घटाया गया था। अर्थात, "y - = 100"; कुछ और, इस संख्या को एक स्थिरांक के रूप में नहीं लिया गया था और सामान्य तौर पर यह स्पष्ट नहीं था कि क्यों? 100. मेरे स्पष्ट प्रश्न के लिए, मुझे उत्तर मिला "ठीक है, हमने प्रयोगात्मक रूप से यह निर्धारित किया है कि इस बिंदु पर y- समन्वय हमेशा 100 (पिक्सेल) से अधिक होना चाहिए।" यहां, निश्चित रूप से, यह प्रोसेसिंग टच पर प्रलेखन को फिर से पढ़ने के लायक होगा और, परियोजना कोड को देखने के बाद, एक त्रुटि खोजने के लिए, लेकिन मैंने और अधिक दिलचस्प तरीके से जाने का फैसला किया - एंड्रॉइड स्रोतों से मोशन को इसकी प्राप्ति से निपटान तक का पता लगाने के लिए।
अगर मैं "फॉलो द स्ट्रीप्ड बग" की शैली में कहानी के साथ किसी को साज़िश करने में सक्षम था - बिल्ली का स्वागत।
नैतिकता
सबसे पहले, सुनिश्चित करें कि एक मोशन को स्टोर करना जो हमारे साथ आया था, खराब है। मैंने निम्नलिखित कोड के साथ एक छोटा परीक्षण एप्लिकेशन का उपयोग किया:
package com.alcsan.test;
हम एप्लिकेशन लॉन्च करते हैं, एक्शनबार के तहत एक बिंदु पर कई बार टैप करें और लॉग को देखें। व्यक्तिगत रूप से, मुझे निम्न चित्र मिला: "32.0", "41.0 41.0", "39.0 39.0 39.0", "39.0 39.0 39.0 39.0"। यही है, पहली कॉल के बाद, हमने इतिहास में y = 32 के साथ एक ऑब्जेक्ट को बचाया, लेकिन y के अगले प्रेस के बाद यह ऑब्जेक्ट 41 है, और उसी y के साथ एक ऑब्जेक्ट को इतिहास में दर्ज किया गया है। वास्तव में, यह सभी एक ही वस्तु है जिसका उपयोग पहली बार किया गया था और इसे दूसरी बार पुन: उपयोग करने के लिए बुलाया गया था। इसलिए, नैतिक सरल है: ऑनटच में प्राप्त मोशन को स्टोर न करें! इस ऑब्जेक्ट का उपयोग केवल ऑनटच विधि के भीतर करें, और अन्य आवश्यकताओं के लिए, इससे निर्देशांक निकालें और उन्हें उदाहरण के लिए, प्वाइंटफ़ में स्टोर करें।
एंड्रॉइड स्रोत - मोशनवेंट पूल
और अब मैं एंड्रॉइड स्रोतों के खरगोश छेद को देखने का प्रस्ताव करता हूं और यह निर्धारित करता हूं कि MotionEvent इस तरह से व्यवहार क्यों करता है।
सबसे पहले, परीक्षण एप्लिकेशन के व्यवहार से यह स्पष्ट है कि हर बार छुआ जाने पर मोशनवेंट ऑब्जेक्ट नहीं बनाए जाते हैं, लेकिन पुन: उपयोग किए जाते हैं। ऐसा इसलिए किया जाता है क्योंकि बहुत कम समय में कई स्पर्श हो सकते हैं और कई वस्तुओं का निर्माण प्रदर्शन को नीचा दिखाएगा। कम से कम कचरे के बढ़ते संग्रह के कारण। सोचिए कि फ्रूट निंजा खेलने के एक मिनट में कितनी वस्तुएं बन जाएंगी, क्योंकि इवेंट केवल DOWN, UP और CANCEL ही नहीं हैं, बल्कि बहुत सारे हैं।
MotionEvent ऑब्जेक्ट पूल के साथ काम करने का तर्क MotionEvent वर्ग -
grepcode.com/file/repository.grepcode.com/java/ext/google.android/android/2.2_r.1.1andand/view/MotionEvent.java में स्थित है। यहां पूल के साथ स्टैटिक तरीके और चर जुड़े हुए हैं। एक साथ संग्रहीत वस्तुओं की अधिकतम संख्या MAX_RECYCLED स्थिरांक (और 10 के बराबर है) द्वारा निर्धारित की जाती है, संग्रहीत वस्तुओं के काउंटर को gRecyclerUsed द्वारा निर्धारित किया जाता है, gRecyclerLock को अतुल्यकालिक मोड में सिंक्रनाइज़ करने और संचालन सुनिश्चित करने के लिए उपयोग किया जाता है। gRecyclerTop - रीसाइक्लिंग के लिए छोड़ी गई वस्तुओं की सूची का प्रमुख। और एक गैर-स्थिर चर mNext, साथ ही mRecycledLocation और mRecycled भी है।
जब सिस्टम को एक ऑब्जेक्ट की आवश्यकता होती है, तो स्थैतिक प्राप्त () विधि कहा जाता है। यदि पूल खाली है (gRecyclerTop == null), तो एक नई वस्तु बनाई जाती है और उसे लौटा दिया जाता है। अन्यथा, अंतिम पुनर्नवीनीकरण ऑब्जेक्ट (gRecyclerTop) वापस कर दिया जाता है, और अंतिम लेकिन एक (gRecyclerTop = gRecyclerTop.mNext) इसकी जगह लेता है।
निपटान के लिए, रीसायकल () को निपटान की जाने वाली वस्तु पर कहा जाता है। यह "अंतिम जोड़ा" (gRecyclerTop) का स्थान लेता है, और वर्तमान "अंतिम" का लिंक mNext (mNext = gRecyclerTop) में संग्रहीत किया जाता है। पूल ओवरफ्लो की जांच के बाद यह सब होता है।
एंड्रॉइड स्रोत - मोशनईवेंट प्रोसेसिंग
हम बहुत गहराई से गोता नहीं लगाएंगे और हैंडलमैसेज (संदेश संदेश) विधि के साथ शुरू करेंगे -
grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.2_b1.1/android/view/ViewRoot। java? av = f # 1712 - ViewRoot class। यहाँ समाप्त MotionEvent आता है (MotionEvent.obtain ()) के माध्यम से सिस्टम द्वारा प्राप्त, संदेश में लिपटे। विधि, वैसे, न केवल स्पर्श करने के लिए, बल्कि अन्य घटनाओं को भी संभालने का कार्य करती है। इसलिए, विधि का शरीर एक बड़ा स्विच है, जिसमें हम 1744 से 1847 तक लाइनों में रुचि रखते हैं। यहां, घटना पूर्व-संसाधित है, फिर mView.dispatchTouchEvent (ईवेंट), फिर ईवेंट को पूल में जोड़ा गया है - event.recycle ()। DispatchTouchEvent (...) विधि एक श्रोता घटना को बढ़ाती है, यदि कोई हो, और आंतरिक दृश्य को घटना के प्रसंस्करण को सौंपने का प्रयास करती है।
बग के निशान
और अब संक्षेप में कि बग क्या था।
सबसे पहले, इस परियोजना में MotionEvent के साथ उन्होंने वास्तव में क्या किया, इसके बारे में थोड़ा। ऑब्जेक्ट प्राप्त करने के बाद, एप्लिकेशन ने इसे एक चर में सहेजा, एक निश्चित संख्या में मिलीसेकंड की प्रतीक्षा की और इसे संसाधित किया। इस तरह का व्यवहार इशारों से जुड़ा था: मोटे तौर पर, अगर उपयोगकर्ता ने स्क्रीन को छुआ और एक दूसरे के लिए अपनी उंगली रखी - तो उसे एक निश्चित संवाद दिखाएं। अनुप्रयोग ने ACTION_DOWN घटना प्राप्त की और, एक सेकंड के भीतर ACTION_UP या ACTION_CANCEL ईवेंट प्राप्त किए बिना, जवाब दिया। इसके अलावा, यह पहल MotionEvent पर आधारित है। इस प्रकार, उसके लिए लिंक कुछ समय तक रहता था, जिसके दौरान कई अन्य स्पर्श घटनाएं हो सकती थीं।
लगातार, निम्नलिखित हुआ:
1. उपयोगकर्ता स्क्रीन को छू गया।
2. सिस्टम ने MotionEvent.obtain () पद्धति का उपयोग करके एक नई वस्तु प्राप्त की और इसे टच डेटा के साथ पॉप्युलेट किया।
3. ईवेंट ऑब्जेक्ट हैंडलमेसेज (...) में मिला, इसे संसाधित किया गया और, कई विधियों के बाद, यह श्रोता के ऑनटच () विधि में मिला।
4. onTouch () विधि ऑब्जेक्ट के लिए एक संदर्भ रखती है। यहां टाइमर शुरू होता है।
5. हैंडलमैसेज (...) विधि में, ऑब्जेक्ट को पूल में रखा गया था - event.recycle ()। अर्थात्, सिस्टम अब इस वस्तु को पुन: उपयोग के लिए स्वतंत्र मानता है।
6. जबकि टाइमर टिक कर रहा है, उपयोगकर्ता ने स्क्रीन को कई बार स्पर्श किया, जबकि इन टच को संसाधित करने के लिए उसी ऑब्जेक्ट का उपयोग किया गया था।
7. टाइमर ने उलटी गिनती पूरी कर ली है, एक निश्चित विधि को कहा जाता है, जो पहले स्पर्श पर प्राप्त MotionEvent ऑब्जेक्ट के संदर्भ में संदर्भित करता है। ऑब्जेक्ट समान है, लेकिन x और y पहले से ही बदल चुके हैं।
परीक्षण के उदाहरण में, सब कुछ सरल भी था:
1. पहला स्पर्श। MotionEvent ऑब्जेक्ट अनुरोध किया है। पहली कॉल के बाद से - ऑब्जेक्ट बनाया जाता है।
2. विषय स्पर्श जानकारी से भरा है।
3. वस्तु onTouch () में आती है और हम इसे इतिहास सूची में लिंक को सहेजते हैं।
4. वस्तु का निपटान किया जाता है।
5. दूसरा स्पर्श। MotionEvent ऑब्जेक्ट अनुरोध किया है। चूंकि पूल में पहले से ही एक है, वह लौटता है।
6. पूल से प्राप्त वस्तु इसके निर्देशांक को बदल देती है।
7. वस्तु onTouch () में आती है, हम इसे कहानी में जोड़ते हैं, लेकिन यह वही वस्तु है जो पहले से ही कहानी में है, और पहले स्पर्श के निर्देशांक खो गए हैं - उन्हें दूसरे स्पर्श के निर्देशांक द्वारा प्रतिस्थापित किया गया था।
निष्कर्ष
हां, दस्तावेज़ीकरण पढ़ना और वहां देखना अधिक आसान होगा और यह देखना होगा कि MotionEvent ऑब्जेक्ट्स को इस तरह से स्टोर करना असंभव है। StackOverflow पर समस्या का समाधान देखना तेजी से होगा। लेकिन, उपयोग से निर्माण के लिए स्रोत से मोशन-वे पर जाने के लिए दिलचस्प और जानकारीपूर्ण था।