24

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

यहां कोड है जिसका मैं उपयोग कर रहा हूं। गतिविधि:

public final class DemoActivity extends Activity 
     implements NumberListFragment.RowTappedListener, 
        LoaderManager.LoaderCallbacks<String> { 

    private static final AtomicInteger activityCounter = new AtomicInteger(0); 

    private int myActivityId; 

    private ResultFragment resultFragment; 

    private Integer selectedNumber; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     myActivityId = activityCounter.incrementAndGet(); 
     Log.d("DemoActivity", "onCreate for " + myActivityId); 

     setContentView(R.layout.demo); 

     resultFragment = (ResultFragment) getFragmentManager().findFragmentById(R.id.result_fragment); 

     getLoaderManager().initLoader(0, null, this); 

    } 

    @Override 
    protected void onDestroy() { 
     super.onDestroy(); 
     Log.d("DemoActivity", "onDestroy for " + myActivityId); 
    } 

    @Override 
    public void onRowTapped(Integer number) { 
     selectedNumber = number; 
     resultFragment.setResultText("Fetching details for item " + number + "..."); 
     getLoaderManager().restartLoader(0, null, this); 
    } 

    @Override 
    public Loader<String> onCreateLoader(int id, Bundle args) { 
     return new ResultLoader(this, selectedNumber); 
    } 

    @Override 
    public void onLoadFinished(Loader<String> loader, String data) { 
     Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId); 
     resultFragment.setResultText(data); 
    } 

    @Override 
    public void onLoaderReset(Loader<String> loader) { 

    } 

    static final class ResultLoader extends AsyncTaskLoader<String> { 

     private static final Random random = new Random(); 

     private final Integer number; 

     private String result; 

     ResultLoader(Context context, Integer number) { 
      super(context); 
      this.number = number; 
     } 

     @Override 
     public String loadInBackground() { 
      // Simulate expensive Web call 
      try { 
       Thread.sleep(5000); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 

      return "Item " + number + " - Price: $" + random.nextInt(500) + ".00, Number in stock: " + random.nextInt(10000); 
     } 

     @Override 
     public void deliverResult(String data) { 
      if (isReset()) { 
       // An async query came in while the loader is stopped 
       return; 
      } 

      result = data; 

      if (isStarted()) { 
       super.deliverResult(data); 
      } 
     } 

     @Override 
     protected void onStartLoading() { 
      if (result != null) { 
       deliverResult(result); 
      } 

      // Only do a load if we have a source to load from 
      if (number != null) { 
       forceLoad(); 
      } 
     } 

     @Override 
     protected void onStopLoading() { 
      // Attempt to cancel the current load task if possible. 
      cancelLoad(); 
     } 

     @Override 
     protected void onReset() { 
      super.onReset(); 

      // Ensure the loader is stopped 
      onStopLoading(); 

      result = null; 
     } 

    } 

} 

सूची टुकड़ा:

public final class NumberListFragment extends ListFragment { 

    interface RowTappedListener { 

     void onRowTapped(Integer number); 

    } 

    private RowTappedListener rowTappedListener; 

    @Override 
    public void onAttach(Activity activity) { 
     super.onAttach(activity); 

     rowTappedListener = (RowTappedListener) activity; 
    } 

    @Override 
    public void onActivityCreated(Bundle savedInstanceState) { 
     super.onActivityCreated(savedInstanceState); 

     ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(getActivity(), 
                    R.layout.simple_list_item_1, 
                    Arrays.asList(1, 2, 3, 4, 5, 6)); 
     setListAdapter(adapter); 

    } 

    @Override 
    public void onListItemClick(ListView l, View v, int position, long id) { 
     ArrayAdapter<Integer> adapter = (ArrayAdapter<Integer>) getListAdapter(); 
     rowTappedListener.onRowTapped(adapter.getItem(position)); 
    } 

} 

परिणाम टुकड़ा:

public final class ResultFragment extends Fragment { 

    private TextView resultLabel; 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
     View root = inflater.inflate(R.layout.result_fragment, container, false); 

     resultLabel = (TextView) root.findViewById(R.id.result_label); 
     if (savedInstanceState != null) { 
      resultLabel.setText(savedInstanceState.getString("labelText", "")); 
     } 

     return root; 
    } 

    @Override 
    public void onSaveInstanceState(Bundle outState) { 
     super.onSaveInstanceState(outState); 

     outState.putString("labelText", resultLabel.getText().toString()); 
    } 

    void setResultText(String resultText) { 
     resultLabel.setText(resultText); 
    } 

} 

मैं प्राप्त करने में सक्षम किया गया है इस सादे AsyncTask s का उपयोग करके काम कर रहा है, लेकिन मैं अधिक जानने के लिए कोशिश कर रहा हूँ लगभग Loader एस क्योंकि वे कॉन्फ़िगरेशन को स्वचालित रूप से बदलते हैं।


संपादित: मुझे लगता है मैं LoaderManager के लिए स्रोत को देखकर मुद्दा नीचे ट्रैक किए गए हो सकता है। कॉन्फ़िगरेशन परिवर्तन के बाद initLoader को कॉल किया जाता है, LoaderInfo ऑब्जेक्ट में mCallbacks फ़ील्ड LoaderCallbacks के कार्यान्वयन के रूप में नई गतिविधि के साथ अद्यतन किया गया है, जैसा कि मैं अपेक्षा करता हूं।

public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) { 
    if (mCreatingLoader) { 
     throw new IllegalStateException("Called while creating a loader"); 
    } 

    LoaderInfo info = mLoaders.get(id); 

    if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args); 

    if (info == null) { 
     // Loader doesn't already exist; create. 
     info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 
     if (DEBUG) Log.v(TAG, " Created new loader " + info); 
    } else { 
     if (DEBUG) Log.v(TAG, " Re-using existing loader " + info); 
     info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; 
    } 

    if (info.mHaveData && mStarted) { 
     // If the loader has already generated its data, report it now. 
     info.callOnLoadFinished(info.mLoader, info.mData); 
    } 

    return (Loader<D>)info.mLoader; 
} 

हालांकि, जब वहाँ एक लंबित लोडर, मुख्य LoaderInfo वस्तु भी एक mPendingLoader क्षेत्र एक LoaderCallbacks के लिए एक संदर्भ के साथ भी है, और इस वस्तु mCallbacks क्षेत्र में नई गतिविधि के साथ अद्यतन कभी नहीं किया गया है। मैं इस के बजाय की तरह कोड नज़र देखने की अपेक्षा करेंगे:

// This line was already there 
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; 
// This line is not currently there 
info.mPendingLoader.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback; 

यह इस है कि लंबित लोडर कॉल onLoadFinished वर्ष गतिविधि उदाहरण पर की वजह से प्रतीत होता है। अगर मैं इस विधि में टूटता हूं और कॉल करता हूं जो मुझे लगता है कि डीबगर का उपयोग कर गायब है, तो सबकुछ काम करता है जैसा मैं उम्मीद करता हूं।

नया सवाल यह है: क्या मुझे एक बग मिला है, या यह अपेक्षित व्यवहार है?

+0

['CursorLoader.java'] पर एक नज़र डालें (http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/ सामग्री/CursorLoader.java /? v = स्रोत) स्रोत कोड। 'ऑनस्टार्ट लोडिंग', 'ऑनस्टॉपलोडिंग', 'ऑन कैंसल', 'ऑनसेट', और 'डिलिवरी रिसेट' को स्रोत कोड के समान ही लागू करने का प्रयास करें ... 'लोडर प्रबंधक' मानता है कि इन सभी विधियों को सही तरीके से कार्यान्वित किया गया है। यही कारण है कि आपका कार्यान्वयन केवल आंशिक रूप से कॉन्फ़िगरेशन परिवर्तनों में काम कर रहा है। –

+1

तो यह पता चला है कि 'ऑनलोड लोड() '* * वास्तव में कहा जा रहा है - यह सिर्फ पुराने गतिविधि की बजाय पुरानी गतिविधि (कॉन्फ़िगरेशन परिवर्तन से पहले एक) को रिपोर्ट कर रहा है। प्रश्न संपादित किया गया है और कोड अद्यतन किया गया है। –

+0

ईमानदारी से, मुझे लगता है कि यहां समस्या इस उदाहरण की तुच्छता है ... वास्तविक जीवन की स्थिति में 'लोडर' में वास्तविक डेटा स्रोत नहीं होगा (यानी' निजी अंतिम int 'फ़ील्ड आपका वास्तविक डेटा स्रोत है, क्या ऐसा नहीं है?)। 'लोडर को अपने डेटा स्रोत की निगरानी भी करनी चाहिए और परिवर्तन किए जाने पर वापस रिपोर्ट करना चाहिए। अधिकांश मामलों में 'ऑनलोड लोडेड' कॉन्फ़िगरेशन परिवर्तन के बाद आपकी नई गतिविधि पर नहीं कहा जाएगा क्योंकि लोडर अपने पुराने डेटा को बरकरार रखने के लिए पर्याप्त स्मार्ट है ... यह केवल तब लोड होगा जब यह देखता है कि उसके बैकिंग डेटा में परिवर्तन हुए हैं बनाया गया। –

उत्तर

-2

ठीक है, अगर मैं कुछ भी गलत समझता हूं तो मैं यह बहाना समझने की कोशिश कर रहा हूं, लेकिन जब डिवाइस घूमता है तो आप कुछ के संदर्भ खो रहे हैं।

एक चाकू ले रहा है ...

android:configChanges="orientation|keyboardHidden|screenSize" 

कि गतिविधि के लिए अपने मेनिफ़ेस्ट में जोड़ने अपनी त्रुटि को ठीक? या गतिविधि को रोकने से onLoadFinished() को रोकें?

+0

यह एक कामकाज हो सकता है, लेकिन यह एंड्रॉइड में एक वास्तविक बग की संभावना है। –

+0

@StevePomeroy जरूरी नहीं है, जब डिवाइस घुमाया जाता है; गतिविधि को फिर से बनाया जाता है, इस प्रकार वस्तुओं या सॉर्ट के अन्य चर के किसी संदर्भ को पुन: प्रारंभ करना। यह सिस्टम को गतिविधि को फिर से बनाने से गतिविधि को फिर से बनाने से रोक रहा है, जिससे इसे अभिविन्यास परिवर्तन को संभालने/ओवरराइड करने की इजाजत मिलती है। यदि उपकरण लैंडस्केप मोड में जाता है तो कुछ लेआउट तत्वों को फिर से आकार दिया जा सकता है या फिर से स्थानांतरित किया जा सकता है। – StrikeForceZero

+0

@StevePomeroy आपका asyncloader कभी भी समाप्त नहीं होता है क्योंकि इसे – StrikeForceZero

2

ज्यादातर मामलों में आपको ऐसी रिपोर्टों को अनदेखा करना चाहिए यदि गतिविधि पहले से ही नष्ट हो गई है।

public void onLoadFinished(Loader<String> loader, String data) { 
    Log.d("DemoActivity", "onLoadFinished reporting to activity " + myActivityId); 
    if (isDestroyed()) { 
     Log.i("DemoActivity", "Activity already destroyed, report ignored: " + data); 
     return; 
    } 
    resultFragment.setResultText(data); 
} 

इसके अलावा, आप किसी भी आंतरिक कक्षाओं में isDestroyed() जाँच डालूँ।रननेबल - सबसे ज्यादा इस्तेमाल किया जाने वाला मामला है।

उदाहरण के लिए:

// UI thread 
final Handler handler = new Handler(); 
Executor someExecutorService = ... ; 
someExecutorService.execute(new Runnable() { 
    public void run() { 
     // some heavy operations 
     ... 
     // notification to UI thread 
     handler.post(new Runnable() { 
      // this runnable can link to 'dead' activity or any outer instance 
      if (isDestroyed()) { 
       return; 
      } 

      // we are alive 
      onSomeHeavyOperationFinished(); 
     }); 
    } 
}); 

लेकिन ऐसे मामलों सबसे अच्छा तरीका है एक और धागा (AsynkTask, लोडर, निर्वाहक, आदि) के लिए गतिविधि पर मजबूत संदर्भ गुजर से बचने के लिए है में।

सबसे विश्वसनीय समाधान यहाँ है:

// BackgroundExecutor.java 
public class BackgroundExecutor { 
    private static final Executor instance = Executors.newSingleThreadExecutor(); 

    public static void execute(Runnable command) { 
     instance.execute(command); 
    } 
} 

// MyActivity.java 
public class MyActivity extends Activity { 
    // Some callback method from any button you want 
    public void onSomeButtonClicked() { 
     // Show toast or progress bar if needed 

     // Start your heavy operation 
     BackgroundExecutor.execute(new SomeHeavyOperation(this)); 
    } 

    public void onSomeHeavyOperationFinished() { 
     if (isDestroyed()) { 
      return; 
     } 

     // Hide progress bar, update UI 
    } 
} 

// SomeHeavyOperation.java 
public class SomeHeavyOperation implements Runnable { 
    private final WeakReference<MyActivity> ref; 

    public SomeHeavyOperation(MyActivity owner) { 
     // Unlike inner class we do not store strong reference to Activity here 
     this.ref = new WeakReference<MyActivity>(owner); 
    } 

    public void run() { 
     // Perform your heavy operation 
     // ... 
     // Done! 

     // It's time to notify Activity 
     final MyActivity owner = ref.get(); 
     // Already died reference 
     if (owner == null) return; 

     // Perform notification in UI thread 
     owner.runOnUiThread(new Runnable() { 
      public void run() { 
       owner.onSomeHeavyOperationFinished(); 
      } 
     }); 
    } 
} 
0
नहीं सबसे अच्छा समाधान

हो सकता है कि लेकिन ... इस कोड को पुनः आरंभ लोडर हर बार है, जो बुरा है, लेकिन केवल कि काम करता है के आसपास काम - आप लोडर इस्तेमाल किया करना चाहते हैं ।

Loader l = getLoaderManager().getLoader(MY_LOADER); 
if (l != null) { 
    getLoaderManager().restartLoader(MY_LOADER, null, this); 
} else { 
    getLoaderManager().initLoader(MY_LOADER, null, this); 
} 

बीटीडब्ल्यू। मैं Cursorloader का उपयोग कर रहा हूं ...

0

एक संभावित समाधान एक कस्टम सिंगलटन ऑब्जेक्ट में AsyncTask शुरू करना और अपनी गतिविधि के भीतर सिंगलटन से समाप्त() परिणाम प्राप्त करना है। हर बार जब आप अपनी स्क्रीन घुमाते हैं, तो पॉज़() या ऑनस्यूम() पर जाएं, नवीनतम परिणाम का उपयोग/एक्सेस किया जाएगा। यदि आपके पास अभी भी आपके सिंगलटन ऑब्जेक्ट का नतीजा नहीं है, तो आप जानते हैं कि यह अभी भी व्यस्त है या आप कार्य को फिर से लॉन्च कर सकते हैं।

एक और तरीका ओटो जैसे सेवा बस के साथ काम करना या सेवा के साथ काम करना है।

संबंधित मुद्दे