Android Volley图片加载功能详解

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;

  /** Bitmap解析同步锁,保证同一时间只有一个Bitmap被load到内存进行解析,防止OOM. */
  private static final Object sDecodeLock = new Object();

  /**
   * 构造一个网络图片请求.
   * @param url 图片的url地址.
   * @param listener 请求成功用户设置的回调接口.
   * @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,
            Response.ErrorListener errorListener) {
    super(Method.GET, url, errorListener);
    mListener = listener;
    mDecodeConfig = decodeConfig;
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;
    mScaleType = scaleType;
  }

  /** 设置网络图片请求的优先级. */
  @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 {
      // 获取网络图片的真实尺寸.
      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;
  }

  /** 根据ImageView的ScaleType设置图片的大小. */
  private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
                      int actualSecondary, ImageView.ScaleType scaleType) {
    // 如果没有设置ImageView的最大值,则直接返回网络图片的真实大小.
    if ((maxPrimary == 0) && (maxSecondary == 0)) {
      return actualPrimary;
    }

    // 如果ImageView的ScaleType为FIX_XY,则将其设置为图片最值.
    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);
  }
}

因为Volley本身框架已经实现了对网络请求的本地缓存,所以ImageRequest做的主要事情就是解析字节流为Bitmap,再解析过程中,通过静态变量保证每次只解析一个Bitmap防止OOM,使用ScaleType和用户设置的MaxWidth和MaxHeight来设置图片大小.
总体来说,ImageRequest的实现非常简单,这里不做过多的讲解.ImageRequest的缺陷在于:

1.需要用户进行过多的设置,包括图片的大小的最大值.
2.没有图片的内存缓存,因为Volley的缓存是基于Disk的缓存,有对象反序列化的过程.

ImageLoader.java

鉴于以上两个缺点,Volley又提供了一个更牛逼的ImageLoader类.其中,最关键的就是增加了内存缓存.
再讲解ImageLoader的源码之前,需要先介绍一下ImageLoader的使用方法.和之前的Request请求不同,ImageLoader并不是new出来直接扔给RequestQueue进行调度,它的使用方法大体分为4步:

 •创建一个RequestQueue对象.

RequestQueue queue = Volley.newRequestQueue(context);

 •创建一个ImageLoader对象.

ImageLoader构造函数接收两个参数,第一个是RequestQueue对象,第二个是ImageCache对象(也就是内存缓存类,我们先不给出具体实现,讲解完ImageLoader源码之后,我会提供一个利用LRU算法的ImageCache实现类)

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

•调用ImageLoader的get方法加载网络图片.

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

有了ImageLoader的使用方法,我们结合使用方法来看一下ImageLoader的源码:

@SuppressWarnings({"unused", "StringBufferReplaceableByString"})
public class ImageLoader {
  /**
   * 关联用来调用ImageLoader的RequestQueue.
   */
  private final RequestQueue mRequestQueue;

  /** 图片内存缓存接口实现类. */
  private final ImageCache mCache;

  /** 存储同一时间执行的相同CacheKey的BatchedImageRequest集合. */
  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);

    // 从L1级缓存中根据key获取对应的Bitmap.
    Bitmap cacheBitmap = mCache.getBitmap(cacheKey);
    if (cacheBitmap != null) {
      // L1缓存命中,通过缓存命中的Bitmap构造ImageContainer,并调用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缓存命中失败,则先需要对ImageView设置默认图片.然后通过子线程拉取网络图片,进行显示.
    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);
      }
    });
  }

  /** 图片请求失败回调.运行在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) {
    // 增加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 must be invoked from the main thread.");
    }
  }

  /** 抽象出请求成功和失败的回调接口.默认可以使用Volley提供的ImageListener. */
  public interface ImageListener extends Response.ErrorListener {
    void onResponse(ImageContainer response, boolean isImmediate);
  }

  /** 网络图片请求的承载对象. */
  public class ImageContainer {
    /** ImageView需要加载的Bitmap. */
    private Bitmap mBitmap;

    /** L1缓存的key */
    private final String mCacheKey;

    /** ImageRequest请求的url. */
    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() {
      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相同的ImageRequest请求抽象类.
   * 判定两个ImageRequest相同包括:
   * 1. url相同.
   * 2. maxWidth和maxHeight相同.
   * 3. 显示的scaleType相同.
   * 同一时间可能有多个相同CacheKey的ImageRequest请求,由于需要返回的Bitmap都一样,所以用BatchedImageRequest
   * 来实现该功能.同一时间相同CacheKey的ImageRequest只能有一个.
   * 为什么不使用RequestQueue的mWaitingRequestQueue来实现该功能?
   * 答:是因为仅靠URL是没法判断两个ImageRequest相等的.
   */
  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请求成功后,已经从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);
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 深入剖析Android的Volley库中的图片加载功能

    一.基本使用要点回顾 Volley框架在请求网络图片方面也做了很多工作,提供了好几种方法.本文介绍使用ImageLoader来进行网络图片的加载. ImageLoader的内部使用ImageRequest来实现,它的构造器可以传入一个ImageCache缓存形参,实现了图片缓存的功能,同时还可以过滤重复链接,避免重复发送请求. 下面是ImageLoader加载图片的实现方法: public void displayImg(View view){ ImageView imageView = (Im

  • Android Volley框架全面解析

     Volley简介 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高. 不过HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码.于是乎,一些Android网络

  • 深入解读Android的Volley库的功能结构

    Volley 是一个 HTTP 库,它能够帮助 Android app 更方便地执行网络操作,最重要的是,它更快速高效.我们可以通过开源的 AOSP 仓库获取到 Volley . Volley 有如下的优点: 自动调度网络请求. 高并发网络连接. 通过标准的 HTTP cache coherence(高速缓存一致性)缓存磁盘和内存透明的响应. 支持指定请求的优先级. 撤销请求 API.我们可以取消单个请求,或者指定取消请求队列中的一个区域. 框架容易被定制,例如,定制重试或者回退功能. 强大的指

  • Android 开发中Volley详解及实例

    Android 开发中Volley详解及实例 最近在做项目的时候,各种get和post.简直要疯了,我这种啥都不了解的,不知道咋办了,然后百度看了下,可以用volley进行网络请求与获取,下面就介绍下volley的用法. volley有三种方式:JsonObjectRequest,JsonArrayRequest,StringRequest.其实都是差不多了,举一反三就ok了,这里我就讲下JsonObjectRequest. 方法如下: JsonObjectRequest jsonObjectR

  • Android的HTTP类库Volley入门学习教程

    1. 什么是Volley 我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据.Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高. 不过HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码.于是乎,一些Androi

  • Android的HTTP操作库Volley的基本使用教程

    以前原本都用android内建的Library来进行GET.POST等等对API的连线与操作. 但最近想说来找找看有没有好用的library,应该可以事半功倍. 当初有找了三套比较多人用的 1.Android Asynchronous Http Client 2.okhttp square开发并且开源的,因为之前用过他们家的picasso,所以对这套满有好感的,只可惜使用方式不太喜欢 3.Volley Volley是Google在2013年Google I/O的时候发布的,到现在已经积累了很高的

  • android 网络请求库volley方法详解

    使用volley进行网络请求:需先将volley包导入androidstudio中 File下的Project Structrue,点加号导包 volley网络请求步骤: 1. 创建请求队列       RequestQueue queue = Volley.newRequestQueue(this); 2.创建请求对象(3种) StringRequest request = new StringRequest("请求方法","请求的网络地址","成功的网

  • Android开发中使用Volley库发送HTTP请求的实例教程

    Android Volley 是Google开发的一个网络lib,可以让你更加简单并且快速的访问网络数据.Volley库的网络请求都是异步的,你不必担心异步处理问题. Volley的优点: 请求队列和请求优先级 请求Cache和内存管理 扩展性性强 可以取消请求 下载和编译volley.jar 需要安装git,ant,android sdk clone代码: git clone https://android.googlesource.com/platform/frameworks/volley

  • Android Volley框架使用源码分享

    过去在Android上网络通信都是使用的Xutils 因为用它可以顺道处理了图片和网络这两个方面,后来发觉Xutils里面使用的是HttpClient  而Google在6.0的版本上已经把HttpClient废除了,所以开始寻找新的网络框架,okhttp也用过,但是它是在作用在UI线程,使用起来还需要用handler 所以就先用着Volley框架了.  这里我先分析下Volley框架的简单网络请求的源码. 使用Volley请求网络数据的简单过程: RequestQueue queue = Vo

  • Android Volley图片加载功能详解

    Gituhb项目 Volley源码中文注释项目我已经上传到github,欢迎大家fork和start. 为什么写这篇博客 本来文章是维护在github上的,但是我在分析ImageLoader源码过程中与到了一个问题,希望大家能帮助解答. Volley获取网络图片  本来想分析Universal Image Loader的源码,但是发现Volley已经实现了网络图片的加载功能.其实,网络图片的加载也是分几个步骤: 1. 获取网络图片的url. 2. 判断该url对应的图片是否有本地缓存. 3. 有

  • ios开发UITableViewCell图片加载优化详解

    目录 前言 图片自适应比例 XHWebImageAutoSize 仅加载当前屏幕的内容 预加载 前言 我们平时用UITableView用的很多,所以对列表的优化也是很关注的.很多时候,我们设置UIImageView,都是比例固定好宽高的,然后通过 scaleAspectFill 和 clipsToBounds 保持图片不变形,这样子做开发的效率是很高的,毕竟图片宽高我们都是固定好的了. 那如果产品要求图片按真正的比例展示出来呢?如果服务器有返回宽和高,那就好办了,那如果没有呢,我们应该怎么去做呢

  • flutter中的资源和图片加载示例详解

    目录 封面图 指定相应的资源 资源绑定 Asset bundling 资源变体 加载资源 加载文本资源 加载图片 加载依赖包中的图片 最后 封面图 下个季度的目标是把前端监控相关的内容梳理出来,梳理出来之后可能会在公司内部做个分享- Flutter应用程序既括代码也包括一些其他的资产,我们通常这些资产为资源. 有时候我会思考assets这个单词,在程序中到底应该翻译为资产呢?还是翻译为资源?按照习惯,我们这里还是称为资源好了- 这些资源是一些与应用程序捆绑在一起和并且部署应用时会用到的的文件,在

  • 微信小程序实现页面下拉刷新和上拉加载功能详解

    本文实例讲述了微信小程序实现页面下拉刷新和上拉加载功能.分享给大家供大家参考,具体如下: web手机端或App中经常会有下拉刷新,上拉加载这些功能. 微信小程序中如何实现下拉刷新,上拉加载的功能. 实现思路: 1.监听界面的下拉刷新事件和上拉加载事件 bindscrolltolower 监听上拉加载 bindscrolltoupper 监听下拉刷新 2.下拉刷新时清空数据列表,并重新请求数据进行界面展示. 3.上拉加载增量请求数据,增量增加数据列表,增量界面展示 效果图: 实现代码: Water

  • Android通用LoadingView加载框架详解

    手写一个通用加载中.显示数据.加载失败.空数据的LoadingView框架. 定义3个布局:加载中,加载失败,空数据 加载中: <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent&q

  • 详解Android GLide图片加载常用几种方法

    目录 缓存浅析 GLide图片加载方法 图片加载周期 图片格式(Bitmap,Gif) 缓存 集成网络框架 权限 占位符 淡入效果 变换 启动页/广告页 banner 固定宽高 圆角 圆形 总结 缓存浅析 为啥要做缓存? android默认给每个应用只分配16M的内存,所以如果加载过多的图片,为了 防止内存溢出 ,应该将图片缓存起来. 图片的三级缓存分别是: 1.内存缓存 2.本地缓存 3.网络缓存 其中,内存缓存应优先加载,它速度最快:本地缓存次优先加载,它速度也快:网络缓存不应该优先加载,它

  • Android LayoutInflater加载布局详解及实例代码

    Android  LayoutInflater加载布局详解 对于有一定Android开发经验的同学来说,一定使用过LayoutInflater.inflater()来加载布局文件,但并不一定去深究过它的原理,比如 1.LayoutInflater为什么可以加载layout文件? 2.加载layout文件之后,又是怎么变成供我们使用的View的? 3.我们定义View的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢? 既然在这篇文章提出来,那说明这三

  • Android开发之自定义加载动画详解

    目录 一.demo简介 二.分析贪吃动画的尺寸比例 三.画圆 四.实现张嘴闭嘴动画 五.小球移动动画 一.demo简介 1.效果展示如下图,我截了三个瞬间,但其实这是一个连续的动画,就是这个大圆不停地吞下小圆. 2.这个动画可以拆分为两部分,首先是大圆张嘴闭嘴的动画,相当于画一个圆弧,规定一下它的角度就好.小圆就是一个从右向左移动的动画.然后不停地刷新界面,让动画的持续时间为永恒,这样就会有一个持续的动态效果. 二.分析贪吃动画的尺寸比例 1.在制作动画之前,我们要先建一个模型,来确定一下大圆和

  • Android  LayoutInflater加载布局详解及实例代码

    Android  LayoutInflater加载布局详解 对于有一定Android开发经验的同学来说,一定使用过LayoutInflater.inflater()来加载布局文件,但并不一定去深究过它的原理,比如 1.LayoutInflater为什么可以加载layout文件? 2.加载layout文件之后,又是怎么变成供我们使用的View的? 3.我们定义View的时候,如果需要在布局中使用,则必须实现带AttributeSet参数的构造方法,这又是为什么呢? 既然在这篇文章提出来,那说明这三

  • Vue openLayers实现图层数据切换与加载流程详解

    目录 openlayers介绍 一.实现效果预览 二.代码实现 openlayers介绍 OpenLayers是一个用于开发WebGIS客户端的JavaScript包.OpenLayers 支持的地图来源包括Google Maps.Yahoo. Map.微软Virtual Earth 等,用户还可以用简单的图片地图作为背景图,与其他的图层在OpenLayers 中进行叠加,在这一方面OpenLayers提供了非常多的选择.OpenLayers采用面向对象方式开发. OpenLayers 是一个专

随机推荐