Android网络请求之Volley

一、概述

        在上一篇文章中,我们分析了Android中常用的网络请求框架HttpURLConnection:HttpURLConnction源码分析,通过分析,我们知道,HttpURLConnection发起请求是同步的,而Android系统规定,不能在主线程中发起网络请求,以免阻塞主线程导致界面卡顿甚至ANR。所以我们在使用HttpURLConnection发起网络请求时,首先要创建一个子线程,在子线程中发起网络请求,请求完成后,如果需要根据请求结果更新界面,还需要通过Handler向主线程发送消息,让主线程更新界面。整个网络请求的过程很麻烦,为了更加方便开发者处理网络请求,形形色色的网络请求框架也应运而生,比如AsyncHttpClient、Universal-Image-Loader、Glide、Volley等。本篇文章我们就来分析Volley库的源码实现,来更进一步了解Android中的网络请求。

      在开始分析之前,我们先考虑一下,如果让我们来实现一个Android端的网络请求框架,应该要怎样实现?按照我的理解,一个好的Android端网络请求框架应该做到以下几点:

  • 提供给开发者的API应该尽可能简单
  • 应该具有可扩展性,开发者通过API调用,可以实现不同的网络请求功能
  • 基于Android的特性,应该能够做到线程的切换,开发者在主线程中调用API发起网络请求,框架应该能够将请求任务切换到子线程中去执行,并且可以指定处理返回结果的线程
  • 应该有缓存功能,对于不必要的请求直接使用缓存中的数据,这样可以提高性能和节省网络流量
  • 应该具有较高的性能,网络请求可以并发执行,不会因为之前的网络请求未完成而产生阻塞

     上面这些是我目前能够想到的,后面如果有新的点在补充。

     Volley库是2013年Google I/O大会推出的一个新的网络请求框架。Volley既可以访问网络取得数据,也可以加载图片,并且在性能方面进行了大幅度的调整。它的设计目标就是适用于进行数据量不大但是通信频繁的网络操作。对于大数据量的网络请求,比如下载文件等,Volley的表现却非常糟糕。

二、Volley的基本用法

      Volley请求网络都是基于请求队列的,开发者只需要把请求放入到请求队里中就可以了,请求队列会依次进行请求。一般情况下,如果一个应用程序请求不是特别频繁,完全可以只有一个请求队列;如果网络请求非常多或有其他情况,则可以是一个Activity对应一个网络请求队列。

1、StringRequest

下面我们利用Volley来请求百度的首页,并打印返回结果,代码如下:

        mRequestQueue = Volley.newRequestQueue(getApplicationContext()); // 构造请求队列

        // 创建一个StringRequest对象,可以设置请求方法,请求URL,处理返回结果的监听,处理异常情况的监听
        StringRequest stringRequest = new StringRequest(Request.Method.GET, "https://www.baidu.com",
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Log.i("liunianprint:", response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.i("liunianprint:", error.getMessage() + error.toString());
                    }
        });
        
        mRequestQueue.add(stringRequest); // 将请求加入到请求队列中

执行代码,可以打印出百度的首页HTML文本。通过上面的代码,我们发现,Volley请求网络大致可以分为三个部分:

1、构造请求队列(对个请求可以共用一个请求队列)

2、构造对应的Request对象,传入请求参数、处理请求结果的监听已经处理异常情况的监听

3、将请求加入到请求队列中

单从使用方式来看,相比于HttpURLConnection,Volley库请求网络不用主动切换线程(其实Volley底层也会将网络请求放在子线程中执行,只是隐藏了实现细节),并且需要提供了监听处理返回结果,监听中方法是在主线程中执行的,这样就省去了需要通过Handler让主线程处理结果的步骤(Volley底层也是通过Handler将处理返回结果的方法切换到主线程执行),最后一点,Volley库提供的API操作更为简单和易于理解,我们不需要关心打开socket连接、判断响应状态码以及直接操作连接的InputStream和OutputStream来写入和读取数据。Volley对这些网络请求的底层实现细节进行了再度封装,使开发者能够更加方便的进行网络请求。

上面是利用Volley发送一个GET请求,如果是POST请求呢?应该如何发送,代码如下:

 StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener) {  
    @Override  
    protected Map<String, String> getParams() throws AuthFailureError {  
        Map<String, String> map = new HashMap<String, String>();  
        map.put("params1", "value1");  
        map.put("params2", "value2");  
        return map;  
    }  
};

StringRequest没有提供直接设置POST请求参数的方法,但是当发出POST请求的时候,Volley会尝试调用StringRequest的父类中的getParams()方法来获取POST参数,所以我们可以重写StringRequest的getParams()方法,写入POST数据。你可能会说,每次都这样用起来岂不是很累?连个设置POST参数的方法都没有。但是不要忘记,Volley是开源的,只要你愿意,你可以自由地在里面添加和修改任何的方法,轻松就能定制出一个属于你自己的Volley版本。

2、JsonRequest

为了方便处理Json格式的返回数据,Volley提供了JsonRequest类来处理,类似于StringRequest,JsonRequest也是继承自Request类的,不过由于JsonRequest是一个抽象类,因此我们无法直接创建它的实例,那么只能从它的子类入手了。JsonRequest有两个直接的子类,JsonObjectRequest和JsonArrayRequest,从名字上你应该能就看出它们的区别了吧?一个是用于请求一段JSON数据的,一个是用于请求一段JSON数组的。以JsonObjectRequest为例,我们发起一个POST请求:

        // 写入请求参数
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("params1", value1);
            jsonObject.put("params2", value2);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        // 构造JsonObjectRequest对象
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, url, jsonObject,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        Log.d("liunianprint:", response.toString());
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("liunianprint:", error.getMessage(), error);
            }
        });

        // 将请求加入到请求队列中
        mRequestQueue.add(jsonObjectRequest);

通过设置一个JsonObject,JsonObjectRequest的构造函数中可以直接设置请求参数。在onResponse方法中,提供的返回结果是一个JsonObject对象,我们可以通过Gson等库将其转换为一个具体的类对象。

3、ImageRequest

ImageRequest是已经过时的类,主要是用来加载图片,它的使用方法和StringRequest和JsonRequest类似:

                String imagePath = "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1641460231,985790943&fm=27&gp=0.jpg";
                ImageRequest imageRequest = new ImageRequest(imagePath, new Response.Listener<Bitmap>() {
                    @Override
                    public void onResponse(Bitmap response) {
                        mImageView.setImageBitmap(response);
                    }
                }, 0, 0, Bitmap.Config.RGB_565, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.i("liunianprint:", error.getMessage() + error.toString());
                    }
                });

查看ImageRequest源码可以发现,它可以设置你想要的图片的最大高度和宽度,在设置图片时,如果图片超过了期望的最大高度和宽度,最会进行压缩:

    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
            ScaleType scaleType, 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;
        mScaleType = scaleType;
    }

4、ImageLoader

ImageLoader的内部使用了ImageRequest来实现,它的构造方法中可以传入一个ImageCache形参,实现图片的缓存功能,同时还可以过滤重复连接,避免重复请求。与ImageRequest不同,ImageLoader加载图片时会先显示默认的图片,等图片请求完成后才会将其显示在ImageView上,如果请求发送错误,则会显示设置的加载错误图片。利用ImageLoader加载图片的代码如下:

                ImageLoader imageLoader = new ImageLoader(mRequestQueue, new BitmapCache());
                ImageLoader.ImageListener listener = ImageLoader.getImageListener(mImageView, R.mipmap.ic_launcher, R.mipmap.ic_launcher);
                String imagePath = "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1641460231,985790943&fm=27&gp=0.jpg";
                imageLoader.get(imagePath, listener);

可以看到,ImageLoader的构造函数中会传入一个ImageCahce对象,BitmapCache是我自己实现的类,其继承了ImageCache:

public class BitmapCache implements ImageLoader.ImageCache {
    private LruCache<String, Bitmap> lruCache;
    private int max = 10*1024*1024;

    public BitmapCache() {
        lruCache = new LruCache<String, Bitmap>(max) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight();
            }
        };
    }

    @Override
    public Bitmap getBitmap(String url) {
        return lruCache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        lruCache.put(url, bitmap);
    }
}

三、Volley源码分析

1、新建请求队列

RequestQueue = Volley.newRequestQueue(getApplicationContext());

跟踪源码:

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

    public static RequestQueue newRequestQueue(Context context, HttpStack stack)
    {
    	return newRequestQueue(context, stack, -1);
    }

    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); // 缓存目录

        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode; // userAgent为包名加上版本号
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack(); // 2.3以后,网络请求类用HrlStack,实现为HttpUrlConnection
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); // 2.3以前用HttpClient
            }
        }

        Network network = new BasicNetwork(stack); // 实例化NetWork
        
        // 实例化请求队列
        RequestQueue queue;
        if (maxDiskCacheBytes <= -1)
        {
        	// No maximum size specified
        	queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }
        else
        {
        	// Disk cache size specified
        	queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }

        queue.start(); // 调用请求队列的start方法

        return queue;
    }

我们一般直接使用第一个方法来获得请求队列,最终都是实现是在第三个方法中。首先我们来看一下三个参数:

context:上下文环境

stack:需要使用的网络连接请求类

maxDiskCacheBytes:缓存目录的最大容量

经过分析,我们知道,newRequestQueue主要做了三件事情:

1、首先创建缓存目录以及设置userAgent;

2、根据Android系统的版本来实例化网络请求类,如果是Android2.3以前的版本,会使用HttpClient请求网络,否则使用HttpURLConnection,这也和我们在HttpURLConnction源码分析这篇文章中说道的HTTPClient和HttpURLConnection的优缺点对比有关;现在的Android系统基本都是2.3以后的版本,所以后面我们只分析2.3版本以后的实现。

3、最后根据缓存目录和网络请求类生成请求队列,然后调用其start()方法并返回请求队列对象。

我们来看一下HurlStack和BasicNetWork的构造方法:

public class HurlStack implements HttpStack {
    private final UrlRewriter mUrlRewriter;
    private final SSLSocketFactory mSslSocketFactory;

    public HurlStack() {
        this(null);
    }

    /**
     * @param urlRewriter Rewriter to use for request URLs
     */
    public HurlStack(UrlRewriter urlRewriter) {
        this(urlRewriter, null);
    }

    /**
     * @param urlRewriter Rewriter to use for request URLs
     * @param sslSocketFactory SSL factory to use for HTTPS connections
     */
    public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
        mUrlRewriter = urlRewriter;
        mSslSocketFactory = sslSocketFactory;
    }
    ...
}
public class BasicNetwork implements Network {    
    public BasicNetwork(HttpStack httpStack) {
        // If a pool isn't passed in, then build a small default pool that will give us a lot of
        // benefit and not use too much memory.
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    /**
     * @param httpStack HTTP stack to be used
     * @param pool a buffer pool that improves GC performance in copy operations
     */
    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mPool = pool;
    }
    ...
}

BasicNetwork中保存了网络请求类HttpStack,并且构造了一个ByteArrayPool对象,ByteArrayPool的作用我们后面再分析。

接下来分析RequestQueue的构造方法:

    /** Number of network request dispatcher threads to start. */
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;  
  
    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

    /**
     * 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; // BasicNetwork实例
        mDispatchers = new NetworkDispatcher[threadPoolSize]; // 网络请求线程数组,默认是4个请求线程
        mDelivery = delivery; // 派发请求结果的接口
    }

最后一个构造函数中,RequestQueue有四个参数形参需要传入:

mCache:文件缓存对象(用来缓存请求内容)

mNetwork:BasicNetwork实例,用来发送网络请求

mDispatchers:网络请求线程数组,默认开启4个请求线程来发送网络请求

mDelivery:派发网络请求返回结果的接口

构造完RequestQueue对象后,紧接着,我们调用了其start()方法:

    /**
     * Starts the dispatchers in this queue.
     */
    public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        // 创建缓存线程并开启
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // 创建网络请求线程并开启
        // Create network dispatchers (and corresponding threads) up to the pool size.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    /**
     * Stops the cache and network dispatchers.
     */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }

start()方法主要做了三件事:

1、调用stop方法确保之前开启的线程都已经关闭

2、创建缓存线程并开启

3、创建网络请求线程并开启

到目前为止,请求队列的实例化过程就分析完了,总结一下,主要处理了以下几件事情:

1、创建缓存目录

2、根据Android系统版本来决定使用何种网络请求框架,Android2.3以前的版本使用HttpClient,Android2.3以后的版本使用HttpURLConnection

3、创建处理请求结果对象

4、开启一条缓存线程和4条(默认情况)网络请求线程

2、创建请求对象

在生成请求队列后,接下来我们就要创建请求对象了,请求的方式可以是StringRequest、JsonArrayRequest或者ImageRequest等。那么这些请求的背后原理是什么呢?我们拿最简单的StringRequest来说,它继承自Request,而Request则是所有请求的父类,所以说如果你要自定义一个网络请求,就应该继承自Request。接下来我们以StringRequest为例,分析一下创建请求对象的源码。

1、构造方法

    /**
     * Creates a new request with the given method.
     *
     * @param method the request {@link Method} to use
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

StringRequest的构造函数需要传入四个参数:

method:请求方法

mUrl:请求url

listener:处理返回结果的监听

errorListener:处理请求错误的监听

构造方法没有做太多的事情,只是记录了传入的参数

2、需要实现的抽象方法

下面我们看一下StringRequest需要实现的抽象方法,在Request类中,定义了两个抽象方法:

    /**
     * Subclasses must implement this to parse the raw network response
     * and return an appropriate response type. This method will be
     * called from a worker thread.  The response will not be delivered
     * if you return null.
     * @param response Response from the network
     * @return The parsed response, or null in the case of an error
     */
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);    

    /**
     * Subclasses must implement this to perform delivery of the parsed
     * response to their listeners.  The given response is guaranteed to
     * be non-null; responses that fail to parse are not delivered.
     * @param response The parsed response returned by
     * {@link #parseNetworkResponse(NetworkResponse)}
     */
    abstract protected void deliverResponse(T response);

抽象方法是所有子类都必须实现的方法,也就是说所有的Request具体实现类都需要实现这两个方法,在StringRequest类中,定义如下:

    @Override
    protected void deliverResponse(String response) {
        if (mListener != null) {
            mListener.onResponse(response);
        }
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

先看deliverResponse方法:它内部调用了mListener.onResponse(response)方法,而这个方法正是我们在写一个请求的时候,添加的listener所重写的onResponse方法,由此可知deliverResponse方法的作用是调用监听处理返回结果。

再来看一下pareNetworkResponse方法:看它的实现应该是解析网络请求的返回结果,将其封装成对应的返回类型。比如说,如果是StringRequest,则将响应包装成String类型;如果是JsonObjectRequest,则将响应包装成JsonObject。所以说,这个方法是针对不同的请求类型而对响应做出不同的处理

总结一下:Request类做的工作主要是初始化一些参数,比如说请求类型、请求的url、错误的回调方法;而它的任一子类重写deliverResponse方法来实现成功的回调,重写parseNetworkResponse()方法来处理响应数据,获得对应的响应类型数据。

3、将请求对象加入到请求队列中

构造完请求对象后,我们需要将请求对象加入到请求队列中来发送请求,来看一下RequestQueue的add()方法:

    /**
     * Adds a Request to the dispatch queue.
     * @param request The request to service
     * @return The passed-in request
     */
    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对象设置请求队列为当前队列
        request.setRequestQueue(this);
        // 加入到mCurrentRequests中
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // Process requests in the order they are added.
        // 给请求对象设置编号并标记其加入到队列中
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // If the request is uncacheable, skip the cache queue and go straight to the network.
        // 如果这个请求是否可以缓存,如果无法缓存,则直接加入到网络请求队列中,并返回
        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();
            if (mWaitingRequests.containsKey(cacheKey)) { // 判断当前是否有相同的请求正在进行,如果有,则将其放入到mWaitingRequests
                // 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);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request); // 将其放入到缓存队列中
            }
            return request;
        }
    }

分析add方法,主要判断一个Request请求是否可以缓存(默认是可以缓存的),如果不可以则直接添加到网络请求队列开始网络通信;如果可以,则进一步判断当前是否有相同的请求正在进行,如果有相同的请求,则让这个请求排队等待,如果没有相同的请求,则直接放进缓存队列中。

4、缓存线程CacheDispatcher

add()方法只是将请求加入到了缓存队列或者网络请求队列中,那么请求是如何发送出去的呢?这就要看缓存线程和网络请求线程的源码了,首先我们看一下CacheDispather的run()方法:

public class CacheDispatcher extends Thread {
    @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

        // Make a blocking call to initialize the cache.
        mCache.initialize();

        Request<?> request;
        while (true) {
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try {
                // Take a request from the queue.
                request = mCacheQueue.take(); // 从缓存队列中取出请求,如果缓存队列中没有请求将会一直阻塞
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
            try {
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) { // 如果请求已经被取消了
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // Attempt to retrieve this item from cache.
                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 it is completely expired, just send it to the network.
                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.
                // 将缓存结果解析为Response对象
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) { // 判断缓存结果是否新鲜,如果新鲜则直接将缓存结果当做最终响应处理
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response);
                } else {
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    // 如果缓存结果需要刷新,则先将缓存结果返回处理,然后再将请求加入到网络请求队列中
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // Mark the response as intermediate.
                    response.intermediate = true;

                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    final Request<?> finalRequest = request;
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(finalRequest);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
            }
        }
    }
}

在run方法中,会构造一个while循环来等待处理缓存队列中的请求,缓存队列是一个PriorityBlockingQueue对象,它是一个带优先级的无界阻塞队列,当队列为空时,调用其take()方法会阻塞线程,当调用其add()方法加入请求后,会唤醒线程继续执行。我们拿到请求后,先判断这个请求是否有对应的缓存结果,如果没有则直接添加到网络请求队列;接着再判断这个缓存结果是否过期了,如果过期则同样地添加到网络请求队列;接下来便是对缓存结果的处理了,我们可以看到,先是把缓存结果包装成NetworkResponse类,然后调用了Request的parseNetworkResponse,这个方法我们之前说过,子类需要重写这个方法来处理响应数据。最后,把处理好的数据post到主线程处理,这里还会判断缓存结果是否新鲜,如果不新鲜还会将请求加入到网络请求队列中。

5、网络请求线程NetworkDispatcher

在Volley中,真正发送网络请求的线程是NetworkDispather,上面提到,请求不能缓存、缓存结果不存在、缓存过期的时候会把请求添加进请求队列,这个时候网络请求线程就会从网络请求队列中取出请求并发送:

public class NetworkDispatcher extends Thread {
    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try {
                // Take a request from the queue.
                request = mQueue.take(); // 从网络请求队列中取出请求
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                request.addMarker("network-queue-take");

                // If the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request); // 调用网络请求类发送请求并获得请求结果
                request.addMarker("network-http-complete");

                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // Parse the response here on the worker thread.
                // 将缓存结果解析为Response对象
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                // TODO: Only update cache metadata instead of entire record for 304s.
                if (request.shouldCache() && response.cacheEntry != null) { // 如果请求需要缓存则将其缓存
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back.
                request.markDelivered(); // 标记这个请求已经处理
                mDelivery.postResponse(request, response); // 处理返回结果
            } catch (VolleyError volleyError) { // 异常处理
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) { // 异常处理
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }
}

网络请求线程主要是不断的从网络请求队列中取出请求并处理,处理网络请求的类是之前创建的BasicNetwork类,获得请求结果后,我们会调用Request的parseNetworkResponse方法来解析返回结果获得对应的Response,然后判断请求是否需要缓存,如果需要则将其缓存到本地文件中,最后,调用mDelivery.postResponse来处理返回结果。这里,我们主要看一下BasicNetwork请求网络的实现:

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = Collections.emptyMap();
            try {
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers); // 调用HttpStack类发送请求并获得返回结果
                StatusLine statusLine = httpResponse.getStatusLine(); // 获得响应状态行
                int statusCode = statusLine.getStatusCode(); // 获得响应状态码

                responseHeaders = convertHeaders(httpResponse.getAllHeaders()); // 获得响应报头
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {

                    Entry entry = request.getCacheEntry();
                    if (entry == null) {
                        return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
                                responseHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }

                    // A HTTP 304 response does not have all header fields. We
                    // have to use the header fields from the cache entry plus
                    // the new ones from the response.
                    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
                    entry.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }
                
                // Handle moved resources
                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                	String newUrl = responseHeaders.get("Location");
                	request.setRedirectUrl(newUrl);
                }

                // Some responses such as 204s do not have content.  We must check.
                if (httpResponse.getEntity() != null) {
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  responseContents = new byte[0];
                }

                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                if (statusCode < 200 || statusCode > 299) { // 网络请求异常
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                        SystemClock.elapsedRealtime() - requestStart); // 返回请求对象
            } catch (SocketTimeoutException e) {
                attemptRetryOnException("socket", request, new TimeoutError());
            } catch (ConnectTimeoutException e) {
                attemptRetryOnException("connection", request, new TimeoutError());
            } catch (MalformedURLException e) {
                throw new RuntimeException("Bad URL " + request.getUrl(), e);
            } catch (IOException e) {
                int statusCode = 0;
                NetworkResponse networkResponse = null;
                if (httpResponse != null) {
                    statusCode = httpResponse.getStatusLine().getStatusCode();
                } else {
                    throw new NoConnectionError(e);
                }
                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 
                		statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                	VolleyLog.e("Request at %s has been redirected to %s", request.getOriginUrl(), request.getUrl());
                } else {
                	VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
                }
                if (responseContents != null) {
                    networkResponse = new NetworkResponse(statusCode, responseContents,
                            responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                    if (statusCode == HttpStatus.SC_UNAUTHORIZED ||
                            statusCode == HttpStatus.SC_FORBIDDEN) {
                        attemptRetryOnException("auth",
                                request, new AuthFailureError(networkResponse));
                    } else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || 
                    			statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                        attemptRetryOnException("redirect",
                                request, new RedirectError(networkResponse));
                    } else {
                        // TODO: Only throw ServerError for 5xx status codes.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(e);
                }
            }
        }
    }

BasicNetwork的performRequest方法主要是调用mHttpStack.performRequest发送网络请求并获得响应,然后针对返回结果做相应的处理,我们知道,在Android2.3以后,mHttpStack是一个HurlStack对象,我们来看一下其performRequest方法的实现:

    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url); // 构造一个URL对象
        HttpURLConnection connection = openConnection(parsedUrl, request); // 使用HttpURLConnection打开连接
        for (String headerName : map.keySet()) { // 添加请求头
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request); // 根据请求方法配置HttpURLConnection的参数
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
        int responseCode = connection.getResponseCode(); // 获得响应状态码
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage()); // 获得响应状态行
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        // 读取响应正文
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }
        // 读取响应报头
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }

HurlStack的performRequest其实就是调用HttpURLConnection的API发送网络请求,并解析请求结果封装成一个Response对象。不了解HttpURLConnection的同学可以参考HttpURLConnction源码分析

6、ExecutorDelivery处理返回结果

从之前的代码,我们知道,获得返回结果后,Volley会调用以下代码处理返回结果:

mDelivery.postResponse(request, response);

mDelivery对象我们在RequestQueue初始化的时候有定义:

        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));

可以看到,这里传入了一个Handler对象,Handler对象使用的Looper是主线程的Looper,也就是说后续Handler中的操作都会在主线程中处理。我们来看一下ExecutorDelivery的定义:

public class ExecutorDelivery implements ResponseDelivery {
    /** Used for posting responses, typically to the main thread. */
    private final Executor mResponsePoster;

    /**
     * Creates a new response delivery interface.
     * @param handler {@link Handler} to post responses on
     */
    public ExecutorDelivery(final Handler handler) {
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command); // 调用Handler的post方法将runnable切换到Handler对应Looper所在的线程中执行
            }
        };
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); // 构造一个ResponseDeliveryRunnable对象,并将其交给mResponsePoster执行
    }
   ...
}

ExecutorDelivery postResponse方法主要是构造了一个构造一个ResponseDeliveryRunnable对象,并将其交给mResponsePoster执行,而mResponsePoster的execute方法会将这个Runnable切换到Handler对应的Looper所在的线程中执行,前面我们看到过,这里的Handler对应的线程是主线程。我们再来看一下ResponseDeliveryRunnable的定义:

    /**
     * A Runnable used for delivering network responses to a listener on the
     * main thread.
     */
    @SuppressWarnings("rawtypes")
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final Runnable mRunnable;

        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mRunnable = runnable;
        }

        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) { // 判断当前请求是否取消
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) { // 判断当前请求是否成功,如果是,则调用mReqeust的deliverResponse方法处理返回结果
                mRequest.deliverResponse(mResponse.result);
            } else { // 如果当前请求异常,则调用mRequest的deliverError方法处理错误
                mRequest.deliverError(mResponse.error);
            }

            // If this is an intermediate response, add a marker, otherwise we're done
            // and the request can be finished.
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) { // 是否需要后续处理,如果有则处理
                mRunnable.run();
            }
       }
    }
}

ResponseDeliveryRunnable的run方法是运行在主线程中的,其主要是根据请求的响应状态来调用不同的方法处理结果,请求成功则调用Request的deliverResponse方法,请求失败则调用Request的deliverError方法,我们之前说过,Request的deliverResponse方法是一个抽象方法,其具体实现是在子类中实现的,以StringRequest为例:

    @Override
    protected void deliverResponse(String response) {
        if (mListener != null) {
            mListener.onResponse(response);
        }
    }

调用监听的onResponse方法来处理返回结果,mListener就是我们在构造StringRequest对象时设置的处理返回结果的监听:

    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

再来看一下处理异常请求的deliverError方法:

    public void deliverError(VolleyError error) {
        if (mErrorListener != null) {
            mErrorListener.onErrorResponse(error);
        }
    }

调用处理异常的监听的onErrorResponse方法来处理异常,这里的mErrorListener就是我们在构造StringRequest对象时设置的处理异常的监听:

    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mIdentifier = createIdentifier(method, url);
        mErrorListener = listener; // 设置处理异常的监听
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

四、总结

    Volley库是Android网络请求中会经常用到的库,它通过对HttpURLConnection(2.3以前用的HttpClient)的封装,将网络请求的API变得更为简洁,并且通过引入4个(默认情况)网络请求线程,将网络请求的工作交给子线程去处理,同时,还引入了一个缓存线程,专门用来处理网络请求的缓存。相对开发者而言,只需要在主线程中调用API将请求加入到请求队列或者缓存队列(是一个带优先级的阻塞队列)中,其他的工作就自动交给缓存线程和网络请求线程处理了。Volley获得请求结果后,通过Handler将处理返回结果的代码切换到主线程中执行,我们在构造请求对象时,只需要设置好对应的监听即可。

    在分析Volley库时,可以明显的感觉到Volley库设计结构非常清晰,大致可以分为以下几个部分:请求以及请求队列、缓存线程、网络请求线程、网络请求类的具体实现、处理返回结果。这些部分基本都相对独立,Volley库将每个细节的工作完全分离,不仅大大降低了程序的耦合性,也让开发者更加容易理解Volley库。

    我们再来看一下之前我们提出的一个好的网络请求框架需要做到的几点要求:

  • 提供给开发者的API应该尽可能简单(Volley库提供给开发者的API很简单)
  • 应该具有可扩展性,开发者通过API调用,可以实现不同的网络请求功能(开发者可以通过API调用配置Volley库,实现不同的功能,并且Volley是开源的,可以随意定制)
  • 基于Android的特性,应该能够做到线程的切换,开发者在主线程中调用API发起网络请求,框架应该能够将请求任务切换到子线程中去执行,并且可以指定处理返回结果的线程(Volley库处理了网络请求的线程切换,开发者不用关心线程切换的问题)
  • 应该有缓存功能,对于不必要的请求直接使用缓存中的数据,这样可以提高性能和节省网络流量(Volley库针对缓存做了处理)
  • 应该具有较高的性能,网络请求可以并发执行,不会因为之前的网络请求未完成而产生阻塞(Volley库一个请求队列默认是有4个网络请求线程来处理,所以具有较高的并发性)

     可以看到,我们之前提出的要求Volley基本都满足了。

猜你喜欢

转载自blog.csdn.net/xiao_nian/article/details/82221928