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

شرح تفصيلي لوظيفة تحميل الصور في Volley لـ Android

مشروع GitHub

مشروع التعليقات باللغة الصينية لشيفرة 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;
  /** قفل تحليل Bitmap، يضمن أن يتم تحميل Bitmap واحد فقط إلى ذاكرة الوصول العشوائي في نفس الوقت، مما يمنع OOM. */
  private static final Object sDecodeLock = new Object();
  /**
   * بناء طلب صورة شبكية.
   * @param url الصورة URL.
   * @param maxWidth 图片的最大宽度.
   * @param maxHeight 图片的最大高度.
   * @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,
            }
            }
    }
    mListener = listener;
    }
    }
    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 {
      // حصول على حجم الحقيقي للصورة من الإنترنت.
      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;
      {}
    {}
    إذا (bitmap == null) {
      إرجاع Response.error(new VolleyError(response));
    } else {
      إرجاع Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
    {}
  {}
  static int findBestSampleSize(
      int actualWidth, int actualHeight, int desiredWidth, int desireHeight) {
    double wr = (نسبة) عرض فعلية / عرض الرغبة;
    double hr = (نسبة) طول فعلية / طول الرغبة;
    double ratio = Math.min(wr, hr);
    float n = 1.0f;
    while ((n * 2) <= نسبة) {
      n *= 2;
    {}
    إرجاع (نسبة) n;
  {}
  /** تحديد حجم الصورة بناءً على نوع مقياس ImageView. */
  private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                      int actualSecondary, ImageView.ScaleType scaleType) {
    // إذا لم يتم ضبط أكبر قيمة ImageView، فإنه يتم إرجاع حجم صورة الشبكة الفعلية.
    إذا ((أكبر رئيسية == 0) && (أكبر ثانوية == 0)) {
      إرجاع رئيسية فعلية;
    {}
    // إذا كان نوع مقياس ImageView هو FIX_XY، فإنه يتم ضبطه إلى أكبر قيمة للصورة.
    إذا (نوع المقياس == ImageView.ScaleType.FIT_XY) {
      إذا (أكبر رئيسية == 0) {
        إرجاع رئيسية فعلية;
      {}
      return maxPrimary;
    {}
    إذا (أكبر رئيسية == 0) {
      نسبة مضاعفة = (نسبة مضاعفة)أكبر ثانوية / (نسبة مضاعفة ثانوية فعلية;
      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);
  {}
{}

بما أن Volley قد قام بالفعل بتحقيق حماية التخزين المحلي لطلبات الشبكة، فإن مهمة ImageRequest تتكون بشكل رئيسي من تحويل تدفق البايتات إلى Bitmap، وتأمين تحويل Bitmap باستخدام متغيرات ثابتة لضمان تحويل Bitmap واحد فقط في كل مرة لتجنب OOM، وتعيين حجم الصورة باستخدام ScaleType وحدود MaxWidth وMaxHeight التي يحددها المستخدم.
بشكل عام، إن تنفيذ ImageRequest بسيط جدًا، ولا نريد التطرق إليه بشكل مفصل.تعيوب ImageRequest تكمن في:

1. يتطلب من المستخدم إجراء إعدادات إضافية، بما في ذلك الحد الأقصى لقياس الصورة.
2. لا توجد حافظة ذاكرة للصور، لأن حافظة Volley تعتمد على حافظة Disk وتحتاج إلى عملية تفاعلية. 

ImageLoader.java

نظراً لتلك العيوب السابقة، قدم Volley أيضًا ImageLoader 类 الأكثر روعة. وأهم ما في ذلك هو إضافة حافظة ذاكرة.
قبل شرح شيفرة ImageLoader، يجب تقديم شرح حول استخدام ImageLoader. ويختلف استخدام ImageLoader عن طلبات Request السابقة، حيث أن ImageLoader ليس يتم إنشاؤه مباشرة وإلقاءه على RequestQueue للإدارة، ويمكن تقسيم طريقة استخدامه إلى 4 خطوات رئيسية:

 •إنشاء جسم الطلب RequestQueue. 

RequestQueue queue = Volley.newRequestQueue(context);

 • إنشاء كائن ImageLoader.

يستقبل بناء ImageLoader إثنين من المعلمات، المعلمة الأولى هي كائن RequestQueue، والمعلمة الثانية هي كائن ImageCache (وهي كلاس التخزين المؤقت في ذاكرة الوصول العشوائي، لنقدم تنفيذًا دقيقًا الآن، بعد أن نشرح كود ImageLoader المصدر، سأقدم تنفيذًا باستخدام خوارزمية LRU) 

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

 • الحصول على كائن ImageListener. 

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

• استدعاء طريقة get في ImageLoader لتحميل الصورة من الإنترنت. 

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

استخدام طريقة ImageLoader، لنلقي نظرة على كود ImageLoader المصدر:

@SuppressWarnings({"unused", "StringBufferReplaceableByString"})
public class ImageLoader {
  /**
   * مرتبط بـ RequestQueue لاستدعاء ImageLoader.
   */
  private final RequestQueue mRequestQueue;
  /** صورة واجهة تنفيذ التخزين المؤقت في ذاكرة الوصول العشوائي. */
  private final ImageCache mCache;
  /** 存رعنوانة مجموعة BatchedImageRequest التي تنفذ في نفس الوقت بنفس مفتاح CacheKey. */
  private final HashMap<String, BatchedImageRequest> mInFlightRequests =
      new HashMap<String, BatchedImageRequest>();
  private final HashMap<String, BatchedImageRequest> mBatchedResponses =
      new HashMap<String, BatchedImageRequest>();
  /** دریافت Handler خطوط اصلی. */
  private final Handler mHandler = new Handler(Looper.getMainLooper());
  private Runnable mRunnable;
  /** تعریف رابطه‌ی کاوشگر تصویر K1، که کار ذخیره‌سازی تصویر در حافظه‌ی موقت به کاربر واگذار می‌شود. */
  public interface ImageCache {
    Bitmap getBitmap(String url);
    void putBitmap(String url, Bitmap bitmap);
  {}
  /** قالب‌بندی‌کننده‌ی ImageLoader. */
  public ImageLoader(RequestQueue queue, ImageCache imageCache) {
    mRequestQueue = queue;
    mCache = imageCache;
  {}
  /** قالب‌بندی‌کننده‌ی فراخوانی‌های موفق و ناموفق درخواست تصویر شبکه. */
  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) {
    // قم بتحديد ما إذا كانت هذه الطريقة تُنفذ في نواة UI. إذا لم تكن كذلك، قم ب رمي استثناء.
    throwIfNotOnMainThread();}}
    final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
    // قم بجلب Bitmap المترتب على key من L1 Cache.
    Bitmap cacheBitmap = mCache.getBitmap(cacheKey);
    if (cacheBitmap != null) {
      // إذا تم تحقيق نجاح في تحقيق L1 Cache، قم ببناء ImageContainer باستخدام Bitmap الذي تم تحقيقه من خلال L1 Cache، ثم قم بتقديم استجابة نجاح من خلال واجهة الاستجابة imageListener.
      ImageContainer container = new ImageContainer(cacheBitmap, requestUrl, null, null);
      // ملاحظة: لأننا الآن في نواة UI، هنا نستدعي طريقة onResponse وليس الدالة الإرتباطية.
      imageListener.onResponse(container, true);
      return container;
    {}
    ImageContainer imageContainer =
        new ImageContainer(null, requestUrl, cacheKey, imageListener);
    // إذا لم يتم تحقيق نجاح في تحقيق L1 Cache، قم أولاً بتعيين صورة افتراضية لـ ImageView. ثم قم بسحب صورة من الإنترنت في نواة فرعية للعرض.
    imageListener.onResponse(imageContainer, true);
    // قم بفحص ما إذا كان طلب ImageRequest المترتب على cacheKey قيد التشغيل.
    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: التخزين على القرص) أو طلب HTTP عبر الشبكة.
    Request<Bitmap> newRequest =
        makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
    mRequestQueue.add(newRequest);
    mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
    return imageContainer;
  {}
  /** بناء مفتاح التخزين 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);
      {}
    });
  {}
  /** استدعاء فشل طلب الصورة. تشغيل في نواة واجهة المستخدم. */
  private void onGetImageError(String cacheKey, VolleyError error) {
    BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
    if (request != null) {
      request.setError(error);
      batchResponse(cacheKey, request);
    {}
  {}
  /** استدعاء نجاح طلب الصورة. تشغيل في نواة واجهة المستخدم. */
  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() {
    إذا (Looper.myLooper() != Looper.getMainLooper()) {
      throw new IllegalStateException("يجب استدعاء ImageLoader من النواة الرئيسية.");
    {}
  {}
  /** يُستدعى واجهة التبليغ النموذجية بنجاح وفشل الطلب. يمكن استخدام ImageListener المقدم من Volley بشكل افتراضي. */
  public interface ImageListener extends Response.ErrorListener {
    void onResponse(ImageContainer response, boolean isImmediate);
  {}
  /** وصف موضوع طلب الصورة عبر الإنترنت. */
  public class ImageContainer {
    /** وصف Bitmap الذي يحتاجه ImageView للتحميل. */
    private Bitmap mBitmap;
    /** مفتاح L1 المخزن. */
    private final String mCacheKey;
    /** وصف URL طلب ImageRequest. */
    private final String mRequestUrl;
    /** واجهة استدعاء التبليغ بنجاح أو فشل طلب الصورة. */
    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() {
      إذا (mListener == null) {
        return;
      {}
      BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
      if (request != null) {
        boolean المُلغى = request.removeContainerAndCancelIfNecessary(this);
        إذا (المُلغى) {
          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;
    {}
  {}
  /**
   * فئة ImageRequest المماثلة للطلبCacheKey.
   * يمكن تحديد ما إذا كانت RequestImage متطابقة بما يتعلق بما يلي:
   * 1. URL المماثل.
   * 2. maxWidth و maxHeight المماثلين.
   * 3. نوع scaleType المماثل.
   * يمكن أن يكون هناك عدة طلبات ImageRequest مماثلة بنفس CacheKey في نفس الوقت، بسبب أن جميع Bitmaps العائدة يجب أن تكون متطابقة، يتم استخدام BatchedImageRequest
   * لتحقيق هذه الوظيفة. يمكن أن يكون هناك ImageRequest مماثل واحد فقط بنفس CacheKey في نفس الوقت.
   * لماذا لا نستخدم mWaitingRequestQueue من RequestQueue لتحقيق هذه الوظيفة?
   * جواب: لأنه ليس من الممكن تحديد ما إذا كانت RequestImage متطابقة فقط بناءً على URL.
   */
  private class BatchedImageRequest {
    /** الطلب ImageRequest المماثل. */
    private final Request<?> mRequest;
    /** شكل Bitmap لنتيجة الطلب. */
    private Bitmap mResponseBitmap;
    /** خطأ ImageRequest. */
    private VolleyError mError;
    /** جميع مجموعات التعبئة لنتائج ImageRequest المماثلة. */
    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;
    {}
  {}
{}

استفسارات كبيرة

لدي عدة استفسارات كبيرة حول مصدر Imageloader؟

 • تنفيذ batchResponse. 

أنا مستغرب لماذا يجب أن يحتوي كلاس ImageLoader على HashMap لحفظ مجموعة BatchedImageRequest؟

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

باختصار، batchResponse يتم استدعاؤه في استدعاء استجابة نجاح ImageRequest المحدد، والكود الاستدعاء كالتالي:

  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، تم الحصول على كائن BatchedImageRequest المطلوب من mInFlightRequests. وفي نفس الوقت، كل ImageContainer التي تم إيقافها بناءً على نفس ImageRequest تتواجد في مجموعه mContainers الخاصة بـ BatchedImageRequest.
أعتقد أن طريقة batchResponse يجب أن تكون فقط لتكرار مجموعه mContainers الخاصة بـ BatchedImageRequest.
لكن، في مصدر ImageLoader، أعتقد أن هناك بناء غير ضروري لجسم HashMap mBatchedResponses لتح Konsol 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);
  {}
{}

هذا هو نهاية محتوى هذا المقال، نأمل أن يكون قد ساعدكم في التعلم، ونأمل أن تحصلوا على الدعم الكبير لتعليم呐喊.

البيان: محتويات هذا المقال تم جمعها من الإنترنت، حقوق النشر تخص المالك الأصلي، المحتوى تم إدراجه من قبل المستخدمين عبر الإنترنت بطرقهم الخاصة، هذا الموقع لا يملك حقوق الملكية، لم يتم تعديل المحتوى بشكل إنساني، ولا يتحمل أي مسؤولية قانونية متعلقة بذلك. إذا كنت قد وجدت محتوى يشتبه في انتهاك حقوق النسخ، فالرجاء إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (عند إرسال البريد الإلكتروني، يرجى استبدال #بـ @) لإبلاغنا، وقدم الدليل على ذلك، إذا تم التحقق من صحة المعلومات، سيتم حذف المحتوى المزعوم فوراً.

سيكون لك هذا ما تحب