volley基本用法

网络框架

Posted by Cc1over on July 17, 2018

Volley基本用法

首先创建一个RequestQueue

public class MyApplication extends Application {

    private static RequestQueue mQueue;

    @Override
    public void onCreate() {
        super.onCreate();
        mQueue = Volley.newRequestQueue(getApplicationContext());
    }
   
    public static RequestQueue getVolleyQueue(){
        return mQueue;
    }
    
}

1)StringRequest(GET请求)

 StringRequest mStrReq = new StringRequest(Request.Method.GET, url,
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                // 这个方法运行在主线程中,可以直接更新ui
                // 通过参数返回请求到的数据
                mTv_result.setText(response);
                Toast.makeText(StrReqActivity.this, "下载成功", Toast.LENGTH_SHORT).show();
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // 这个方法运行在主线程中,可以直接更新ui
                // 失败在这里处理
                Toast.makeText(StrReqActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
            }
        });
        //设置Tag值
        mStrReq.setTag("100");
        btn_req.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //3.把请求对象添加到请求队列中,会自动发出请求
                mRequestQueue.add(mStrReq);
            }
        });

2)StringRequest(POST请求)

String url_post = url;
StringRequest mStrReq = new StringRequest(Request.Method.POST, url_post
    , new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            //
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            //
        }
    }) {//这里需要重写getParams方法 
       @Override
       protected Map<String, String> getParams() throws AuthFailureError {
           //把post的请求参数,放入请求体中
           //请求条件:platform=2&gifttype=1&compare=60841c5b7c69a1bbb3f06536ed685a48
           Map<String, String> params = new HashMap<>();
           params.put("platform", "2");
           params.put("gifttype", "1");
           params.put("compare", "60841c5b7c69a1bbb3f06536ed685a48");
           return params;
        }
    };
    //点击加入到请求队列中
    btn_req_json.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            mRequestQueue.add(mStrReq);
        }
   });
Volley与Activity生命周期关联

手动停止请求任务

@Override
protected void onDestroy() {
    super.onDestroy();
    //取消请求:有三种方式
    //1. 取消对应的请求对象
    mStrReq.cancel();
    //2. 取消请求队列中对应tag的请求
    //mRequestQueue.cancelAll("100");
    //3. 取消请求队列中所有的请求
    //mRequestQueue.cancelAll(this);
}

3)JsonObjectRequest与JsonArrayRequest(json数据请求)

JsonObjectRequest jsonObjectRequest =new JsonObjectRequest(Request.Method.GET, url, new Response.Listener<JSONObject>() {
            @Override
            public void onResponse(JSONObject response) {

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {

            }
        });

4)ImageRequest图片请求

//1) 图片下载的url
//2) 下载成功后,返回一个bitmap对象
//3)4) 最大宽度和最大高度,如果超过最大宽度和高度,会进行压缩到你设置的宽度和高度,0不限制
//5) 图片加载的形式
//6)图片显示的质量:RGB_565: 每个像素2字节   ARGB_8888:每个像素占4个字节
//7)下载图片失败后,在这里边处理
ImageRequest imgRequest = new ImageRequest(url_img, new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                //显示成功的图片
                iv_show.setImageBitmap(response);
            }
        }, 0, 0, ImageView.ScaleType.FIT_XY, Bitmap.Config.RGB_565, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                //设置失败的图片
                iv_show.setBackgroundResource(R.mipmap.ic_launcher);
            }
        });

5)ImageLoader图片加载

//1、首先有请求队列
RequestQueue requestQueue = Volley.newRequestQueue(this);
//2、创建出ImageLoader对象
ImageLoader loader = new ImageLoader(requestQueue, new BitmapCache());
//3、调用ImageLoader的get方法设置图片
//getImageListener中的三个参数:1,下载好的图片设置给哪个控件 2,默认图片 3.下载失败的图片
loader.get(url_img, ImageLoader.getImageListener(iv_show, R.mipmap.ic_default, R.mipmap.ic_error));

重载方法

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) {
    return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}

public ImageContainer get(String requestUrl, ImageListener imageListener,
            int maxWidth, int maxHeight, ScaleType scaleType) {
}

6)NetworkImageView带下载功能的ImageView

NetworkImageView拓展了ImageView实现了下載的功能,(相对于使用普通的ImageView,避免由于控件重用带来的图片錯位的问题)。使用方法: 把使用到ImageView的地方替換成NetworkImageView

初始化ImageLoader

通过NetWorkImageView对象设置下载图片的配置信息(如最大宽高等)

通过NetWorkImageView对象调用setImageUrl方法进行图片的下载

//事先初始化好的RequestQueue和ImageLoader
RequestQueue requestQueue = Volley.newRequestQueue(this);
ImageLoader imageLoader = new ImageLoader(requestQueue, new BitmapCache());

//NetWorkImageView
netiv.setDefaultImageResId(R.mipmap.ic_default); //设置默认的图片的id
netiv.setErrorImageResId(R.mipmap.ic_error); //设置下载失败后的图片的id
netiv.setImageUrl(url_img, imageLoader); //下载图片

Volley的实现原理

Volley.newRequestQueue() 返回个RequestQueue实例,该静态方法有两个重载,如下:

RequestQueue newRequestQueue(Context context);
RequestQueue newRequestQueue(Context context, HttpStack stack);

当调用第一个方法的时候也会调用第二个方法

 public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }

第二个参数HttpStack,是用来进行网路请求的,由Volley的整体框架图,可以看出其有两个实现子类,选择哪个子类是有SDK的版本决定的,源码如下:

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//放置缓存的地方
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        String userAgent = "volley/0";
        if (stack == null) {
               //如果版本号大于9(V2.3)
            if (Build.VERSION.SDK_INT >= 9) {
                //创建基于HttpURLConnection的HttpStack
                stack = new HurlStack();
            } else {
                //创建基于HttpClient的HttpStack
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //创建网络请求和queue
        Network network = new BasicNetwork(stack);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }

PS:主要是由于低版本的情况下HttpURLConnection存在BUG,当对一个可读的InputStream调用close()方法时,就有可能会导致连接池失效了,所以才会这样分版本创建
再来看看RequestQueue的构造方法

/**
     * Creates the worker pool. Processing will not begin until {@link #start()} is called.
     *
     * @param cache A Cache to use for persisting responses to disk
     * @param network A Network interface for performing HTTP requests
     * @param threadPoolSize Number of network dispatcher threads to create
     * @param delivery A ResponseDelivery interface for posting responses and errors
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

PS:这里的第三个参数是网络请求的线程数,默认值为4 RequestQueue.Start()

public void start() {
        stop();  // 确定当前线程已经停下来了
        // 只创建一条缓存分发线程并且启动,注入mNetWorkQueue,用于缓存获取失          
        // 败时进行网络请求
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        //创建多条网络请求线程并启动,在创建时注入mCache,用于缓存
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

首先是创建一个CacheDispatcher缓存发送者,它是一个缓存发送的线程,然后调用CacheDispatcher的start()函数。
然后循环创建NetworkDispatcher对象,因为默认的线程数是4,所以会循环4次创建网络调度(和相应的线程)到池大小,接着再调用start()函数。
所以其实这时候有5条线程在后台运行,不断等待网络请求的到来,其中CacheDispatcher是缓存线程,这也是为什么volley不适于数据量大、通讯频繁的网络操作,因为会占用网络请求的访问线程。 RequestQueue.add()

public <T> Request<T> add(Request<T> request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // 判断是否可缓存,如果不可缓存,直接添加到网络请求队列
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
        //如果已经存在当前Request,那么只需要在等待队列里面插入当前请求即可,防止多次网络访问
            if (mWaitingRequests.containsKey(cacheKey)) {
                // There is already a request in flight. Queue up.
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey, stagedRequests);
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

添加请求首先将Request标识为当前队列,然后添加到mCurrentRequests当前请求队列里。接着判断是否可以缓存,如果不行则直接调用网络队列进行请求然后方法结束,如果可以缓存,再判断当前等待队列是否包含这个请求,如果是添加到等待队列value队列中(PS:第一天添加的是的,等待队列的value列是为null的,只是用来指出这个地方有一个请求),防止重复请求;如果不是那就添加到缓存队列中。

请求处理过程

CacheDispatcher的run方法

@Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // Make a blocking call to initialize the cache.
        mCache.initialize();

        while (true) {
            try {
                // 阻塞获取一个Request,queue原型为BlockingQueue
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
                // 如果请求已经取消,则跳过该请求
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
                // 尝试从缓存里面获取数据
                Cache.Entry entry = mCache.get(request.getCacheKey());
                //如果为空,表示没有缓存,添加请求到网络队列
                if (entry == null) {
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }
                // 如果缓存过期了,添加到网络请求对列
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) {
                    // 不需要刷新缓存,直接进行结果传递
                    mDelivery.postResponse(request, response);
                } else {
                    // 需要刷新的缓存,在把缓存结果传递时,同时应该进行缓存的刷新
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);
                    response.intermediate = true;
                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

上面也提到了add()方法只是简单的把请求插入到了网络请求对列或者缓存请求对列,而在消息处理过程中会有CacheDispatcher一直在循环中的,并且阻塞获取获取到请求,然后判断这个请求是否过期,如果过期就跳过这个请求,如果没过期就从缓存中获取到当前请求的缓存,如果缓存为空或者缓存过期就把该请求添加到NetWorkQueue中,如果缓存存在,那就再判断缓存是否需要刷新,如果需要刷新就传递mCache的内容并把这个请求添加到请求队列中,如果不需要刷新就直接传递mCache的内容。

小结
  • 当一个Request请求添加到RequestQueue请求队列中,Volley就开始工作了。RequestQueue请求队列中持有一个CacheDispatcher缓存管家和一组NetworkDispatcher网络管家。
  • RequestQueue会先叫来CacheDispatcher缓存管家,让他去看看,当前请求的数据在没在cache中。
    1) 当前的数据在cache中,那就把数据从cache中取出来,然后经过一番加工,将加工好的数据交付给主线程
    2)当前数据没在cache中,进行第3步
  • 进行到了这一步,那肯定是数据没有在缓存中,那只能去网络中获取了,这时候RequestQueue会叫来NetworkDispatcher,NetworkDispatcher可是有一帮呢,其实这是一个线程池,默认情况下会启动4个线程去网络下载数据。所以RequestQueue把当前闲着的NetworkDispatcher叫来,给他们分配任务。
  • 拿到任务的NetworkDispatcher就会去网络上下载数据了,与此同时,他会判断下载到的数据能否写入到cache缓存中,如果可以的话就写入cache,以便于下一次直接从cache中获取到数据。最后,将数据加工,交付给主线程。(PS:对请求返回的结果,是通过我们的Request.parseNetworkResponse(NetworkResponse response)处理的,也就是说,如果是StringRequest,那么这个方法就是把请求结果转换为String,所以可以通过实现这个方法,自定义一个Request)