android 使用volley下载文件和加载gif图片

  • Post author:
  • Post category:其他


这个是我写的比较满意的,禁止一切转载,需要请联系博主。

大家都知道Volley是谷歌官方推出来的网络库,使用他进行数据交互,图片加载都非常方便,但是他不支持下载文件,网上很多人都说,他下载文件的性能非常不好,所以就没有提供下载的功能,这个是前提。

在安卓上面,默认是不支持显示GIF图片的,如果你用ImageView,也只会显示GIF图的第一帧,这个我们可以用的开源的GifImageView 控件,github地址如下:


https://github.com/felipecsl/GifImageView

在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。

这个文章,禁止一些转载,谢谢。



版权声明:本文为jie11447416原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。