2012-07-11 15 views
9

Opis problemu: Tworzę przewijaną listę artykułów z miniaturami zapełnionymi przez moją bazę danych SQLite. Ogólnie rzecz biorąc, "działa" z wyjątkiem powolnego działania:Obrazy nie są buforowane lokalnie (przy użyciu programu Universal Image Loader) - czas ładowania wolnego obrazu

Obrazy ładują się bardzo powoli ... Pomyślałem, że użycie "Universal Image Loader" spowoduje zapisanie obrazów na urządzeniu i sprawi, że będą one wyglądać tak, jakby przewijały widok, już je obejrzałeś (lub przynajmniej blisko). Ale - gdy przeciągasz w górę/w dół, żadne z obrazów nie są dostępne, a następnie 3-5 sekund później, obrazy zaczynają pojawiać się (tak, jakby ponownie je pobierać)

Zmieniam widoczność miniatury pudła w locie, ale działa to bezbłędnie - nie wydają się zmieniać - po prostu przewijają się do widoku lub nie, nie migają ani nic. (ale obrazy nie pojawiają się przez kolejne kilka sekund).

Testowałem przez usunięcie mojego skryptu php po przewijaniu dookoła ... kiedy przewijam z powrotem do miejsca poprzedniego, obrazy nie pojawiają się - co powoduje, że zakładam, że ładuje się z mojego skryptu PHP KAŻDY czas.

Ale według the docs: "UsingFreqLimitedMemoryCache (najrzadziej używane bitmapy zostanie usunięta gdy zostanie przekroczony limit wielkości cache) - używana domyślnie"

szczegóły:

w moim ArticleEntryAdapter.js mam :

@Override 
public View getView(final int position, final View convertView, final ViewGroup parent) { 

    // We need to get the best view (re-used if possible) and then 
    // retrieve its corresponding ViewHolder, which optimizes lookup efficiency 
    final View view = getWorkingView(convertView); 
    final ViewHolder viewHolder = getViewHolder(view); 
    final Article article = getItem(position); 

    // Set the title 
    viewHolder.titleView.setText(article.title); 

    //Set the subtitle (subhead) or description 
    if(article.subtitle != null) 
    { 
     viewHolder.subTitleView.setText(article.subtitle); 
    } 
    else if(article.description != null) 
    { 
     viewHolder.subTitleView.setText(article.description); 
    } 

    ImageLoader imageLoader = ImageLoader.getInstance(); 

    imageLoader.displayImage("", viewHolder.thumbView); //clears previous one 
    if(article.filepath != null && article.filepath.length() != 0) { 
     imageLoader.displayImage(
      "http://img.sltdb.com/processes/resize.php?image=" + article.filepath + "&size=100&quality=70", 
      viewHolder.thumbView 
      ); 
     viewHolder.thumbView.setVisibility(View.VISIBLE); 
    } else { 
     viewHolder.thumbView.setVisibility(View.GONE); 
    } 

    return view; 
} 

Jeśli chodzi o niepoprawne obrazy - nie jest często, ale czasami przewijając, zobaczę 2 tego samego obrazu, a kiedy patrzę na artykuły, nie są one wcale powiązane (tj. nie ma szansy na to, że faktycznie mają ten sam obraz). Więc - odwracam się od niego iz powrotem, i nie jest już niepoprawnym obrazem.

UWAGA: Jestem nowy w Javie/Androidzie - prawdopodobnie już to zauważyłeś.

więcej kodu na komentarz-request:

private View getWorkingView(final View convertView) { 
    // The workingView is basically just the convertView re-used if possible 
    // or inflated new if not possible 
    View workingView = null; 

    if(null == convertView) { 
     final Context context = getContext(); 
     final LayoutInflater inflater = (LayoutInflater)context.getSystemService 
      (Context.LAYOUT_INFLATER_SERVICE); 

     workingView = inflater.inflate(articleItemLayoutResource, null); 
    } else { 
     workingView = convertView; 
    } 

    return workingView; 
} 

UPDATE: Mój plik manifestu zawiera:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

Ale folder cache znalazłem jest całkowicie pusta:

mnt 
    -sdcard 
    -Android 
     -data 
     -com.mysite.news 
      -cache 
      -uil-images 
+0

co jest 'getWorkingView (convertView);'? –

+0

@Sherif elKhatib - dodano – Dave

Odpowiedz

19

Wystąpiły podobne problemy z obrazami w widoku listy. Prawdopodobnie this answer naprawi twój zły problem z obrazem.

Właśnie pobrałem przykładowy projekt z UniversalImageLoader i pokazuje to samo zachowanie, które opisujesz.

Kilka uwag tak daleko od przejrzenia kodu źródłowego.

public static final int DEFAULT_THREAD_POOL_SIZE = 3; 
public static final int DEFAULT_THREAD_PRIORITY = Thread.NORM_PRIORITY - 1; 
public static final int DEFAULT_MEMORY_CACHE_SIZE = 2 * 1024 * 1024; // bytes 

Oznacza to, że w dowolnym momencie będą pobierane trzy wątki i maksymalnie 2 MB obrazów. Jak duże są pobierane obrazy? Również przechowujesz dane na dysku?Jeśli tak, to będzie wolno.

Aby skonfigurować kilka podstawowych opcji w imageLoader trzeba będzie przejść do displayImage:

DisplayImageOptions options = new DisplayImageOptions.Builder() 
    .showStubImage(R.drawable.stub_image) 
    .cacheInMemory() 
    .cacheOnDisc() 
    .build(); 

Chciałbym również, aby spróbować tych opcji:

ImageLoaderConfiguration imageLoaderConfiguration = new ImageLoaderConfiguration.Builder(this) 
    .enableLogging() 
    .memoryCacheSize(41943040) 
    .discCacheSize(104857600) 
    .threadPoolSize(10) 
    .build(); 

imageLoader = ImageLoader.getInstance(); 
imageLoader.init(imageLoaderConfiguration); 

Z moich testów , obrazy są na dysku, ale ładowanie jest nadal wolne.

Po szeroko zakrojonych testach zdecydowałem, że głównym problemem jest to, że UniversalImageLoader jest po prostu wolny. W szczególności, ImageLoader i LoadAndDisplayImageTask wstrzymują prace. I (bardzo szybko) przepisałem LoadAndDisplayImageTask jako AsyncTask i od razu wypadło lepiej. Możesz pobrać rozwidloną wersję kodu na GitHub.

Universal Image Loader with AsyncTasks

+0

Nieprawidłowe zdjęcie dzieje się niezbyt często - im większy problem, tym wolniejsza prędkość ładowania obrazów. Jeśli są zbuforowane, czy nie powinny po prostu pojawiać się, a nie opóźniać o kilka sekund? Chociaż doceniam notatkę i zajrzę do pozostałych pytań. – Dave

+0

Bez tej linii poprzedni obraz w tym zawróconym widoku jest wyświetlany do momentu załadowania nowego obrazu. Wyjąłem go i nadal występuje opóźnienie. Wygląda na to, że nie buforuje niczego na urządzeniu - jeśli załaduję aplikację, przewiń w dół, usuń mój kod PHP, a następnie przewiń w górę, obrazy nie są wyświetlane. – Dave

+0

Obrazy nie są buforowane na dysku (chciałbym je jednak).Spróbuję twoich sugestii i mam nadzieję, ale zgodnie z dokumentami, DEFAULT powinien zapisywać je na dysk. Moje obrazy są małe - 100x100px – Dave

1

podejrzewam resize.php jest powolny, zwłaszcza jeśli ma się do rozmiaru dużych stron i kilka wniosków. I jakoś buforowanie w imageLoader nie jest wykonywane.

Najpierw zrobię resztę po ładowanie obrazu: podtytuł, opis i wszystko. Ponieważ jeśli ładowanie obrazu trwa zbyt długo, pojawia się bardziej natychmiastowy efekt, jeśli opis i cała reszta pojawiają się razem. Zazwyczaj kolejność poleceń jest w porządku.

Odpowiedź @CameronLowellPallmer zajmuje się przełączanymi obrazami i buforowaniem.

+0

Pliki resize.php są zapisywane w pamięci podręcznej i ładowane natychmiast po kliknięciu adresu URL w przeglądarce (ponieważ są to pliki TINY). Nie jestem pewien, co masz na myśli przez "odpoczynek po wczytaniu obrazu ..." (pamiętaj, że jestem noobem :) – Dave

+0

Przesunąłem obrazy przed tytułem ... itd. - bez zmian. – Dave

+0

(a co ważniejsze, obrazy mają być buforowane na urządzeniu - jeśli za każdym razem uderza w mój skrypt PHP, to jest to kwestia sama w sobie!) – Dave

3

Alternatywnym rozwiązaniem jest "RemoteImageView" z projektu otwartego źródła zapłonu.

http://kaeppler.github.com/ignition-docs/ignition-core/apidocs/com/github/ignition/core/widgets/RemoteImageView.html

Skutecznie, RemoteImageView rozciąga ImageView i robi wszystko ściągam/cache dla ciebie za kulisami.

Chociaż nie musi to rozwiązać problemu, który podałeś, warto rozważyć alternatywne rozwiązanie.

EDYCJA: Gorąco polecam Picasso, jeśli nadal potrzebujesz zdalnego rozwiązania graficznego. Mam zastąpiony RemoteImageView w mojej aplikacji z Picasso: http://square.github.io/picasso/

+0

Czy są tam jakieś strony instruktażowe/tutoriale? – Dave

+0

Przykład działania w projekcie zapłonu ma bardzo proste zastosowanie tutaj: https://github.com/kaeppler/ignition/blob/master/ignition-core/ignition-core-samples/src/com/github/ignition/samples /core/RemoteImageViewActivity.java – theelfismike

0

Klasa ta pracował dla mnie:

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.lang.ref.WeakReference; 
import java.util.HashMap; 
import java.util.Map; 

import org.apache.http.HttpEntity; 
import org.apache.http.HttpResponse; 
import org.apache.http.HttpStatus; 
import org.apache.http.HttpVersion; 
import org.apache.http.client.HttpClient; 
import org.apache.http.client.methods.HttpGet; 
import org.apache.http.impl.client.DefaultHttpClient; 
import org.apache.http.params.BasicHttpParams; 
import org.apache.http.params.CoreProtocolPNames; 
import org.apache.http.params.HttpParams; 

import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Color; 
import android.graphics.drawable.ColorDrawable; 
import android.graphics.drawable.Drawable; 
import android.os.AsyncTask; 
import android.util.Log; 
import android.widget.ImageView; 

public class ImageDownloader { 

    Map<String,Bitmap> imageCache; 

    public ImageDownloader(){ 
     imageCache = new HashMap<String, Bitmap>(); 

    } 

    //download function 
    public void download(String url, ImageView imageView) { 
     if (cancelPotentialDownload(url, imageView)&&url!=null) { 

      //Caching code right here 
      String filename = String.valueOf(url.hashCode()); 
      File f = new File(getCacheDirectory(imageView.getContext()), filename); 

       // Is the bitmap in our memory cache? 
      Bitmap bitmap = null; 

       bitmap = (Bitmap)imageCache.get(f.getPath()); 
       BitmapFactory.Options bfOptions=new BitmapFactory.Options(); 
       bfOptions.inDither=false;      //Disable Dithering mode 
       bfOptions.inPurgeable=true;     //Tell to gc that whether it needs free memory, the Bitmap can be cleared 
       bfOptions.inInputShareable=true;    //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future 
       bfOptions.inTempStorage=new byte[32 * 1024]; 
       FileInputStream fs=null; 

       if(bitmap == null){ 

        //bitmap = BitmapFactory.decodeFile(f.getPath(),options); 
        try { 
         fs = new FileInputStream(f); 
         if(fs!=null) bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions); 
        } catch (IOException e) { 
         //TODO do something intelligent 
         e.printStackTrace(); 
        } finally{ 
         if(fs!=null) { 
          try { 
           fs.close(); 
          } catch (IOException e) { 
           // TODO Auto-generated catch block 
           e.printStackTrace(); 
          } 
         } 
        } 

        if(bitmap != null){ 
         imageCache.put(f.getPath(), bitmap); 
        } 

       } 
       //No? download it 
       if(bitmap == null){ 
        BitmapDownloaderTask task = new BitmapDownloaderTask(imageView); 
        DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task); 
        imageView.setImageDrawable(downloadedDrawable); 
        task.execute(url); 
       }else{ 
        //Yes? set the image 
        imageView.setImageBitmap(bitmap); 
       } 
     } 
    } 

    //cancel a download (internal only) 
    private static boolean cancelPotentialDownload(String url, ImageView imageView) { 
     BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); 

     if (bitmapDownloaderTask != null) { 
      String bitmapUrl = bitmapDownloaderTask.url; 
      if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) { 
       bitmapDownloaderTask.cancel(true); 
      } else { 
       // The same URL is already being downloaded. 
       return false; 
      } 
     } 
     return true; 
    } 

    //gets an existing download if one exists for the imageview 
    private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) { 
     if (imageView != null) { 
      Drawable drawable = imageView.getDrawable(); 
      if (drawable instanceof DownloadedDrawable) { 
       DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable; 
       return downloadedDrawable.getBitmapDownloaderTask(); 
      } 
     } 
     return null; 
    } 

    //our caching functions 
    // Find the dir to save cached images 
    public static File getCacheDirectory(Context context){ 
     String sdState = android.os.Environment.getExternalStorageState(); 
     File cacheDir; 

     if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) { 
      File sdDir = android.os.Environment.getExternalStorageDirectory(); 

      //TODO : Change your diretcory here 
      cacheDir = new File(sdDir,"data/tac/images"); 
     } 
     else 
      cacheDir = context.getCacheDir(); 

     if(!cacheDir.exists()) 
      cacheDir.mkdirs(); 
      return cacheDir; 
    } 

    private void writeFile(Bitmap bmp, File f) { 
      FileOutputStream out = null; 

      try { 
      out = new FileOutputStream(f); 
      bmp.compress(Bitmap.CompressFormat.PNG, 80, out); 
      } catch (Exception e) { 
      e.printStackTrace(); 
      } 
      finally { 
      try { if (out != null) out.close(); } 
      catch(Exception ex) {} 
      } 
    } 
    /////////////////////// 

    //download asynctask 
    public class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { 
     private String url; 
     private final WeakReference<ImageView> imageViewReference; 

     public BitmapDownloaderTask(ImageView imageView) { 
      imageViewReference = new WeakReference<ImageView>(imageView); 
     } 

     @Override 
     // Actual download method, run in the task thread 
     protected Bitmap doInBackground(String... params) { 
      // params comes from the execute() call: params[0] is the url. 
      url = (String)params[0]; 
      return downloadBitmap(params[0]); 
     } 

     @Override 
     // Once the image is downloaded, associates it to the imageView 
     protected void onPostExecute(Bitmap bitmap) { 
      if (isCancelled()) { 
       bitmap = null; 
      } 

      if (imageViewReference != null) { 
       ImageView imageView = imageViewReference.get(); 
       BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); 
       // Change bitmap only if this process is still associated with it 
       if (this == bitmapDownloaderTask) { 
        imageView.setImageBitmap(bitmap); 

        //cache the image 


        String filename = String.valueOf(url.hashCode()); 
        File f = new File(getCacheDirectory(imageView.getContext()), filename); 

        imageCache.put(f.getPath(), bitmap); 

        writeFile(bitmap, f); 
       } 
      } 
     } 


    } 

    static class DownloadedDrawable extends ColorDrawable { 
     private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference; 

     public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) { 
      super(Color.BLACK); 
      bitmapDownloaderTaskReference = 
       new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask); 
     } 

     public BitmapDownloaderTask getBitmapDownloaderTask() { 
      return bitmapDownloaderTaskReference.get(); 
     } 
    } 

    //the actual download code 
    static Bitmap downloadBitmap(String url) { 
     HttpParams params = new BasicHttpParams(); 
     params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); 
     HttpClient client = new DefaultHttpClient(params); 
     final HttpGet getRequest = new HttpGet(url); 

     try { 
      HttpResponse response = client.execute(getRequest); 
      final int statusCode = response.getStatusLine().getStatusCode(); 
      if (statusCode != HttpStatus.SC_OK) { 
       Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); 
       return null; 
      } 

      final HttpEntity entity = response.getEntity(); 
      if (entity != null) { 
       InputStream inputStream = null; 
       try { 
        inputStream = entity.getContent(); 
        final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); 
        return bitmap; 
       } finally { 
        if (inputStream != null) { 
         inputStream.close(); 
        } 
        entity.consumeContent(); 
       } 
      } 
     } catch (Exception e) { 
      // Could provide a more explicit error message for IOException or IllegalStateException 
      getRequest.abort(); 
      Log.w("ImageDownloader", "Error while retrieving bitmap from " + url + e.toString()); 
     } finally { 
      if (client != null) { 
       //client.close(); 
      } 
     } 
     return null; 
    } 
} 

Przykład zastosowania:

downloader = new ImageDownloader(); 
ImageView image_profile =(ImageView) row.findViewById(R.id.image_profile); 
downloader.download(url, image_profile); 
Powiązane problemy