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

Detailed Explanation of Android Volley Image Loading Function

Gituhb项目

Volley源码中文注释项目我已经上传到github,欢迎大家fork和start.

为什么写这篇博客

本来文章是维护在github上的,但是我在分析ImageLoader源码过程中与到了一个问题,希望大家能帮助解答.

Volley获取网络图片 

本来想分析Universal Image Loader的源码,但是发现Volley已经实现了网络图片的加载功能.其实,网络图片的加载也是分几个步骤:
1. 获取网络图片的url.
2. 判断该url对应的图片是否有本地缓存.
3. 有本地缓存,直接使用本地缓存图片,通过异步回调给ImageView进行设置.
4. 无本地缓存,就先从网络拉取,保存在本地后,再通过异步回调给ImageView进行设置.

我们通过Volley源码,看一下Volley是否是按照这个步骤实现网络图片加载的.

ImageRequest.java

按照Volley的架构,我们首先需要构造一个网络图片请求,Volley帮我们封装了ImageRequest类,我们来看一下它的具体实现:

/** 网络图片请求类. */
@SuppressWarnings("unused")
public class ImageRequest extends Request<Bitmap> {
  /** 默认图片获取的超时时间(单位:毫秒) */
  public static final int DEFAULT_IMAGE_REQUEST_MS = 1000;
  /** 默认图片获取的重试次数. */
  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;
  /** Synchronisationslock für die Bitmap-Analysis, um sicherzustellen, dass gleichzeitig nur ein Bitmap in den Speicher geladen und analysiert wird, um OOM zu verhindern. */
  private static final Object sDecodeLock = new Object();
  /**
   * Erstellen Sie eine Netzwerkabbildanforderung.
   * @param url URL des Bildes.
   * @param listener Rückrufinterface für den Erfolg des Benutzers.
   * @param maxWidth Maximale Breite des Bildes.
   * @param maxHeight Maximale Höhe des Bildes.
   * @param scaleType Typ der Bildvergrößerung.
   * @param decodeConfig Konfiguration für die Analyse des Bitmaps.
   * @param errorListener Rückrufinterface für den Fehlerfall des Benutzers.
   */
  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);
    mListener = listener;
    mDecodeConfig = decodeConfig;
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;
    mScaleType = scaleType;
  }
  /** Einstellen der Priorität der Netzwerkabbildanforderung. */
  @Override
  public Priority getPriority() {
    return Priority.LOW;
  }
  @Override
  protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
    synchronized (sDecodeLock) {
      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 {
      // Erhalten Sie die tatsächliche Größe des Netzwerkbildes.
      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;
  }
  /** According to the ScaleType of ImageView, set the size of the image. */
  private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                      int actualSecondary, ImageView.ScaleType scaleType) {
    // Wenn keine Maximalwerte für das ImageView gesetzt wurden, wird die tatsächliche Größe des Netzwerkbildes direkt zurückgegeben.
    if ((maxPrimary == 0) && (maxSecondary == 0)) {
      return actualPrimary;
    }
    // Wenn der ScaleType des ImageView FIX_XY ist, wird er auf den Wert des Bildes eingestellt.
    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);
  }
}

Da Volley selbst bereits ein lokales Caching für Netzwerkanfragen implementiert hat, bezieht sich das Hauptthema von ImageRequest darauf, Byte-Streams in Bitmaps zu parsen und dabei durch statische Variablen sicherzustellen, dass immer nur ein Bitmap geparsed wird, um OOM zu vermeiden. Die Größe des Bildes wird mit ScaleType und den von Benutzer gesetzten MaxWidth und MaxHeight eingestellt.
Im Allgemeinen ist die Implementierung von ImageRequest sehr einfach, daher werde ich hier keine ausführliche Erklärung geben. Der Nachteil von ImageRequest liegt darin:

1.Benötigt viele Einstellungen durch den Benutzer, einschließlich der maximalen Größe der Bilder.
2.Es gibt keine Bildspeichercache, da der Cache von Volley auf Disk basiert und ein Objekt-Deserialisierungsprozess erforderlich ist. 

ImageLoader.java

In Anbetracht dieser beiden Nachteile hat Volley einen noch besseren ImageLoader-Klasse bereitgestellt. Der wichtigste Punkt darin ist die Erhöhung des Speichercaches.
Bevor wir den Quellcode von ImageLoader erläutern, müssen wir zunächst die Verwendungsmethoden von ImageLoader vorstellen. Im Gegensatz zu den vorherigen Request-Anfragen ist ImageLoader nicht direkt new herausgegeben und an den RequestQueue übergeben, um abgerufen zu werden, seine Verwendungsmethoden sind im Allgemeinen in4Schritt:

 •Erstellen Sie ein RequestQueue-Objekt. 

RequestQueue queue = Volley.newRequestQueue(context);

 •Erstellen Sie ein ImageLoader-Objekt.

Der Konstruktor von ImageLoader akzeptiert zwei Parameter, den ersten ist ein RequestQueue-Objekt und der zweite ist ein ImageCache-Objekt (das ist auch ein Speichercache-Klasse, wir geben die spezifische Implementierung zunächst nicht heraus, nach der Erklärung des Quellcodes von ImageLoader werde ich eine Implementierung der ImageCache-Klasse mit dem LRU-Algorithmus bereitstellen) 

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

 •Erhalten Sie ein ImageListener-Objekt. 

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

•Laden Sie ein Netzwerkbild mit dem get-Methode von ImageLoader. 

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

Mit der Verwendung von ImageLoader, betrachten wir gemeinsam die Quellcode von ImageLoader, indem wir die Verwendungsmethoden kombinieren:

@SuppressWarnings({"unused", "StringBufferReplaceableByString"})
public class ImageLoader {
  /**
   * Verknüpft mit dem RequestQueue, um ImageLoader aufzurufen.
   */
  private final RequestQueue mRequestQueue;
  /** Implementierung der Schnittstelle für den Speicher der Bildspeicherkache. */
  private final ImageCache mCache;
  /** Speichert eine Sammlung von BatchedImageRequest, die gleichzeitig mit dem gleichen CacheKey ausgeführt werden. */
  private final HashMap<String, BatchedImageRequest> mInFlightRequests =
      new HashMap<String, BatchedImageRequest>();
  private final HashMap<String, BatchedImageRequest> mBatchedResponses =
      new HashMap<String, BatchedImageRequest>();
  /** Erhalten Sie den Handler des Hauptthreads. */
  private final Handler mHandler = new Handler(Looper.getMainLooper());
  private Runnable mRunnable;
  /** Definieren Sie das Bild K1Cache-Schnittstelle, bei der die Arbeit der Speicherkache für Bilder an den Benutzer übertragen wird. */
  public interface ImageCache {
    Bitmap getBitmap(String url);
    void putBitmap(String url, Bitmap bitmap);
  }
  /** Konstruieren eines ImageLoader. */
  public ImageLoader(RequestQueue queue, ImageCache imageCache) {
    mRequestQueue = queue;
    mCache = imageCache;
  }
  /** Konstruieren eines Callback-Interfaces für den Erfolg und den Fehler einer Netzwerkbildanfrage. */
  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 get(String requestUrl, ImageListener imageListener,
                int maxWidth, int maxHeight, ScaleType scaleType) {
    // Überprüft, ob die aktuelle Methode im UI-Thread ausgeführt wird. Wenn nicht, wird eine Ausnahme ausgelöst.
    throwIfNotOnMainThread();
    final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
    // Von L1Lädt das Bitmap aus dem Ebenen-Cache basierend auf dem Schlüssel.
    Bitmap cacheBitmap = mCache.getBitmap(cacheKey);
    if (cacheBitmap != null) {
      // L1Wenn der Cache erfolgreich getroffen wird, wird ImageContainer durch das Bitmap des Cache konstruiert und die erfolgreiche Antwort-Schnittstelle von imageListener aufgerufen.
      ImageContainer container = new ImageContainer(cacheBitmap, requestUrl, null, null);
      // Hinweis: Da sich derzeit der UI-Thread befindet, wird hier die onResponse-Methode aufgerufen, nicht der Callback.
      imageListener.onResponse(container, true);
      return container;
    }
    ImageContainer imageContainer =
        new ImageContainer(null, requestUrl, cacheKey, imageListener);
    // L1Falls der Cache nicht erfolgreich getroffen wird, muss zunächst das Standardbild für ImageView gesetzt werden. Anschließend wird das Netzwerkbild durch einen Unterthread abgerufen und angezeigt.
    imageListener.onResponse(imageContainer, true);
    // 检查cacheKey对应的ImageRequest请求是否正在运行.
    BatchedImageRequest request = mInFlightRequests.get(cacheKey);
    if (request != null) {
      // 相同的ImageRequest正在运行,不需要同时运行相同的ImageRequest.
      // 只需要将其对应的ImageContainer加入到BatchedImageRequest的mContainers集合中.
      // 当正在执行的ImageRequest结束后,会查看当前有多少正在阻塞的ImageRequest,
      // 然后对其mContainers集合进行回调.
      request.addContainer(imageContainer);
      return imageContainer;
    }
    // L1缓存未命中,还是需要构造ImageRequest,通过RequestQueue的调度来获取网络图片
    // 获取方法可能是:L2缓存(PS:Disk缓存)或者HTTP网络请求.
    Request<Bitmap> newRequest =
        makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
    mRequestQueue.add(newRequest);
    mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
    return imageContainer;
  }
  /** 构造L1缓存的key值. */
  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);
      }
    });
  }
  /** Callback für den Fehler einer Bildanfrage. Wird im UI-Thread ausgeführt. */
  private void onGetImageError(String cacheKey, VolleyError error) {
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
    if (request != null) {
      request.setError(error);
      batchResponse(cacheKey, request);
    }
  }
  /** Callback für den Erfolg einer Bildanfrage. Wird im UI-Thread ausgeführt. */
  protected void onGetImageSuccess(String cacheKey, Bitmap response) {
    // 增加L1缓存的键值对。
    mCache.putBitmap(cacheKey, response);
    // 在最初的ImageRequest执行成功后,回调这段时间阻塞的相同ImageRequest对应的成功回调接口。
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
    if (request != null) {
      request.mResponseBitmap = response;
      // 分发阻塞的ImageRequest的结果。
      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 muss aus dem Main-Thread aufgerufen werden.");
    }
  }
  /** Abstrahiert den Callback-Interface für Erfolg und Fehler. Standardmäßig kann der ImageListener von Volley verwendet werden. */
  public interface ImageListener extends Response.ErrorListener {
    void onResponse(ImageContainer response, boolean isImmediate);
  }
  /** Objekt zur Trägerschaft von Netzwerkbildanfragen. */
  public class ImageContainer {
    /** Bitmap, das der ImageView geladen werden soll. */
    private Bitmap mBitmap;
    /** L1Schlüssel für den Cache */
    private final String mCacheKey;
    /** URL der ImageRequest. */
    private final String mRequestUrl;
    /** Klasse für den Callback-Interface für den Erfolg oder die Fehlermeldung einer Bildanfrage. */
    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;
    }
  }
  /**
   * Abstrakter Request-Klasse mit gleicher CacheKey.
   * Die Feststellung, dass zwei ImageRequest gleich sind, beinhaltet:
   * 1. Gleiches URL.
   * 2. Gleiches maxWidth und maxHeight.
   * 3. Gleiches scaleType angezeigt.
   * Gleichzeitig können mehrere ImageRequest-Anfragen mit gleicher CacheKey vorliegen, da alle zurückgegebenen Bitmaps gleich sind, wird BatchedImageRequest verwendet
   * um diese Funktion zu realisieren. Gleichzeitig kann nur eine ImageRequest mit gleicher CacheKey existieren.
   * Warum wird nicht die mWaitingRequestQueue von RequestQueue verwendet, um diese Funktion zu realisieren?
   * Antwort: Es ist nicht möglich, zwei ImageRequest miteinander zu vergleichen, nur durch die URL.
   */
  private class BatchedImageRequest {
    /** entsprechende ImageRequest-Anfrage. */
    private final Request<?> mRequest;
    /** Bitmap-Objekt des Anfrageergebnisses. */
    private Bitmap mResponseBitmap;
    /** Fehler bei ImageRequest. */
    private VolleyError mError;
    /** Sammlung aller Encapsulierung von ImageRequest-Anfragen mit gleichen Ergebnissen. */
    private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>();
    public BatchedImageRequest(Request<63;> 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;
    }
  }
}

große Fragen

Ich habe zwei große Fragen an den Quellcode von Imageloader.63;

 •Implementierung der batchResponse-Methode. 

Ich verstehe nicht, warum die Klasse ImageLoader eine HashMap hat, um die Sammlung von BatchedImageRequest zu speichern?&63;

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

Schließlich wird batchResponse aufgerufen, wenn ein bestimmter ImageRequest erfolgreich ausgeführt wird, der Aufrufcode lautet wie folgt:

  protected void onGetImageSuccess(String cacheKey, Bitmap response) {
    // 增加L1缓存的键值对。
    mCache.putBitmap(cacheKey, response);
    // 在最初的ImageRequest执行成功后,回调这段时间阻塞的相同ImageRequest对应的成功回调接口。
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
    if (request != null) {
      request.mResponseBitmap = response;
      // 分发阻塞的ImageRequest的结果。
      batchResponse(cacheKey, request);
    }
  }

从上述代码中可以看出,ImageRequest请求成功后,已经从mInFlightRequests中获取了对应的BatchedImageRequest对象。而同一时间被阻塞的相同的ImageRequest对应的ImageContainer都在BatchedImageRequest的mContainers集合中。
我认为,batchResponse方法只需要遍历对应BatchedImageRequest的mContainers集合即可。
然而,在ImageLoader的源代码中,我认为多创建了一个HashMap对象mBatchedResponses来保存BatchedImageRequest集合,然后在batchResponse方法中又对集合进行了两层for循环遍历,这实在是非常诡异,希望得到指导。
以下代码非常诡异:

  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 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);
  }
}

Dies ist der gesamte Inhalt dieses Artikels. Wir hoffen, dass er Ihnen bei Ihrem Lernen hilft und dass Sie die呐喊教程 unterstützen.

Erklärung: Der Inhalt dieses Artikels wurde aus dem Internet übernommen und gehört dem Urheberrechtlichem Eigentümer. Der Inhalt wurde von Internetbenutzern selbstständig beigesteuert und hochgeladen. Diese Website besitzt keine Eigentumsrechte und hat den Inhalt nicht manuell bearbeitet. Sie übernimmt auch keine rechtlichen Verantwortlichkeiten. Wenn Sie verdächtige Urheberrechtsinhalte entdecken, freuen wir uns über eine E-Mail an: notice#oldtoolbag.com (Bitte ersetzen Sie # durch @, wenn Sie eine Beschwerde einreichen, und fügen Sie relevante Beweise bei. Sobald nachgewiesen, wird diese Website die beanstandeten Inhalte sofort löschen.)

You May Also Like