2011-11-03 11 views
7

In primo luogo il problema:Android: strategia di pool di thread e può essere utilizzato Loader per implementarlo?

  • sto lavorando sull'applicazione che utilizza più FragmentLists all'interno di una personalizzata FragmentStatePagerAdapter. Ci potrebbe essere, numero potenzialmente sostanziale di tali frammenti dire tra 20 e 40.
  • Ogni frammento è un elenco in cui ogni elemento può contenere testo o immagine.
  • Le immagini devono essere caricati in modo asincrono dal web e cache di memoria cache temporanea e anche a SD, se disponibile
  • Quando Frammento si spegne lo schermo eventuali arrivi e le attività in corso dovrebbero essere annullati (non in pausa)

La mia prima implementazione ha seguito ben noto image loader code da Google. Il mio problema con quel codice è che fondamentalmente crea un'istanza di AsyncTask per immagine. Che nel mio caso uccide l'app molto velocemente.

Dal momento che sto usando il pacchetto di compatibilità v4 ho pensato che l'utilizzo di Loader personalizzato che si estende AsyncTaskLoader mi aiuterebbe dal momento che implementa internamente un pool di thread. Tuttavia, a mia spiacevole sorpresa se eseguo questo codice più volte ogni successiva chiamata interromperà il precedente. Dire che ho questo nel mio metodo ListView#getView:

getSupportLoaderManager().restartLoader(0, args, listener); 

Questo metodo viene eseguito in loop per ogni elemento della lista che entra in vista. E come ho affermato - ogni successiva invocazione terminerà la precedente. O almeno questo è quello che succedere basa sulla LogCat

11-03 13:33:34.910: V/LoaderManager(14313): restartLoader in LoaderManager: args=Bundle[{URL=http://blah-blah/pm.png}] 
11-03 13:33:34.920: V/LoaderManager(14313): Removing pending loader: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}} 
11-03 13:33:34.920: V/LoaderManager(14313): Destroying: LoaderInfo{405d44c0 #2147483647 : ImageLoader{405118a8}} 
11-03 13:33:34.920: V/LoaderManager(14313): Enqueuing as new pending loader 

Poi ho pensato che forse dando ID univoco a ogni caricatore aiuterà le questioni, ma non sembra fare alcuna differenza. Come risultato, finisco con immagini apparentemente casuali e l'app non carica mai nemmeno 1/4 di ciò di cui ho bisogno.

La questione

  • quale sarebbe il modo per risolvere il Loader per fare quello che voglio (e c'è un modo?)
  • Se non ciò che è un buon modo per creare AsyncTask piscina ed è c'è forse un'implementazione funzionante di questo?

Per dare l'idea del codice, ecco la versione ridotta di Loader dove la logica di download/salvataggio è in una classe separata di ImageManager.

public class ImageLoader extends AsyncTaskLoader<TaggedDrawable> { 
     private static final String TAG = ImageLoader.class.getName(); 
     /** Wrapper around BitmapDrawable that adds String field to id the drawable */ 
     TaggedDrawable img; 
     private final String url; 
     private final File cacheDir; 
     private final HttpClient client; 


    /** 
    * @param context 
    */ 
    public ImageLoader(final Context context, final String url, final File cacheDir, final HttpClient client) { 
     super(context); 
     this.url = url; 
     this.cacheDir = cacheDir; 
     this.client = client; 
    } 

    @Override 
    public TaggedDrawable loadInBackground() { 
     Bitmap b = null; 
     // first attempt to load file from SD 
     final File f = new File(this.cacheDir, ImageManager.getNameFromUrl(url)); 
     if (f.exists()) { 
      b = BitmapFactory.decodeFile(f.getPath()); 
     } else { 
      b = ImageManager.downloadBitmap(url, client); 
      if (b != null) { 
       ImageManager.saveToSD(url, cacheDir, b); 
      } 
     } 
     return new TaggedDrawable(url, b); 
    } 

    @Override 
    protected void onStartLoading() { 
     if (this.img != null) { 
      // If we currently have a result available, deliver it immediately. 
      deliverResult(this.img); 
     } else { 
      forceLoad(); 
     } 
    } 

    @Override 
    public void deliverResult(final TaggedDrawable img) { 
     this.img = img; 
     if (isStarted()) { 
      // If the Loader is currently started, we can immediately deliver its results. 
      super.deliverResult(img); 
     } 
    } 

    @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(); 
     // At this point we can release the resources associated with 'apps' 
     // if needed. 
     if (this.img != null) { 
      this.img = null; 
     } 

    } 

} 
+0

'AsyncTask' utilizza già un pool. Il pool sale a 128 threads IIRC, che potrebbe essere la fonte della tua difficoltà. È sempre possibile implementare il proprio pool di thread utilizzando le classi 'java.util.concurrent'. – CommonsWare

+1

Se lo sviluppo è destinato a Android 3.0 (livello API 11), è possibile utilizzare l'API appena aggiunta [AsyncTask.executeOnExecutor()] (http://developer.android.com/reference/android/os/AsyncTask.html#executeOnExecutor% 28java.util.concurrent.Executor,% 20Params ...% 29) controlla bene il pool di thread con il ciclo di vita della creazione AsyncTask. – yorkw

+0

AsynkTask tuttavia può essere eseguito solo una volta, quindi ho bisogno di creare un'istanza per immagine che sto caricando. rendilo 60 e questo è un sacco di oggetti – Bostone

risposta

11

Ok, quindi prima le cose. Il AsyncTask che viene fornito con Android non dovrebbe affogare la tua app o causare l'arresto anomalo. AsyncTasks vengono eseguiti in un pool di thread in cui al momento sono in esecuzione al massimo 5 thread contemporaneamente. Mentre è possibile accodare molte attività da eseguire, solo 5 di esse sono in esecuzione alla volta. Eseguendo questi nel threadpool in background non dovrebbero avere alcun effetto sulla tua app, dovrebbero semplicemente funzionare senza intoppi.

L'utilizzo di AsyncTaskLoader non risolve il problema se non si è soddisfatti delle prestazioni del caricatore AsyncTask. AsyncTaskLoader prende solo l'interfaccia del caricatore e lo sposa ad un AsyncTask. Quindi è essenzialmente la mappatura suLoadFinished -> onPostExecute, onStart -> onLoadInBackground. Quindi è la stessa cosa esatta.

Utilizziamo lo stesso codice caricatore di immagini per la nostra app che fa sì che un asynctask venga inserito nella coda del threadpool ogni volta che proviamo a caricare un'immagine. Nell'esempio di google essi associano la visualizzazione di immagini con la relativa attività asincrona in modo che possano annullare l'attività asincrona se tentano di riutilizzare la visualizzazione di immagini in una sorta di adattatore. Dovresti prendere una strategia simile qui. Dovresti associare la tua vista di immagini con l'attività asincrona che sta caricando l'immagine in background. Quando si dispone di un frammento che non viene visualizzato, è quindi possibile scorrere le viste delle immagini associate a tale frammento e annullare le attività di caricamento. Semplicemente usando AsyncTask.cancel() dovrebbe funzionare abbastanza bene.

Si dovrebbe anche provare a implementare il meccanismo di caching dell'immagine semplice l'esempio di visualizzazione di immagine asincrona. Semplicemente creiamo una hashmap statica che va da url -> weakreference. In questo modo le immagini possono essere riciclate quando devono essere, perché sono trattenute solo con un riferimento debole.

Ecco una descrizione dei caricamento delle immagini che facciamo

public class LazyLoadImageView extends ImageView { 
     public WeakReference<ImageFetchTask> getTask() { 
     return task; 
    } 

    public void setTask(ImageFetchTask task) { 
     this.task = new WeakReference<ImageFetchTask>(task); 
    } 

    private WeakReference<ImageFetchTask> task; 

     public void loadImage(String url, boolean useCache, Drawable loadingDrawable){ 

     BitmapDrawable cachedDrawable = ThumbnailImageCache.getCachedImage(url); 
     if(cachedDrawable != null){ 
      setImageDrawable(cachedDrawable); 
      cancelDownload(url); 
      return; 
     } 

     setImageDrawable(loadingDrawable); 

     if(url == null){ 
      makeDownloadStop(); 
      return; 
     } 

     if(cancelDownload(url)){ 
      ImageFetchTask task = new ImageFetchTask(this,useCache); 
      this.task = new WeakReference<ImageFetchTask>(task); 
      task.setUrl(url); 
      task.execute(); 
     } 


     ...... 

     public boolean cancelDownload(String url){ 

     if(task != null && task.get() != null){ 

      ImageFetchTask fetchTask = task.get(); 
      String downloadUrl = fetchTask.getUrl(); 

      if((downloadUrl == null) || !downloadUrl.equals(url)){ 
       fetchTask.cancel(true); 
       return true; 
      } else 
       return false; 
     } 

     return true; 

      } 
    } 

Quindi, solo ruotare di vostro punto di vista di immagine che sono nel vostro frammento e poi cancellarli quando il vostro pelli frammento e mostrare loro quando il frammento è visibile.

+0

Questa è, liberamente, la strategia che già utilizzo anche se uso ModernAsyncTask con pool personalizzato, riferimenti deboli e annullamento di upload non visualizzati. Ma penso che Loader sia più di un semplice riutilizzo di AsyncTask. Per esempio, posso vedere che se pianifico più esecuzioni in rapida successione, l'ultima invocazione cancella quella precedente. Immagino che dovrei passare una notte semplicemente impostando il progetto di esempio e passando attraverso il codice per comprendere appieno come funziona il Loader. – Bostone

+2

Il programma di caricamento non fa nulla. AsyncTaskLoader è un'implementazione di Loader che utilizza AsyncTask. Per quanto riguarda il threading, aggiunge * nothing * che non è già presente in AsyncTask. Quando ha bisogno di cancellare qualcosa, annulla semplicemente AsyncTask che puoi fare da solo con un AsyncTask. – hackbod

+0

Va bene se Dianne ha detto così :) Ho detto che - vedo ancora il comportamento in cui più attività non sono in coda ma cancellate invece con solo l'ultima rimane. È questo il comportamento giusto? – Bostone

Problemi correlati