Android 学习笔记之Volley(八)实现网络图片的数据加载

PS:最后一篇关于Volley框架的博客...

 

学习内容:

1.使用ImageRequest.java实现网络图片加载

2.使用ImageLoader.java实现网络图片加载

3.使用NetWorkImageView.java实现网络图片加载

 

  Volley的第三个作用就是实现网络图片的加载,图片加载在Volley中有三种不同的方式,各有各的用法,只是后两个还算是有相关联系的,因为NetWorkImageView是基于ImageLoader的..内部的图片加载还是使用ImageLoader,只是在后续的过程中有一些不同...

1.ImageRequest.java

  使用ImageRequest加载图片数据,ImageRequest没有什么特殊的,也需要建立一个请求队列,向请求队列当中添加这次请求就可以完成图片数据的加载...

package com.android.volley.toolbox;

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 android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;

public class ImageRequest extends Request<Bitmap> {
    /** Socket timeout in milliseconds for image requests */
    private static final int IMAGE_TIMEOUT_MS = 1000; //超时时间的定义..

    /** Default number of retries for image requests */
    private static final int IMAGE_MAX_RETRIES = 2; //一次最多重试的图片数量...

    /** Default backoff multiplier for image requests */
    private static final float IMAGE_BACKOFF_MULT = 2f; //后退请求的数量..

    private final Response.Listener<Bitmap> mListener; //成功监听..
    private final Config mDecodeConfig;  //属性编码...
    private final int mMaxWidth;    //最大宽度...
    private final int mMaxHeight;   //最大高度...

    /** 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(); //一个编码锁..目的是一次只能对一个图片进行编码,加载,避免OOM的发生...
    //指定url来创建一个ImageRequest请求...
    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
            Config decodeConfig, Response.ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(
                new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
    }
    //获取优先级...图片加载的优先级一般是最低的..
    @Override
    public Priority getPriority() {
        return Priority.LOW;
    }

    //重新设置图片的大小...其实为了缩小图片...
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
            int actualSecondary) {
        // If no dominant value at all, just return the actual.
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // If primary is unspecified, scale primary to match secondary's scaling ratio.
        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 (resized * ratio > maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }
    //解析响应...
    @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        // Serialize all decode on a global lock to reduce concurrent heap usage.
        synchronized (sDecodeLock) {
            try {
                return doParse(response); //调用doParse函数..
            } catch (OutOfMemoryError e) {
                VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
                return Response.error(new ParseError(e));
            }
        }
    }

    /**
     * The real guts of parseNetworkResponse. Broken out for readability.
     */
    //获取图片数据的过程...
    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;  //图片数据...
        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();  //定义一个位图选项对象..
        Bitmap bitmap = null;
        if (mMaxWidth == 0 && mMaxHeight == 0) {//这里表示如果为0,那么表示图片按照图片本身的大小进行展现..不用进行其他操作...
            decodeOptions.inPreferredConfig = mDecodeConfig;
            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
        } else {
            // If we have to resize this image, first get the natural bounds.
            decodeOptions.inJustDecodeBounds = true; //表示图片需要缩放..
            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);//这里并不是完全对图片进行编码,而是获取图片的基本参数..
            int actualWidth = decodeOptions.outWidth; //获取图片的宽度...
            int actualHeight = decodeOptions.outHeight; //获取图片的高度..

            // Then compute the dimensions we would ideally like to decode to.
            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                    actualWidth, actualHeight); //对宽度进行缩放...
            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                    actualHeight, actualWidth); //对高度进行缩放...

            // Decode to the nearest power of two scaling factor.
            decodeOptions.inJustDecodeBounds = false; //缩放后,表示图片不需要进行缩放了...
            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
            decodeOptions.inSampleSize =
                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);//根据实际大小和所需大小去找到一个最合适的大小...
            Bitmap tempBitmap =
                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);//对图片编码,转化成比特流的形式...

            // If necessary, scale down to the maximal acceptable size.
            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                    tempBitmap.getHeight() > desiredHeight)) {
                //这里表示如果通过上述缩放后图片的大小仍然比所需大小要大,那么按照所需大小进一步进行缩放...
                bitmap = Bitmap.createScaledBitmap(tempBitmap,
                        desiredWidth, desiredHeight, true);
                tempBitmap.recycle();
            } else {
                bitmap = tempBitmap; //否则直接为当前临时图片...
            }
        }

        if (bitmap == null) {
            return Response.error(new ParseError(response)); //返回错误数据..
        } else {
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); //返回成功数据...
        }
    }
    //对响应分发...
    @Override
    protected void deliverResponse(Bitmap response) {
        mListener.onResponse(response);
    }

    //需找最优大小..
    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }
} 

  源码看起来其实并不是非常的困难,还是比较好理解的..如果预先对ImageLoader加载图片非常熟悉的话,那么这个理解起来就不是非常的费劲...源码就分析到这,还是看看调用过程..这里的构造函数需要传递六个参数...

  public ImageRequest(url,Listener,maxWidth,maxHeight,Config,errorListener);

  传递的参数分别是url,成功监听,最大宽度和高度,图片使用的颜色属性,以及失败后的监听...这里宽度和高度如果指定成自定义的大小,那么图图片就会按照指定大小进行缩放,如果都指定成0,那么图片按照原本大小进行显示,不进行缩放设置...

package com.example.oop;


import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageCache;
import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.Volley;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;


public class MainActivity extends Activity implements OnClickListener {

    String url="http://192.168.199.172:8080/JSP/imageview.jpg";
    ImageView iv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv=(ImageView)findViewById(R.id.iv);
         init();
    }
    
    public void init(){
        RequestQueue queue=Volley.newRequestQueue(MainActivity.this);
         queue.add(new ImageRequest("http://192.168.199.172:8080/JSP/imageview.jpg",new Listener<Bitmap>(){
        @Override
          public void onResponse(Bitmap response){
            //自定义了一个imageview...图片资源就是下载后的Bitmap
            iv.setImageBitmap(response);
        }
    },0,0,Config.ARGB_8888, new ErrorListener(){
        @Override
         public void onErrorResponse(VolleyError error){
            System.out.println(error.toString());
        }
    }));
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub

    }

}

  这样就成功的使用ImageRequest加载了网络上的图片数据...但是使用ImageRequest加载网络图片的缺点是没有缓存机制,只能频繁的发送网络请求,而没有缓存请求,这样会增加服务器的负担...因此在这点上ImageRequest并不是非常的完善...

2.ImageLoader.java

  ImageLoader算是对ImageRequest的一个强化,这个类内部提供了缓存机制,这样我们可以更好的去处理请求,由于存在缓存,那么相同的请求可以通过从缓存中直接获取相关的数据,从而能够减少一些不必要的请求,这样会减少网络请求,减少服务器的负担...

package com.android.volley.toolbox;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;

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 com.android.volley.toolbox.ImageRequest;

import java.util.HashMap;
import java.util.LinkedList;

public class ImageLoader {
    /** 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 ImageCache mCache; //图片缓存对象...

    
    private final HashMap<String, BatchedImageRequest> mInFlightRequests =
            new HashMap<String, BatchedImageRequest>(); //表示正在处理的请求集合...

    /** HashMap of the currently pending responses (waiting to be delivered). */
    private final HashMap<String, BatchedImageRequest> mBatchedResponses =
            new HashMap<String, BatchedImageRequest>(); //批处理响应的集合..

    /** Handler to the main thread. */
    private final Handler mHandler = new Handler(Looper.getMainLooper());

    /** Runnable for in-flight response delivery. */
    private Runnable mRunnable; //用于分发响应...
    //缓存对象构造...
    public interface ImageCache {
        public Bitmap getBitmap(String url);
        public 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 onErrorResponse(VolleyError error) {
                if (errorImageResId != 0) {
                    view.setImageResource(errorImageResId); //如果加载失败,使用默认图片来显示...
                }
            }

            @Override
            public void onResponse(ImageContainer response, boolean isImmediate) {
                if (response.getBitmap() != null) {
                    view.setImageBitmap(response.getBitmap());//如果响应数据不为空,那么显示网络图片...
                } else if (defaultImageResId != 0) {
                    view.setImageResource(defaultImageResId);//失败显示默认图片...
                }
            }
        };
    }

    //失败监听的一个接口...
    public interface ImageListener extends ErrorListener {
   
        public void onResponse(ImageContainer response, boolean isImmediate);
    }

    //如果请求允许缓存,那么保存缓存数据...
    public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
        throwIfNotOnMainThread();

        String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
        return mCache.getBitmap(cacheKey) != null;
    }

    //调用下面的函数...
    public ImageContainer get(String requestUrl, final ImageListener listener) {
        return get(requestUrl, listener, 0, 0);
    }

    //获取图片的过程...
    public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight) {
        // only fulfill requests that were initiated from the main thread.
        throwIfNotOnMainThread();
        //如果缓存中存在,那么从缓存当中取出数据...
        final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

        Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
        if (cachedBitmap != null) { //缓存存在...
            // Return the cached bitmap.
            ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);//将缓存数据的图片,以及url进行封装,返回...
            imageListener.onResponse(container, true);
            return container;
        }

        ImageContainer imageContainer =
                new ImageContainer(null, requestUrl, cacheKey, imageListener); //缓存不存在,新建立一个ImageContainer对象...

        // Update the caller to let them know that they should use the default bitmap.
        //回调函数,使用默认图片...
        imageListener.onResponse(imageContainer, true);

        // Check to see if a request is already in-flight.
        BatchedImageRequest request = mInFlightRequests.get(cacheKey);  //查看当前执行队列中是否有与之相同的请求..
        if (request != null) {
            // If it is, add this request to the list of listeners.
            request.addContainer(imageContainer); //如果有,那么加入到批处理请求队列当中,由这个请求队列去处理这些相同的请求..
            return imageContainer;
        }

        //如果没有,那么新建立一个请求...
        Request<?> newRequest =
            new ImageRequest(requestUrl, new Listener<Bitmap>() {
                @Override
                public void onResponse(Bitmap response) {
                    onGetImageSuccess(cacheKey, response);
                }
            }, maxWidth, maxHeight,
            Config.RGB_565, new ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                    onGetImageError(cacheKey, error);
                }
            });
        //添加新请求到请求队列当中
        mRequestQueue.add(newRequest);
        //向当前请求队列中加入这次请求的键和值...
        mInFlightRequests.put(cacheKey,
                new BatchedImageRequest(newRequest, imageContainer));
        return imageContainer;
    }

    //设置批处理响应的延时...
    public void setBatchedResponseDelay(int newBatchedResponseDelayMs) {
        mBatchResponseDelayMs = newBatchedResponseDelayMs;
    }

    //成功获取图片...
    private void onGetImageSuccess(String cacheKey, Bitmap response) {
        // cache the image that was fetched.
        mCache.putBitmap(cacheKey, response); //向缓存中放入数据...

        // remove the request from the list of in-flight requests.
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey); //请求成功,那么表示所有与之相同的请求都获取到了相关数据,那么就将请求移出队列...

        if (request != null) {
            // Update the response bitmap.
            request.mResponseBitmap = response;

            // Send the batched response
            batchResponse(cacheKey, request);//发送相同请求的响应...
        }
    }

    //获取图片失败的时候和成功时执行的操作基本是相同的...
    private void onGetImageError(String cacheKey, VolleyError error) {
        // Notify the requesters that something failed via a null result.
        // Remove this request from the list of in-flight requests.
        BatchedImageRequest request = mInFlightRequests.remove(cacheKey);

        // Set the error for this request
        request.setError(error);

        if (request != null) {
            // Send the batched response
            batchResponse(cacheKey, request);
        }
    }

    //ImageContainer类..
    public class ImageContainer {
   
        private Bitmap mBitmap; //图片对象..

        private final ImageListener mListener; //成功监听..

        /** The cache key that was associated with the request */
        private final String mCacheKey; //缓存键值

        /** The request URL that was specified */
        private final String mRequestUrl; //url地址...

        //将图片,url,缓存键值,以及监听的一个封装...
        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 {
                // 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);
                    }
                }
            }
        }
        //获取图片数据...
        public Bitmap getBitmap() {
            return mBitmap;
        }

        //获取url...
        public String getRequestUrl() {
            return mRequestUrl;
        }
    }

   
    private class BatchedImageRequest {
        /** The request being tracked */
        private final Request<?> mRequest; //请求对象..

        /** The result of the request being tracked by this item */
        private Bitmap mResponseBitmap; //位图对象...

        /** Error if one occurred for this response */
        private VolleyError mError; //错误对象...

        private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>(); //用于保存相同的键值对应的请求...

        //构造函数...
        public BatchedImageRequest(Request<?> request, ImageContainer 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;
        }

        //将每一个不同的请求加入队列中
        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;
        }
    }

    //批处理响应...
    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 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.mBitmap = bir.mResponseBitmap;
                                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("ImageLoader must be invoked from the main thread.");
        }
    }
    //获取缓存中的键值..
    private static String getCacheKey(String url, int maxWidth, int maxHeight) {
        return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
                .append("#H").append(maxHeight).append(url).toString();
    }
}

  这个源码相对就比较多,但是其中的过程并不是非常的难理解...还是看一下实际中是如何使用的...

package com.example.oop;


import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageCache;
import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.Volley;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;


public class MainActivity extends Activity implements OnClickListener {

    String url="http://192.168.199.172:8080/JSP/imageview.jpg";
    ImageView iv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView=(ImageView) findViewById(R.id.iv);
        init();
    }
    
    public void init(){
        RequestQueue queue=Volley.newRequestQueue(MainActivity.this);
        ImageLoader imageLoader=new ImageLoader(queue, new ImageCache() {
            
            LruCache<String,Bitmap>cache=new LruCache<String, Bitmap>(((int) Runtime.getRuntime().maxMemory())/8){
                
                @Override
                protected int sizeOf(String key,Bitmap bitmap){
                    return bitmap.getRowBytes()*bitmap.getHeight();
                }
            };
            
            @Override
            public void putBitmap(String url, Bitmap bitmap) {
                // TODO Auto-generated method stub
                cache.put(url, bitmap);
            }
            
            @Override
            public Bitmap getBitmap(String url) {
                // TODO Auto-generated method stub
                return cache.get(url);
            }
        });
        //为ImageLoader设置监听...成功则iv显示成功图片,没加载完显示默认图片,加载失败显示失败图片..
        ImageListener imagelistener=imageLoader.getImageListener(iv,default_image,error_image);
imageloader.get(url,imagelistener); } @Override
public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public void onClick(View v) { // TODO Auto-generated method stub } }

   imageloader.get(url,imagelistener);获取图片的最终过程...这里get函数首先判断图片是否有缓存,通过url的形式来判断是否存在缓存,缓存内部存在,那么直接从缓存中取出,如果缓存中不存在,那么首先看本次的请求是否在批处理图像请求队列中,如果队列中存在这类请求,那么就直接再次添加相同的请求,就没有必要重新建立一个新的请求了...如果这个请求在批处理队列中并不存在,那么就创建一个新的请求来完成...然后放入RequestQueue中和批处理请求队列中...这样就完成了使用ImageLoader来加载图片数据...

  这里使用了LruCache()最近最久未使用算法设立的缓存机制,如果缓存的容量发生了不足,那么直接清除最近最久未被使用的图片缓存...同时每一个Key对应的请求仅仅被放入请求队列一次,对于相同key值的请求,直接从缓存中取出即可,如果缓存内部没有数据,那么通过判断当前是否有这类的请求...如果有,直接进行赋值,如果没有定义一个新的请求...

3.NetWorkImageLoader.java

  第三种加载图片的方式...它基于ImageLoader,加载图片也是通过ImageLoader来实现的,只是在一些地方有一定的差异...

package com.android.volley.toolbox;

import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;

import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.android.volley.toolbox.ImageLoader.ImageListener;


public class NetworkImageView extends ImageView {
    /** The URL of the network image to load */
    private String mUrl; //图片的url

    /**
     * Resource ID of the image to be used as a placeholder until the network image is loaded.
     */
    private int mDefaultImageId; //默认图片的ID..

    /**
     * Resource ID of the image to be used if the network response fails.
     */
    private int mErrorImageId; //错误显示的图片...

    /** Local copy of the ImageLoader. */
    private ImageLoader mImageLoader;  //图片加载对象...

    /** Current ImageContainer. (either in-flight or finished) */
    private ImageContainer mImageContainer; //ImageContainer对象..

    public NetworkImageView(Context context) {
        this(context, null);
    }

    public NetworkImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public NetworkImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    //设置图片的url...
    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }

    //设置默认图片...
    public void setDefaultImageResId(int defaultImage) {
        mDefaultImageId = defaultImage;
    }

    //设置错误图片...
    public void setErrorImageResId(int errorImage) {
        mErrorImageId = errorImage;
    }

    //这个函数在OnLauout调用时才被调用...用于加载图片...
    private void loadImageIfNecessary(final boolean isInLayoutPass) {
        //获取长度和宽度...
        int width = getWidth();
        int height = getHeight();

        boolean isFullyWrapContent = getLayoutParams() != null
                && getLayoutParams().height == LayoutParams.WRAP_CONTENT
                && getLayoutParams().width == LayoutParams.WRAP_CONTENT;
        //如果view控件的大小界限是不确定的,并且还不是wrap_content属性,那么放弃加载...
        if (width == 0 && height == 0 && !isFullyWrapContent) {
            return;
        }

        // if the URL to be loaded in this view is empty, cancel any old requests and clear the
        // currently loaded image.
        //如果这个url被加载后,view是空的,那么就撤销掉与之相同的所有请求...
        if (TextUtils.isEmpty(mUrl)) {
            if (mImageContainer != null) {
                mImageContainer.cancelRequest();
                mImageContainer = null;
            }
            setImageBitmap(null);
            return;
        }

        // if there was an old request in this view, check if it needs to be canceled.
        //如果请求是相同的url,那么直接return,数据的获取由缓存处理...
        if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
            if (mImageContainer.getRequestUrl().equals(mUrl)) {
                // if the request is from the same URL, return.
                return;
            } else {
                // if there is a pre-existing request, cancel it if it's fetching a different URL.
                mImageContainer.cancelRequest();
                setImageBitmap(null);
            }
        }

        // The pre-existing content of this view didn't match the current URL. Load the new image
        // from the network.
        //如果不满足上述情况,那么就建立一个新的ImageContainer来保存...
        ImageContainer newContainer = mImageLoader.get(mUrl,
                new ImageListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        if (mErrorImageId != 0) {
                            setImageResource(mErrorImageId);
                        }
                    }

                    @Override
                    public void onResponse(final ImageContainer response, boolean isImmediate) {
                        // If this was an immediate response that was delivered inside of a layout
                        // pass do not set the image immediately as it will trigger a requestLayout
                        // inside of a layout. Instead, defer setting the image by posting back to
                        // the main thread.
                        if (isImmediate && isInLayoutPass) {
                            post(new Runnable() {
                                @Override
                                public void run() {
                                    onResponse(response, false);
                                }
                            });
                            return;
                        }

                        if (response.getBitmap() != null) {
                            setImageBitmap(response.getBitmap());
                        } else if (mDefaultImageId != 0) {
                            setImageResource(mDefaultImageId);
                        }
                    }
                });

        // update the ImageContainer to be the new bitmap container.
        mImageContainer = newContainer;
    }
    //布局函数调用...
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        loadImageIfNecessary(true); //这里才会去调用图片加载函数...
    }

    @Override
    protected void onDetachedFromWindow() {
        if (mImageContainer != null) {
            // If the view was bound to an image request, cancel it and clear
            // out the image from the view.
            mImageContainer.cancelRequest();
            setImageBitmap(null);
            // also clear out the container so we can reload the image if necessary.
            mImageContainer = null;
        }
        super.onDetachedFromWindow();
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        invalidate();
    }
}

 

  这是源码的实现过程,有些细节上的问题就不进行分析了...我们还是来看一下实例...

 

package com.example.oop;


import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageCache;
import com.android.volley.toolbox.NetworkImageView;
import com.android.volley.toolbox.Volley;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;


public class MainActivity extends Activity implements OnClickListener {

    String url="http://192.168.199.172:8080/JSP/imageview.jpg";
    NetworkImageView imageView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView=(NetworkImageView) findViewById(R.id.network);
        init();
    }
    
    public void init(){
        RequestQueue queue=Volley.newRequestQueue(MainActivity.this);
        ImageLoader imageLoader=new ImageLoader(queue, new ImageCache() {
            
            LruCache<String,Bitmap>cache=new LruCache<String, Bitmap>(((int) Runtime.getRuntime().maxMemory())/8){
                
                @Override
                protected int sizeOf(String key,Bitmap bitmap){
                    return bitmap.getRowBytes()*bitmap.getHeight();
                }
            };
            
            @Override
            public void putBitmap(String url, Bitmap bitmap) {
                // TODO Auto-generated method stub
                cache.put(url, bitmap);
            }
            
            @Override
            public Bitmap getBitmap(String url) {
                // TODO Auto-generated method stub
                return cache.get(url);
            }
        });
        imageView.setDefaultImageResId(R.drawable.default_image); //这里需要手动设置默认图片和错误显示图片...
        imageView.setErrorImageResId(R.drawable.no_image);
                //加载获取图片的过程...
        imageView.setImageUrl(url, imageLoader);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub

    }

}

 需要说的是,布局文件中我们需要这样进行书写去定义一个NetWorkImageView对象...通过获取ID,来进行数据信息的加载...

<com.android.volley.toolbox.NetworkImageView
        android:id="@+id/network"
        android:layout_height="100dip"
        android:layout_width="100dip">
    </com.android.volley.toolbox.NetworkImageView>

 

posted @ 2015-10-26 16:51  代码丶如风  阅读(1734)  评论(0编辑  收藏  举报