这个是我写的比较满意的,禁止一切转载,需要请联系博主。
大家都知道Volley是谷歌官方推出来的网络库,使用他进行数据交互,图片加载都非常方便,但是他不支持下载文件,网上很多人都说,他下载文件的性能非常不好,所以就没有提供下载的功能,这个是前提。
在安卓上面,默认是不支持显示GIF图片的,如果你用ImageView,也只会显示GIF图的第一帧,这个我们可以用的开源的GifImageView 控件,github地址如下:
在android studio中,可以直接添加依赖:
compile ‘pl.droidsonroids.gif:android-gif-drawable:1.1.+’
然后就可以使用他显示GIF图片了,具体使用方式如下:
1、布局文件声明;
<pl.droidsonroids.gif.GifImageView
android:id="@+id/gif_1"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
2、在代码中实现:
gifImageView=getView(R.id.gif_1);
gifImageView.setImageResource( R.drawable.gif);
具体gif的使用,可以参见我看到的另一篇博客,当然,这个是我在网上看见的,我也不认识博主的:
GifView——Android显示GIF动画
然后来讲我们的重头戏,也就是对volley的扩展,其实我当时的目的是用volley来接在gif,所以,主要是围绕gif写的,但是为了以后方便,所以 一起随便考虑了对文件下载的支持。我觉得用Volley来下载 一点碎文件还是很不错的,毕竟,图片也就是一下碎文件而已。
我们的核心需求也是加载图片,所以,我是直接去看了Volley原来的关于图片加载的部分,也就是:ImageLoader和ImageRequest类,如果不知道Volley图片加载是怎麽用的,建议你先去看一下,Volley原生的是怎麽加载图片的。
然后我在ImageLoader和ImageRequest类的基础上修改了一下,得到了如下两个类:
改成了FileLoader,内容如下:
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.VolleyError;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import pl.droidsonroids.gif.GifDrawable;
import pl.droidsonroids.gif.GifImageView;
/**
* Helper that handles loading and caching images from remote URLs.
*
* The simple way to use this class is to call {@link FileLoader#get(String requestUrl, FileListener fileListener, boolean mastRunMainThread)}
* and to pass in the default image listener provided by
* {@link FileLoader#getFileListener()}. Note that all function calls to
* this class must be made from the main thead, and all responses will be delivered to the main
* thread as well.
*/
public class FileLoader {
/** RequestQueue for dispatching ImageRequests onto. */
private final RequestQueue mRequestQueue;
/** Amount of time to wait after first response arrives before delivering all responses. */
private int mBatchResponseDelayMs = 100;
/** The cache implementation to be used as an L1 cache before calling into volley. */
private final FileCache mCache;
/**
* HashMap of Cache keys -> BatchedFileRequest used to track in-flight requests so
* that we can coalesce multiple requests to the same URL into a single network request.
*/
private final HashMap<String, BatchedFileRequest> mInFlightRequests =
new HashMap<String, BatchedFileRequest>();
/** HashMap of the currently pending responses (waiting to be delivered). */
private final HashMap<String, BatchedFileRequest> mBatchedResponses =
new HashMap<String, BatchedFileRequest>();
/** Handler to the main thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper());
/** Runnable for in-flight response delivery. */
private Runnable mRunnable;
/**callback Listener*/
private FileListener mFileListener;
/**if need control UI,this is must be true*/
private boolean mMastRunMainThread;
/**use cache if this is true*/
private boolean useCache=true;
/**
* Simple cache adapter interface. If provided to the FileLoader, it
* will be used as an L1 cache before dispatch to Volley. Implementations
* must not block. Implementation with an LruCache is recommended.
*/
public interface FileCache {
public byte[] getData(String url);
public void putData(String url, byte[] data);
}
/**
* Constructs a new FileLoader.
* @param queue The RequestQueue to use for making file requests.
* @param fileCache The cache to use as an L1 cache.
*/
public FileLoader(RequestQueue queue, FileCache fileCache) {
mRequestQueue = queue;
mCache = fileCache;
}
/**
*Constructs a new FileLoader.
* @param queue The RequestQueue to use for making file requests.
* @param fileCache The cache to use as an L1 cache.
* @param mastRunMainThread if need control UI,this is must be true
*/
public FileLoader(RequestQueue queue, FileCache fileCache, boolean mastRunMainThread) {
this(queue, fileCache);
this.mMastRunMainThread = mastRunMainThread;
}
public FileListener getFileListener() {
return mFileListener;
}
public void setFileListener(FileListener mFileListener) {
this.mFileListener = mFileListener;
}
public boolean ismMastRunMainThread() {
return mMastRunMainThread;
}
public void setmMastRunMainThread(boolean mMastRunMainThread) {
this.mMastRunMainThread = mMastRunMainThread;
}
public boolean isUseCache() {
return useCache;
}
public void setUseCache(boolean useCache) {
this.useCache = useCache;
}
/**
* Interface for the response handlers on image requests.
*
* The call flow is this:
* 1. Upon being attached to a request, onResponse(response, true) will
* be invoked to reflect any cached data that was already available. If the
* data was available, response.getBitmap() will be non-null.
*
* 2. After a network response returns, only one of the following cases will happen:
* - onResponse(response, false) will be called if the image was loaded.
* or
* - onErrorResponse will be called if there was an error loading the image.
*/
public interface FileListener extends ErrorListener {
/**
* Listens for non-error changes to the loading of the image request.
*
* @param fileContainer Holds all information pertaining to the request, as well
* as the bitmap (if it is loaded).
* @param isImmediate True if this was called during FileLoader.get() variants.
* This can be used to differentiate between a cached image loading and a network
* loading in order to, for example, run an animation to fade in network loaded
* images.
*/
public void onResponse(FileContainer fileContainer, boolean isImmediate);
}
/**这里是对gif图片设置的封装,可以设置默认图片,加载出错图片*/
public class GIFUtil{
private GifImageView gifImageView;
private int defaultImg;
private int errorImg;
/**
* 设置GIF图片的默认构造工具。
* @param gifImageView 要现实图片的控件
* @param defaultImg 默认图片资源
* @param errorImg 加载出错的图片资源
*/
public GIFUtil(GifImageView gifImageView,int defaultImg, int errorImg) {
this.defaultImg = defaultImg;
this.errorImg = errorImg;
this.gifImageView = gifImageView;
}
public int getDefaultImg() {
return defaultImg;
}
public void setDefaultImg(int defaultImg) {
this.defaultImg = defaultImg;
}
public int getErrorImg() {
return errorImg;
}
public void setErrorImg(int errorImg) {
this.errorImg = errorImg;
}
public GifImageView getGifImageView() {
return gifImageView;
}
public void setGifImageView(GifImageView gifImageView) {
this.gifImageView = gifImageView;
}
}
/**
* Checks if the item is available in the cache.
*
* @param requestUrl The url of the remote image
* @return True if the item exists in cache, false otherwise.
*/
public boolean isCached(String requestUrl) {
if(mMastRunMainThread){
throwIfNotOnMainThread();
}
String cacheKey = getCacheKey(requestUrl);
return mCache.getData(cacheKey) != null;
}
/**
* Issues a bitmap request with the given URL if that image is not available
* in the cache, and returns a bitmap container that contains all of the data
* relating to the request (as well as the default image if the requested
* image is not available).
* @param requestUrl The url of the remote image
* @param fileListener The listener to call when the remote image is loaded
* @param mastRunMainThread need run in main thread
* @return A container object that contains all of the properties of the request, as well as
* the currently available image (default if remote is not loaded).
*/
public FileContainer get(String requestUrl, FileListener fileListener, boolean mastRunMainThread) {
// only fulfill requests that were initiated from the main thread.
if (mMastRunMainThread) {
throwIfNotOnMainThread();
}
this.mMastRunMainThread = mastRunMainThread;
mFileListener=fileListener;
final String cacheKey = requestUrl;
if(useCache){
// Try to look up the request in the cache of remote images.
byte[] cachedData = mCache.getData(cacheKey);
if (cachedData != null) {
// Return the cached bitmap.
FileContainer container = new FileContainer(cachedData, requestUrl, null, null);
fileListener.onResponse(container, true);
return container;
}
}
// The data did not exist in the cache, fetch it!
FileContainer fileContainer = new FileContainer(null, requestUrl, cacheKey, fileListener);
// Update the caller to let them know that they should use the default bitmap.
// fileListener.onResponse(fileContainer, true);
// Check to see if a request is already in-flight.
BatchedFileRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(fileContainer);
return fileContainer;
}
// The request is not already in flight. Send the new request to the network and track it.
Request<byte[]> newRequest =makeFileRequest(requestUrl, cacheKey);
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey, new BatchedFileRequest(newRequest, fileContainer));
return fileContainer;
}
/**
* Issues a bitmap request with the given URL if that image is not available
* in the cache, and returns a bitmap container that contains all of the data
* relating to the request (as well as the default image if the requested
* image is not available).
* @param requestUrl The url of the remote image
* @param fileListener The listener to call when the remote image is loaded
* @return A container object that contains all of the properties of the request, as well as
* the currently available image (default if remote is not loaded).
*/
public FileContainer get(String requestUrl, FileListener fileListener){
return get(requestUrl,fileListener,false);
}
public FileContainer get(String requestUrl){
return get(requestUrl,mFileListener);
}
/**
* 显示一个GIF的图片,需要android开源库android-gif-drawable的支持,默认使用缓存,并且必须在UI线程调用
* @param requestUrl gif地址
* @param gifUtil GIf控件,默认图,出错图等。
* @return 一个FileContainer ,包括下载的图片的信息,可以是缓存中来的。
*/
public FileContainer getToShowGif(String requestUrl, final GIFUtil gifUtil){
if(null==gifUtil)return null;
gifUtil.getGifImageView().setImageResource(gifUtil.getDefaultImg());
return get(requestUrl, new FileListener() {
@Override
public void onResponse(FileContainer fileContainer, boolean isImmediate) {
if(fileContainer.getData()!=null){
try {
gifUtil.getGifImageView().setImageDrawable(new GifDrawable(fileContainer.getData()));
} catch (IOException e) {
e.printStackTrace();
gifUtil.getGifImageView().setImageResource(gifUtil.getErrorImg());
}
}
}
@Override
public void onErrorResponse(VolleyError volleyError) {
gifUtil.getGifImageView().setImageResource(gifUtil.getErrorImg());
}
});
}
protected Request<byte[]> makeFileRequest(String requestUrl, final String cacheKey) {
return new FileRequest(requestUrl, new Listener<byte[]>() {
@Override
public void onResponse(byte[] data) {
onGetFileSuccess(cacheKey,data);
}
}, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError volleyError) {
onGetFileError(cacheKey,volleyError);
}
});
}
/**
* Sets the amount of time to wait after the first response arrives before delivering all
* responses. Batching can be disabled entirely by passing in 0.
* @param newBatchedResponseDelayMs The time in milliseconds to wait.
*/
public void setBatchedResponseDelay(int newBatchedResponseDelayMs) {
mBatchResponseDelayMs = newBatchedResponseDelayMs;
}
/**
* Handler for when an image was successfully loaded.
* @param cacheKey The cache key that is associated with the image request.
* @param data The data that was returned from the network.
*/
protected void onGetFileSuccess(String cacheKey, byte[] data) {
// cache the image that was fetched.
if(useCache){
mCache.putData(cacheKey, data);
}
// remove the request from the list of in-flight requests.
BatchedFileRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Update the response bitmap.
request.mResponseData = data;
// Send the batched response
batchResponse(cacheKey, request);
}
}
/**
* Handler for when an image failed to load.
* @param cacheKey The cache key that is associated with the image request.
*/
protected void onGetFileError(String cacheKey, VolleyError error) {
// Notify the requesters that something failed via a null result.
// Remove this request from the list of in-flight requests.
BatchedFileRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Set the error for this request
request.setError(error);
// Send the batched response
batchResponse(cacheKey, request);
}
}
/**
* Container object for all of the data surrounding an image request.
*/
public class FileContainer {
/**
* The most relevant bitmap for the container. If the image was in cache, the
* Holder to use for the final bitmap (the one that pairs to the requested URL).
*/
private byte[] mdata=null;
private final FileListener mListener;
/** The cache key that was ass ociated with the request */
private final String mCacheKey;
/** The request URL that was specified */
private final String mRequestUrl;
/**
* Constructs a BitmapContainer object.
* @param data The final bitmap (if it exists).
* @param requestUrl The requested URL for this container.
* @param cacheKey The cache key that identifies the requested URL for this container.
*/
public FileContainer(byte[] data, String requestUrl,
String cacheKey, FileListener listener) {
this.mdata = data;
mRequestUrl = requestUrl;
mCacheKey = cacheKey;
mListener = listener;
}
/**
* Releases interest in the in-flight request (and cancels it if no one else is listening).
*/
public void cancelRequest() {
if (mListener == null) {
return;
}
BatchedFileRequest request = mInFlightRequests.get(mCacheKey);
if (request != null) {
boolean canceled = request.removeContainerAndCancelIfNecessary(this);
if (canceled) {
mInFlightRequests.remove(mCacheKey);
}
} else {
// check to see if it is already batched for delivery.
request = mBatchedResponses.get(mCacheKey);
if (request != null) {
request.removeContainerAndCancelIfNecessary(this);
if (request.mContainers.size() == 0) {
mBatchedResponses.remove(mCacheKey);
}
}
}
}
/**
* Returns the bitmap associated with the request URL if it has been loaded, null otherwise.
*/
public byte[] getData() {
return mdata;
}
/**
* Returns the requested URL for this container.
*/
public String getRequestUrl() {
return mRequestUrl;
}
}
/**
* Wrapper class used to map a Request to the set of active FileContainer objects that are
* interested in its results.
*/
private class BatchedFileRequest {
/** The request being tracked */
private final Request<?> mRequest;
/** The result of the request being tracked by this item */
private byte[] mResponseData;
/** Error if one occurred for this response */
private VolleyError mError;
/** List of all of the active FileContainers that are interested in the request */
private final LinkedList<FileContainer> mContainers = new LinkedList<FileContainer>();
/**
* Constructs a new BatchedFileRequest object
* @param request The request being tracked
* @param container The FileContainer of the person who initiated the request.
*/
public BatchedFileRequest(Request<?> request, FileContainer container) {
mRequest = request;
mContainers.add(container);
}
/**
* Set the error for this response
*/
public void setError(VolleyError error) {
mError = error;
}
/**
* Get the error for this response
*/
public VolleyError getError() {
return mError;
}
/**
* Adds another FileContainer to the list of those interested in the results of
* the request.
*/
public void addContainer(FileContainer container) {
mContainers.add(container);
}
/**
* Detatches the data container from the request and cancels the request if no one is
* left listening.
* @param container The container to remove from the list
* @return True if the request was canceled, false otherwise.
*/
public boolean removeContainerAndCancelIfNecessary(FileContainer container) {
mContainers.remove(container);
if (mContainers.size() == 0) {
mRequest.cancel();
return true;
}
return false;
}
}
/**
* Starts the runnable for batched delivery of responses if it is not already started.
* @param cacheKey The cacheKey of the response being delivered.
* @param request The BatchedFileRequest to be delivered.
*/
private void batchResponse(String cacheKey, BatchedFileRequest request) {
mBatchedResponses.put(cacheKey, request);
// If we don't already have a batch delivery runnable in flight, make a new one.
// Note that this will be used to deliver responses to all callers in mBatchedResponses.
if (mRunnable == null) {
mRunnable = new Runnable() {
@Override
public void run() {
for (BatchedFileRequest bir : mBatchedResponses.values()) {
for (FileContainer container : bir.mContainers) {
// If one of the callers in the batched request canceled the request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mdata = bir.mResponseData;
container.mListener.onResponse(container, false);
} else {
container.mListener.onErrorResponse(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
// Post the runnable.
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
private void throwIfNotOnMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("FileLoader must be invoked from the main thread.");
}
}
/**
* Creates a cache key for use with the L1 cache.
* @param url The URL of the request.
*/
private static String getCacheKey(String url) {
return url;
}
}
ImageRequest改成 了FileRequest,内容如下:
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.widget.ImageView.ScaleType;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.HttpHeaderParser;
/**
* A canned request for getting an image at a given URL and calling
* back with a decoded Bitmap.
*/
public class FileRequest extends Request<byte[]> {
private final Response.Listener<byte[]> mListener;
/**
* Decoding lock so that we don't decode more than one image at a time (to avoid OOM's)
*/
private static final Object sDecodeLock = new Object();
/**
* Creates a new image request, decoding to a maximum specified width and
* height. If both width and height are zero, the image will be decoded to
* its natural size. If one of the two is nonzero, that dimension will be
* clamped and the other one will be set to preserve the image's aspect
* ratio. If both width and height are nonzero, the image will be decoded to
* be fit in the rectangle of dimensions width x height while keeping its
* aspect ratio.
*
* @param url URL of the image
* @param listener Listener to receive the decoded bitmap
* @param errorListener Error listener, or null to ignore errors
*/
public FileRequest(String url, Response.Listener<byte[]> listener, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
mListener = listener;
}
@Override
protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
// Serialize all decode on a global lock to reduce concurrent heap usage.
synchronized (sDecodeLock) {
try {
if (response.data == null) {
return Response.error(new ParseError(response));
} else {
return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));
}
} catch (OutOfMemoryError e) {
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
@Override
protected void deliverResponse(byte[] response) {
mListener.onResponse(response);
}
}
到这里,就写完了,你可以直接把上面两个类,复制到你的工程里面,不需要改动原来的Volley,毕竟,去改原来的东西,改了以后,总感觉不舒服。
使用方法如下:
和ImageLoader一样的,先实例话一个FileLoader ,其中的FileCache是缓存方法,这个可以自己去实现。
FileLoader fileLoader=new FileLoader(Volley.newRequestQueue(this), new FileLoader.FileCache() {
@Override
public byte[] getData(String url) {
}
@Override
public void putData(String url, byte[] data) {
}
});
然后实例化一个GIF加载的类
FileLoader.GIFUtil gifUtil=fileLoader.new GIFUtil(gifImageView,R.drawable.loading,R.drawable.error);
说明一下,第一个gifImageView是 gif的控件,也就是上面提到的开源库的,第二,第三个,分别是默认也就是加载中显示的图片,和加载出错显示的图片
fileLoader.getToShowGif("http://img5.imgtn.bdimg.com/it/u=2695557640,2526776933&fm=21&gp=0.jpg",gifUtil);
到这里就完成了,也就是,加载 一个gif只需要2行代码,当然,需要注意的是,如果你是加载图片,必须要在ui线程调用,不然会出错。
当然,还有一下一些方法:
fileLoader.setUseCache(false);//设置不使用缓存
fileLoader.setmMastRunMainThread(false);//设置可以在非main线程使用,下载文件的时候可以这样用。
如果你是下载文件,加载数据,还可以这样用:
fileLoader.get("url", new FileLoader.FileListener() {
@Override
public void onResponse(FileLoader.FileContainer fileContainer, boolean isImmediate) {
byte[] data=fileContainer.getData();//这里就是下载好的数据,你要怎麽用,随便你了
}
@Override
public void onErrorResponse(VolleyError volleyError) {
}
});
更多的,请自己去发现把,为了写这个玩意儿,花了我一天的时间,总算写的比较满意,我只是大概测试了一下,不保证 一定没有bug。
这个文章,禁止一些转载,谢谢。