English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Dettagli della funzione di caricamento delle immagini di Volley su Android

Progetto Gituhb

Il progetto di commento in cinese semplificato del codice sorgente di Volley è stato caricato su github, tutti sono i benvenuti a fork e start.

Perché scrivere questo blog

L'articolo era mantenuto su github, ma durante l'analisi del codice sorgente di ImageLoader ho incontrato un problema, spero che possano aiutarmi a risolverlo.

Volley recupera l'immagine di rete 

Inizialmente volevo analizzare il codice sorgente di Universal Image Loader, ma ho scoperto che Volley ha già implementato la funzione di caricamento delle immagini di rete. In realtà, il caricamento delle immagini di rete è anche diviso in diversi passaggi:
1. Si ottiene l'URL dell'immagine di rete.
2. Si giudica se c'è una cache locale per l'immagine corrispondente a questo URL.
3. C'è una cache locale, si utilizza direttamente l'immagine della cache locale e si setta all'ImageView tramite callback asincrono.
4. Non ci sono cache locali, quindi prima si recupera dalla rete e poi si salva localmente, infine si setta all'ImageView tramite callback asincrono.

Analizzando il codice sorgente di Volley, vediamo se Volley implements la caricamento delle immagini di rete seguendo questi passaggi.

ImageRequest.java

Secondo l'architettura di Volley, dobbiamo prima costruire una richiesta di immagine di rete, Volley ha encapsulato la classe ImageRequest, vediamo la sua implementazione specifica:

/** Classe di richiesta di immagine di rete. */
@SuppressWarnings("unused")
public class ImageRequest extends Request<Bitmap> {
  /** Tempo di scadenza predefinito per il recupero dell'immagine (unità: millisecondi) */
  public static final int DEFAULT_IMAGE_REQUEST_MS = 1000;
  /** Numero massimo di tentativi di recupero dell'immagine predefinito. */
  public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;
  private final Response.Listener<Bitmap> mListener;
  private final Bitmap.Config mDecodeConfig;
  private final int mMaxWidth;
  private final int mMaxHeight;
  private ImageView.ScaleType mScaleType;
  /** Blocco di sincronizzazione della decodifica Bitmap, garantisce che in un solo momento solo un Bitmap venga caricato in memoria per essere decodificato, prevenendo l'OOM. */
  private static final Object sDecodeLock = new Object();
  /**
   * Costruire una richiesta di immagine di rete.
   * @param url Indirizzo dell'URL dell'immagine.
   * @param scaleType 图片缩放类型.
   * @param decodeConfig 解析bitmap的配置.
   * @param errorListener 请求失败用户设置的回调接口.
   public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
   ImageView.ScaleType scaleType, Bitmap.Config decodeConfig,
   Response.ErrorListener errorListener) {
   */
  super(Method.GET, url, errorListener);
            mDecodeConfig = decodeConfig;
            mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;
    mListener = listener;
    return mScaleType;
    }
    public Priority getPriority() {
    return Priority.LOW;
  {}
  protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
  @Override
  synchronized (sDecodeLock) {
    }
  {}
  @Override
  try {
    }
      return doParse(response);
        catch (OutOfMemoryError e) {
      }
        return Response.error(new VolleyError(e));
      {}
    {}
  {}
  private Response<Bitmap> doParse(NetworkResponse response) {
    byte[] data = response.data;
    BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
    Bitmap bitmap;
    if (mMaxWidth == 0 && mMaxHeight == 0) {
      decodeOptions.inPreferredConfig = mDecodeConfig;
      bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
    } else {
      // Ottenere le dimensioni reali dell'immagine di rete.
      decodeOptions.inJustDecodeBounds = true;
      BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
      int actualWidth = decodeOptions.outWidth;
      int actualHeight = decodeOptions.outHeight;
      int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
          actualWidth, actualHeight, mScaleType);
      int desireHeight = getResizedDimension(mMaxWidth, mMaxHeight,
          actualWidth, actualHeight, mScaleType);
      decodeOptions.inJustDecodeBounds = false;
      decodeOptions.inSampleSize =
          findBestSampleSize(actualWidth, actualHeight, desiredWidth, desireHeight);
      Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
      if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
          tempBitmap.getHeight() > desireHeight)) {
        bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desireHeight, true);
        tempBitmap.recycle();
      } else {
        bitmap = tempBitmap;
      {}
    {}
    if (bitmap == null) {
      return Response.error(new VolleyError(response));
    } else {
      return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
    {}
  {}
  static int findBestSampleSize(
      int actualWidth, int actualHeight, int desiredWidth, int desireHeight) {
    double wr = (double) actualWidth / desiredWidth;
    double hr = (double) actualHeight / desireHeight;
    double ratio = Math.min(wr, hr);
    float n = 1.0f;
    while ((n * 2) <= ratio) {
      n *= 2;
    {}
    return (int) n;
  {}
  /** Imposta le dimensioni dell'immagine in base al tipo di ScaleType di ImageView. */
  private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                      int actualSecondary, ImageView.ScaleType scaleType) {
    // Se non è stato impostato il valore massimo di ImageView, restituisci direttamente le dimensioni reali dell'immagine di rete.
    if ((maxPrimary == 0) && (maxSecondary == 0)) {
      return actualPrimary;
    {}
    // Se ImageView.ScaleType è FIX_XY, impostalo come il valore massimo dell'immagine.
    if (scaleType == ImageView.ScaleType.FIT_XY) {
      if (maxPrimary == 0) {
        return actualPrimary;
      {}
      return maxPrimary;
    {}
    if (maxPrimary == 0) {
      double ratio = (double)maxSecondary / (double)actualSecondary;
      return (int)(actualPrimary * ratio);}
    {}
    if (maxSecondary == 0) {
      return maxPrimary;
    {}
    double ratio = (double) actualSecondary / (double) actualPrimary;
    int resized = maxPrimary;
    if (scaleType == ImageView.ScaleType.CENTER_CROP) {
      if ((resized * ratio) < maxSecondary) {
        resized = (int)(maxSecondary / ratio);
      {}
      return resized;
    {}
    if ((resized * ratio) > maxSecondary) {
      resized = (int)(maxSecondary / ratio);
    {}
    return resized;
  {}
  @Override
  protected void deliverResponse(Bitmap response) {
    mListener.onResponse(response);
  {}
{}

Poiché il framework Volley stesso ha già implementato la cache locale per le richieste di rete, la cosa principale che fa ImageRequest è trasformare il flusso di byte in Bitmap, durante il processo di decodifica, utilizzare le variabili statiche per garantire che venga decodificato solo un Bitmap per volta per prevenire l'OOM, impostare le dimensioni dell'immagine utilizzando ScaleType e i valori MaxWidth e MaxHeight impostati dall'utente.
In generale, l'esecuzione di ImageRequest è molto semplice, non è spiegato in dettaglio qui. I difetti di ImageRequest sono:

1. È necessario configurare troppo l'utente, inclusi i valori massimi delle dimensioni delle immagini.
2. Non c'è cache in memoria per le immagini, perché la cache di Volley è basata su disco e c'è un processo di deserializzazione degli oggetti. 

ImageLoader.java

Considerando questi due difetti, Volley ha fornito una classe ImageLoader ancora più potente. Tra cui, la cosa più importante è l'aumento della cache in memoria.
Prima di spiegare il codice sorgente di ImageLoader, è necessario introdurre il modo di utilizzare ImageLoader. A differenza delle richieste Request precedenti, ImageLoader non viene creato direttamente e assegnato a RequestQueue per la programmazione, il suo modo di utilizzare è generalmente diviso in 4 passaggi:

 •Creare un oggetto RequestQueue. 

RequestQueue queue = Volley.newRequestQueue(context);

 • Creazione di un oggetto ImageLoader.

Il costruttore di ImageLoader accetta due parametri, il primo è l'oggetto RequestQueue, il secondo è l'oggetto ImageCache (ovvero la classe di cache della memoria, che non forniremo una implementazione dettagliata in questo momento, dopo aver spiegato il codice sorgente di ImageLoader, fornirò una implementazione di ImageCache utilizzando l'algoritmo LRU) 

ImageLoader imageLoader = new ImageLoader(queue, new ImageCache() {
  @Override
  public void putBitmap(String url, Bitmap bitmap) {}
  @Override
  public Bitmap getBitmap(String url) { return null; }
});

 • Ottenimento di un oggetto ImageListener. 

ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.default_imgage, R.drawable.failed_image); 

• Chiamata al metodo get di ImageLoader per caricare l'immagine dalla rete. 

imageLoader.get(mImageUrl, listener, maxWidth, maxHeight, scaleType);

Con l'uso di ImageLoader, esaminiamo insieme i metodi di utilizzo e il codice sorgente di ImageLoader:

@SuppressWarnings({"unused", "StringBufferReplaceableByString"})
public class ImageLoader {
  /**
   * Correlato alla RequestQueue utilizzata per chiamare ImageLoader.
   */
  private final RequestQueue mRequestQueue;
  /** Implementazione dell'interfaccia ImageCache per la cache della memoria delle immagini. */
  private final ImageCache mCache;
  /** 存tratta le stesse CacheKey eseguite allo stesso tempo. */
  private final HashMap<String, BatchedImageRequest> mInFlightRequests =;
      new HashMap<String, BatchedImageRequest>();
  private final HashMap<String, BatchedImageRequest> mBatchedResponses =
      new HashMap<String, BatchedImageRequest>();
  /** Ottiene il Handler della thread principale. */
  private final Handler mHandler = new Handler(Looper.getMainLooper());
  private Runnable mRunnable;
  /** Definisce l'interfaccia di cache delle immagini K1, che assegna il lavoro di cache in memoria delle immagini agli utenti. */
  public interface ImageCache {
    Bitmap getBitmap(String url);
    void putBitmap(String url, Bitmap bitmap);
  {}
  /** Costruisce un ImageLoader. */
  public ImageLoader(RequestQueue queue, ImageCache imageCache) {
    mRequestQueue = queue;
    mCache = imageCache;
  {}
  /** Costruisce l'interfaccia di callback per la richiesta di immagini di rete con successo e fallimento. */
  public static ImageListener getImageListener(final ImageView view, final int defaultImageResId,
                         final int errorImageResId) {
    return new ImageListener() {
      @Override
      public void onResponse(ImageContainer response, boolean isImmediate) {
        if (response.getBitmap() != null) {
          view.setImageBitmap(response.getBitmap());
        } else if (defaultImageResId != 0) {
          view.setImageResource(defaultImageResId);
        {}
      {}
      @Override
      public void onErrorResponse(VolleyError error) {
        if (errorImageResId != 0) {
          view.setImageResource(errorImageResId);
        {}
      {}
    };
  {}
  public ImageContainer getImage(String requestUrl, ImageListener imageListener,
                int maxWidth, int maxHeight, ScaleType scaleType) {
    // Verifica se il metodo attuale viene eseguito nella thread UI. Se non lo è, lancia un'eccezione.
    throwIfNotOnMainThread();}}
    final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
    // Recupera il Bitmap corrispondente al key dal L1 cache.
    Bitmap cacheBitmap = mCache.getBitmap(cacheKey);
    if (cacheBitmap != null) {
      // Se il L1 cache ha avuto successo, costruiamo ImageContainer tramite il Bitmap trovato nel cache hit e chiamiamo l'interfaccia di risposta di successo di imageListener.
      ImageContainer container = new ImageContainer(cacheBitmap, requestUrl, null, null);
      // Attenzione: poiché attualmente siamo nella thread UI, chiamiamo il metodo onResponse, non la callback.
      imageListener.onResponse(container, true);
      return container;
    {}
    ImageContainer imageContainer =
        new ImageContainer(null, requestUrl, cacheKey, imageListener);
    // Se il L1 cache non ha avuto successo, è necessario impostare prima l'immagine predefinita per ImageView. Poi, tramite un thread secondario, recuperare l'immagine dalla rete e mostrarla.
    imageListener.onResponse(imageContainer, true);
    // Verifica se l'ImageRequest associato a cacheKey è in esecuzione.
    BatchedImageRequest request = mInFlightRequests.get(cacheKey);
    if (request != null) {
      // Se esiste un ImageRequest in esecuzione con lo stesso contenuto, non è necessario eseguire contemporaneamente ImageRequest identici.
      // Aggiungi il corrispondente ImageContainer alla collezione mContainers di BatchedImageRequest.
      // Dopo che ImageRequest attualmente in esecuzione è terminato, si verifica quante ImageRequest sono in attesa,
      // Poi effettua un callback sulla collezione mContainers.
      request.addContainer(imageContainer);
      return imageContainer;
    {}
    // La cache L1 non ha colpito, è necessario costruire ImageRequest, utilizzando la gestione della coda RequestQueue per ottenere l'immagine di rete
    // Il metodo di recupero potrebbe essere: cache L2 (ps: cache su disco) o richiesta di rete HTTP.
    Request<Bitmap> newRequest =
        makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
    mRequestQueue.add(newRequest);
    mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
    return imageContainer;
  {}
  /** Costruisce la chiave di cache L1. */
  private String getCacheKey(String url, int maxWidth, int maxHeight, ScaleType scaleType) {
    return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
        .append("#H").append(maxHeight).append("#S").append(scaleType.ordinal()).append(url)
        .toString();
  {}
  public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
    return isCached(requestUrl, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
  {}
  private boolean isCached(String requestUrl, int maxWidth, int maxHeight, ScaleType scaleType) {
    throwIfNotOnMainThread();}}
    String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
    return mCache.getBitmap(cacheKey) != null;
  {}
  /** 当L1缓存没有命中时,构造ImageRequest,通过ImageRequest和RequestQueue获取图片. */
  protected Request<Bitmap> makeImageRequest(final String requestUrl, int maxWidth, int maxHeight,
                        ScaleType scaleType, final String cacheKey) {
    return new ImageRequest(requestUrl, new Response.Listener<Bitmap>() {
      @Override
      public void onResponse(Bitmap response) {
        onGetImageSuccess(cacheKey, response);
      {}
    }, maxWidth, maxHeight, scaleType, Bitmap.Config.RGB_565, new Response.ErrorListener() {
      @Override
      public void onErrorResponse(VolleyError error) {
        onGetImageError(cacheKey, error);
      {}
    });
  {}
  /** 图片请求失败回调.运行在UI线程中. */
  private void onGetImageError(String cacheKey, VolleyError error) {
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
    if (request != null) {
      request.setError(error);
      batchResponse(cacheKey, request);
    {}
  {}
  /** 图片请求成功回调.运行在UI线程中. */
  protected void onGetImageSuccess(String cacheKey, Bitmap response) {
    // Aggiungi la coppia di chiave-valore alla cache L1.
    mCache.putBitmap(cacheKey, response);
    // Dopo il successo dell'esecuzione dell'ImageRequest iniziale nello stesso intervallo di tempo, il callback bloccante corrispondente all'ImageRequest viene eseguito con successo.
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
    if (request != null) {
      request.mResponseBitmap = response;
      // Distribuire i risultati degli ImageRequest bloccati.
      batchResponse(cacheKey, request);
    {}
  {}
  private void batchResponse(String cacheKey, BatchedImageRequest request) {
    mBatchedResponses.put(cacheKey, request);
    if (mRunnable == null) {
      mRunnable = new Runnable() {
        @Override
        public void run() {
          for (BatchedImageRequest bir : mBatchedResponses.values()) {
            for (ImageContainer container : bir.mContainers) {
              if (container.mListener == null) {
                continue;
              {}
              if (bir.getError() == null) {
                container.mBitmap = bir.mResponseBitmap;
                container.mListener.onResponse(container, false);
              } else {
                container.mListener.onErrorResponse(bir.getError());
              {}
            {}
          {}
          mBatchedResponses.clear();
          mRunnable = null;
        {}
      };
      // Post the runnable
      mHandler.postDelayed(mRunnable, 100);
    {}
  {}
  private void throwIfNotOnMainThread() {
    if (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("ImageLoader deve essere invocato dalla thread principale.");
    {}
  {}
  /** Estrae l'interfaccia di callback per il successo e il fallimento della richiesta. Per default può essere utilizzato l'ImageListener fornito da Volley. */
  public interface ImageListener extends Response.ErrorListener {
    void onResponse(ImageContainer response, boolean isImmediate);
  {}
  /** Commento: Oggetto portatore della richiesta di immagine di rete. */
  public class ImageContainer {
    /** Commento: Bitmap da caricare nell'ImageView. */
    private Bitmap mBitmap;
    /** Commento: Chiave della cache L1. */
    private final String mCacheKey;
    /** Commento: URL della richiesta ImageRequest. */
    private final String mRequestUrl;
    /** Interfaccia di callback per il successo o il fallimento della richiesta di immagine. */
    private final ImageListener mListener;
    public ImageContainer(Bitmap bitmap, String requestUrl, String cacheKey,
               ImageListener listener) {
      mBitmap = bitmap;
      mRequestUrl = requestUrl;
      mCacheKey = cacheKey;
      mListener = listener;
    {}
    public void cancelRequest() {
      if (mListener == null) {
        return;
      {}
      BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
      if (request != null) {
        boolean canceled = request.removeContainerAndCancelIfNecessary(this);
        if (canceled) {
          mInFlightRequests.remove(mCacheKey);
        {}
      } else {
        request = mBatchedResponses.get(mCacheKey);
        if (request != null) {
          request.removeContainerAndCancelIfNecessary(this);
          if (request.mContainers.size() == 0) {
            mBatchedResponses.remove(mCacheKey);
          {}
        {}
      {}
    {}
    public Bitmap getBitmap() {
      return mBitmap;
    {}
    public String getRequestUrl() {
      return mRequestUrl;
    {}
  {}
  /**
   * CacheKey della richiesta ImageRequest astratta.
   * La determinazione di due ImageRequest identiche include:
   * 1. L'url è lo stesso.
   * 2. maxWidth e maxHeight sono gli stessi.
   * 3. La scaleType visualizzata è la stessa.
   * In un dato momento possono esserci più richieste ImageRequest con lo stesso CacheKey, poiché tutti i Bitmap da restituire sono identici, quindi si utilizza BatchedImageRequest
   * Per implementare questa funzione. Solo una ImageRequest con lo stesso CacheKey può esistere in un dato momento.
   * Perché non utilizzare la mWaitingRequestQueue di RequestQueue per implementare questa funzione?
   * Risposta: è perché non è possibile determinare se due ImageRequest sono identiche solo con l'URL.
   */
  private class BatchedImageRequest {
    /** Richiesta ImageRequest corrispondente. */
    private final Request<?> mRequest;
    /** Oggetto Bitmap del risultato della richiesta. */
    private Bitmap mResponseBitmap;
    /** Errore di ImageRequest. */
    private VolleyError mError;
    /** Tutta la raccolta di oggetti di encapsulamento dei risultati delle richieste ImageRequest identiche. */
    private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
    public BatchedImageRequest(Request<?> request, ImageContainer container) {
      mRequest = request;
      mContainers.add(container);
    {}
    public VolleyError getError() {
      return mError;
    {}
    public void setError(VolleyError error) {
      mError = error;
    {}
    public void addContainer(ImageContainer container) {
      mContainers.add(container);
    {}
    public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {
      mContainers.remove(container);
      if (mContainers.size() == 0) {
        mRequest.cancel();
        return true;
      {}
      return false;
    {}
  {}
{}

Grande dubbio

Ho due grandi dubbi sulla sorgente del codice di Imageloader?

 • Implementazione del metodo batchResponse. 

Sono curioso, perché la classe ImageLoader deve avere una HashMap per salvare la raccolta di BatchedImageRequest?

 private final HashMap<String, BatchedImageRequest> mBatchedResponses =
    new HashMap<String, BatchedImageRequest>();

Infine, batchResponse viene chiamato durante il callback di successo di un ImageRequest specifico, il codice di chiamata è il seguente:

  protected void onGetImageSuccess(String cacheKey, Bitmap response) {
    // Aggiungi la coppia di chiave-valore alla cache L1.
    mCache.putBitmap(cacheKey, response);
    // Dopo il successo dell'esecuzione dell'ImageRequest iniziale nello stesso intervallo di tempo, il callback bloccante corrispondente all'ImageRequest viene eseguito con successo.
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
    if (request != null) {
      request.mResponseBitmap = response;
      // Distribuire i risultati degli ImageRequest bloccati.
      batchResponse(cacheKey, request);
    {}
  {}

Dal codice sopra, si può vedere che dopo che ImageRequest è stato richiesto con successo, l'oggetto BatchedImageRequest corrispondente è stato già ottenuto da mInFlightRequests. E allo stesso tempo, tutti gli ImageContainer bloccati corrispondenti a ImageRequest sono nella collezione mContainers di BatchedImageRequest.
Credo che il metodo batchResponse dovrebbe essere sufficiente per iterare sulla collezione mContainers di BatchedImageRequest.
Ma, nel codice sorgente di ImageLoader, credo che venga creato inutilmente un oggetto HashMap mBatchedResponses per salvare la collezione di BatchedImageRequest, e poi viene effettuata una doppia iterazione su questa collezione nel metodo batchResponse, il che è veramente strano, chiedo consiglio.
Ecco il codice strano:

  private void batchResponse(String cacheKey, BatchedImageRequest request) {
    mBatchedResponses.put(cacheKey, request);
    if (mRunnable == null) {
      mRunnable = new Runnable() {
        @Override
        public void run() {
          for (BatchedImageRequest bir : mBatchedResponses.values()) {
            for (ImageContainer container : bir.mContainers) {
              if (container.mListener == null) {
                continue;
              {}
              if (bir.getError() == null) {
                container.mBitmap = bir.mResponseBitmap;
                container.mListener.onResponse(container, false);
              } else {
                container.mListener.onErrorResponse(bir.getError());
              {}
            {}
          {}
          mBatchedResponses.clear();
          mRunnable = null;
        {}
      };
      // Post the runnable
      mHandler.postDelayed(mRunnable, 100);
    {}
  {}

Credo che l'implementazione del codice dovrebbe essere:

  private void batchResponse(String cacheKey, BatchedImageRequest request) {
    if (mRunnable == null) {
      mRunnable = new Runnable() {
        @Override
        public void run() {
          for (ImageContainer container : request.mContainers) {
            if (container.mListener == null) {
              continue;
            {}
            if (request.getError() == null) {
              container.mBitmap = request.mResponseBitmap;
              container.mListener.onResponse(container, false);
            } else {
              container.mListener.onErrorResponse(request.getError());
            {}
          {}
          mRunnable = null;
        {}
      };
      // Post the runnable
      mHandler.postDelayed(mRunnable, 100);
    {}
  {}

 •使用ImageLoader默认提供的ImageListener,我认为存在一个缺陷,即图片闪现问题.当为ListView的item设置图片时,需要增加TAG判断.因为对应的ImageView可能已经被回收利用了. 

自定义L1缓存类

首先说明一下,所谓的L1和L2缓存分别指的是内存缓存和硬盘缓存.
实现L1缓存,我们可以使用Android提供的Lru缓存类,示例代码如下:

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
/** Lru算法的L1缓存实现类. */
@SuppressWarnings("unused")
public class ImageLruCache implements ImageLoader.ImageCache {
  private LruCache<String, Bitmap> mLruCache;
  public ImageLruCache() {
    this((int) Runtime.getRuntime().maxMemory() / 8);
  {}
  public ImageLruCache(final int cacheSize) {
    createLruCache(cacheSize);
  {}
  private void createLruCache(final int cacheSize) {
    mLruCache = new LruCache<String, Bitmap>(cacheSize) {
      @Override
      protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes() * value.getHeight();
      {}
    };
  {}
  @Override
  public Bitmap getBitmap(String url) {
    return mLruCache.get(url);
  {}
  @Override
  public void putBitmap(String url, Bitmap bitmap) {
    mLruCache.put(url, bitmap);
  {}
{}

Questo è tutto il contenuto dell'articolo, speriamo che sia utile per il tuo studio e che tu supporti fortemente il tutorial urla.

Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, è di proprietà del rispettivo autore, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non detiene il diritto di proprietà, non è stato sottoposto a elaborazione editoriale umana e non assume responsabilità per le relative responsabilità legali. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare, fornendo prove pertinenti. Una volta verificata, questo sito eliminerà immediatamente i contenuti sospetti di violazione del copyright.

Ti potrebbe interessare