android - AsyncTask वास्तव में अवधारणात्मक रूप से त्रुटिपूर्ण है या मैं बस कुछ याद कर रहा हूँ?




concurrency handler (8)

मैंने महीनों के लिए इस समस्या की जांच की है, इसके विभिन्न समाधानों के साथ आया है, जो मैं खुश नहीं हूं क्योंकि वे सभी बड़े पैमाने पर हैक्स हैं। मैं अभी भी विश्वास नहीं कर सकता कि डिजाइन में त्रुटिपूर्ण एक वर्ग ने इसे ढांचे में बनाया है और कोई भी इसके बारे में बात नहीं कर रहा है, इसलिए मुझे लगता है कि मुझे बस कुछ याद आना चाहिए।

समस्या AsyncTask साथ है। दस्तावेज के अनुसार यह

"थ्रेड और / या हैंडलर में हेरफेर किए बिना यूआई थ्रेड पर बैकग्राउंड ऑपरेशंस करने और परिणाम प्रकाशित करने की अनुमति देता है।"

उदाहरण तब दिखाता है कि कुछ उदाहरण दिखाने के लिए showDialog() विधि को showDialog() कैसे कहा जाता है। हालांकि, यह पूरी तरह से मेरे लिए प्रतीत होता है, क्योंकि एक संवाद दिखाने के लिए हमेशा एक वैध Context संदर्भ की आवश्यकता होती है, और एक AsyncTask को किसी संदर्भ ऑब्जेक्ट का मजबूत संदर्भ कभी नहीं रखना चाहिए

कारण स्पष्ट है: क्या होगा अगर गतिविधि नष्ट हो जाए जो कार्य को ट्रिगर करता है? यह हर समय हो सकता है, उदाहरण के लिए क्योंकि आपने स्क्रीन फिसल दी है। यदि कार्य उस संदर्भ के संदर्भ में होगा जो इसे बनाया गया है, तो आप न केवल एक बेकार संदर्भ वस्तु को पकड़ रहे हैं (विंडो नष्ट हो जाएगी और कोई यूआई इंटरैक्शन अपवाद के साथ असफल हो जाएगा!), आप भी जोखिम बनाने का जोखिम उठाते हैं स्मृति रिसाव।

जब तक मेरा तर्क यहां दोषपूर्ण न हो, तब तक यह अनुवाद करता है: onPostExecute() पूरी तरह से बेकार है, क्योंकि यूआई थ्रेड पर चलाने के लिए इस विधि के लिए क्या अच्छा है यदि आपके पास किसी भी संदर्भ तक पहुंच नहीं है? आप यहाँ कुछ सार्थक नहीं कर सकते हैं।

एक वर्कअराउंड एक AsyncTask के संदर्भ उदाहरणों को पारित नहीं करेगा, लेकिन एक Handler उदाहरण। यह काम करता है: चूंकि हैंडलर संदर्भ और कार्य को कम से कम बांधता है, इसलिए आप रिसाव (दाएं?) के जोखिम के बिना उनके बीच संदेशों का आदान-प्रदान कर सकते हैं। लेकिन इसका मतलब यह होगा कि AsyncTask का आधार, अर्थात् आपको हैंडलरों से परेशान करने की आवश्यकता नहीं है, गलत है। यह हैंडलर का दुरुपयोग करने जैसा भी लगता है, क्योंकि आप एक ही थ्रेड पर संदेश भेज रहे हैं और प्राप्त कर रहे हैं (आप इसे यूआई थ्रेड पर बनाते हैं और इसे PostExecute () पर भेजते हैं जिसे यूआई थ्रेड पर भी निष्पादित किया जाता है)।

इसे दूर करने के लिए, यहां तक ​​कि उस कार्यवाही के साथ, आपको अभी भी समस्या है कि जब संदर्भ नष्ट हो जाता है, तो आपके पास निकाले गए कार्यों का कोई रिकॉर्ड नहीं होता है । इसका मतलब है कि संदर्भ को फिर से बनाते समय आपको किसी भी कार्य को फिर से शुरू करना होगा, उदाहरण के लिए स्क्रीन अभिविन्यास परिवर्तन के बाद। यह धीमा और अपर्याप्त है।

इसका मेरा समाधान (जैसा कि Droid-Fu लाइब्रेरी में कार्यान्वित किया गया है ) अद्वितीय अनुप्रयोग ऑब्जेक्ट पर घटक नामों से WeakReference मैपिंग को अपने वर्तमान उदाहरणों में बनाए रखना है। जब भी एक AsyncTask शुरू होता है, यह उस मानचित्र में कॉलिंग संदर्भ रिकॉर्ड करता है, और प्रत्येक कॉलबैक पर, यह उस मैपिंग से वर्तमान संदर्भ उदाहरण लाएगा। यह सुनिश्चित करता है कि आप कभी भी एक स्थिर संदर्भ उदाहरण का संदर्भ नहीं देंगे और आपके पास कॉलबैक में हमेशा एक वैध संदर्भ तक पहुंच होगी ताकि आप वहां सार्थक UI कार्य कर सकें। यह रिसाव भी नहीं करता है, क्योंकि संदर्भ कमजोर होते हैं और साफ़ किए जाते हैं जब किसी दिए गए घटक का कोई उदाहरण मौजूद नहीं होता है।

फिर भी, यह एक जटिल कामकाज है और इसे कुछ Droid-Fu लाइब्रेरी कक्षाओं के उप-वर्ग की आवश्यकता होती है, जिससे यह एक सुंदर घुसपैठ दृष्टिकोण बन जाता है।

अब मैं बस जानना चाहता हूं: क्या मैं बस कुछ हद तक याद कर रहा हूं या असिनकटास्क वास्तव में पूरी तरह से त्रुटिपूर्ण है? आपके अनुभव इसके साथ कैसे काम कर रहे हैं? आपने इन समस्याओं को कैसे हल किया?

आपके सहयोग के लिए धन्यवाद।


कारण स्पष्ट है: क्या होगा अगर गतिविधि नष्ट हो जाए जो कार्य को ट्रिगर करता है?

AsyncTask onDestroy() AsyncTask से गतिविधि को मैन्युअल रूप से अलग करें। नई गतिविधि को मैन्युअल रूप से AsyncTask पर AsyncTask onCreate() में फिर से संबद्ध करें। इसके लिए या तो एक स्थिर आंतरिक कक्षा या एक मानक जावा वर्ग, साथ ही कोड की 10 लाइनों की आवश्यकता होती है।


"इसके साथ काम करने के अनुभव" के रूप में: सभी AsyncTasks के साथ प्रक्रिया को मारना possible है, एंड्रॉइड गतिविधि स्टैक को फिर से बना देगा ताकि उपयोगकर्ता कुछ भी उल्लेख न करे।


आप बिल्कुल सही हैं - यही कारण है कि डेटा लाने के लिए गतिविधियों में एसिंक कार्यों / लोडर का उपयोग करने से एक आंदोलन गति प्राप्त कर रहा है। एक नए तरीके में से एक Volley फ्रेमवर्क का उपयोग करना है जो डेटा तैयार होने के बाद अनिवार्य रूप से कॉलबैक प्रदान करता है - एमवीसी मॉडल के साथ अधिक संगत। वॉली को Google I / O 2013 में पॉप्युलाइज़ किया गया था। सुनिश्चित नहीं है कि अधिक लोगों को इसके बारे में क्यों पता नहीं है।


आपकी गतिविधि पर सप्ताहांत रखने के लिए यह और अधिक मजबूत होगा:

public class WeakReferenceAsyncTaskTestActivity extends Activity {
    private static final int MAX_COUNT = 100;

    private ProgressBar progressBar;

    private AsyncTaskCounter mWorker;

    @SuppressWarnings("deprecation")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_task_test);

        mWorker = (AsyncTaskCounter) getLastNonConfigurationInstance();
        if (mWorker != null) {
            mWorker.mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(this);
        }

        progressBar = (ProgressBar) findViewById(R.id.progressBar1);
        progressBar.setMax(MAX_COUNT);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_async_task_test, menu);
        return true;
    }

    public void onStartButtonClick(View v) {
        startWork();
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        return mWorker;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mWorker != null) {
            mWorker.mActivity = null;
        }
    }

    void startWork() {
        mWorker = new AsyncTaskCounter(this);
        mWorker.execute();
    }

    static class AsyncTaskCounter extends AsyncTask<Void, Integer, Void> {
        WeakReference<WeakReferenceAsyncTaskTestActivity> mActivity;

        AsyncTaskCounter(WeakReferenceAsyncTaskTestActivity activity) {
            mActivity = new WeakReference<WeakReferenceAsyncTaskTestActivity>(activity);
        }

        private static final int SLEEP_TIME = 200;

        @Override
        protected Void doInBackground(Void... params) {
            for (int i = 0; i < MAX_COUNT; i++) {
                try {
                    Thread.sleep(SLEEP_TIME);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(getClass().getSimpleName(), "Progress value is " + i);
                Log.d(getClass().getSimpleName(), "getActivity is " + mActivity);
                Log.d(getClass().getSimpleName(), "this is " + this);

                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            if (mActivity != null) {
                mActivity.get().progressBar.setProgress(values[0]);
            }
        }
    }

}

ऐसा लगता है कि AsyncTask केवल अवधारणात्मक रूप से त्रुटिपूर्ण से थोड़ा अधिक है। यह संगतता मुद्दों से भी अनुपयोगी है। एंड्रॉइड दस्तावेज़ पढ़ते हैं:

पहली बार पेश किए जाने पर, AsyncTasks को एक पृष्ठभूमि थ्रेड पर क्रमशः निष्पादित किया गया था। डोनट से शुरू होने पर, यह थ्रेड के पूल में बदल दिया गया था जिससे कई कार्यों को समानांतर में संचालित किया जा सकता था। HONEYCOMB प्रारंभ करना, समानांतर निष्पादन के कारण सामान्य अनुप्रयोग त्रुटियों से बचने के लिए कार्यों को एकल थ्रेड पर निष्पादित किया जा रहा है। यदि आप वास्तव में समानांतर निष्पादन चाहते हैं, तो आप THREAD_POOL_EXECUTOR साथ इस विधि के executeOnExecutor(Executor, Params...) संस्करण का उपयोग कर सकते हैं ; हालांकि, इसके उपयोग पर चेतावनियों के लिए वहां टिप्पणी देखें।

executeOnExecutor() और THREAD_POOL_EXECUTOR दोनों API स्तर 11 (एंड्रॉइड 3.0.x, THREAD_POOL_EXECUTOR executeOnExecutor() में जोड़े गए हैं।

इसका अर्थ यह है कि यदि आप दो फाइलों को डाउनलोड करने के लिए दो AsyncTask s बनाते हैं, तो दूसरा डाउनलोड तब तक शुरू नहीं होगा जब तक कि पहले कोई समाप्त न हो जाए। यदि आप दो सर्वरों के माध्यम से चैट करते हैं, और पहला सर्वर डाउन है, तो आप पहले एक बार कनेक्शन से पहले दूसरे से कनेक्ट नहीं होंगे। (जब तक आप नई एपीआई 11 सुविधाओं का उपयोग नहीं करते हैं, लेकिन यह आपके कोड को 2.x के साथ असंगत बना देगा)।

और यदि आप 2.x और 3.0+ दोनों को लक्षित करना चाहते हैं, तो सामान वास्तव में मुश्किल हो जाता है।

इसके अलावा, docs कहते हैं:

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


क्यों न केवल स्वामित्व गतिविधि में onPause() विधि को ओवरराइड करें और वहां से AsyncTask को रद्द करें?


मुझे यकीन नहीं है कि यह सच है कि आप AsyncTask से किसी संदर्भ के संदर्भ में मेमोरी रिसाव को जोखिम देते हैं।

उन्हें लागू करने का सामान्य तरीका गतिविधि के तरीकों में से किसी एक के दायरे में एक नया AsyncTask उदाहरण बनाना है। तो यदि गतिविधि नष्ट हो जाती है, तो एक बार AsyncTask पूरा हो जाने पर यह पहुंच योग्य नहीं होगा और फिर कचरा संग्रह के लिए योग्य होगा? तो गतिविधि का संदर्भ कोई फर्क नहीं पड़ता क्योंकि AsyncTask स्वयं चारों ओर लटका नहीं होगा।


मैंने सोचा कि काम रद्द करें लेकिन यह नहीं करता है।

यहां वे इसके बारे में आरटीएफएमिंग करते हैं:

"" यदि कार्य पहले ही शुरू हो चुका है, तो MayInterruptIfRunning पैरामीटर यह निर्धारित करता है कि कार्य को रोकने के प्रयास में इस कार्य को निष्पादित करने वाला थ्रेड बाधित होना चाहिए या नहीं। "

हालांकि, यह इंगित नहीं करता है कि धागा अंतःस्थापित है। यह जावा चीज है, एसिंक टास्क चीज़ नहीं। "

http://groups.google.com/group/android-developers/browse_thread/thread/dcadb1bc7705f1bb/add136eb4949359d?show_docid=add136eb4949359d





android-asynctask