Android源码分析之理解Volley

0 参考链接

volley官网

手撕 Volley

Volley全方位解析,带你从源码的角度彻底理解

Android Volley完全解析(四),带你从源码的角度理解Volley

Android 面试之常用开源库

HTTP权威指南笔记

Java并发编程:阻塞队列

1 前言

  Volley 是 Google 推出的轻量级 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。其适用场景是数据量小,通信频繁的网络操作。Volley名称的由来: a burst or emission of many things or a large amount at once。

1.1 Volley的优点

  • 物理质量小,Volley.jar(120k),加上自己的封装最多140k;OkHttp需要 okio.jar (80k), okhttp.jar(330k)这2个jar包,总大小差不多400k,加上自己的封装,差不多得410k。
  • 非常适合进行数据量不大,但通信频繁的网络操作。
  • 扩展性强。Volley 中大多是基于接口的设计,可配置性强。
  • 可直接在主线程调用服务端并处理返回结果。
  • 网络请求线程NetworkDispatcher默认开启了4个,可以优化,通过手机CPU数量。
  • 通过使用标准的HTTP缓存机制保持磁盘和内存响应的一致。

1.2 Volley的缺点

  • 默认 Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现。
  • 6.0不支持httpclient了,如果想支持得添加org.apache.http.legacy.jar
  • 对大文件下载 Volley的表现非常糟糕。
  • 只支持http请求。
  • 图片加载性能一般。

1.3 HTTP协议

  • HTTP 协议属于应用层协议,他的基础是 TCP (传输层)/ IP (网络层)协议;
  • 一个HTTP事务由一条请求命令和一个响应结果组成。这种通信通过名为 HTTP 报文(HTTP message)的格式化数据块进行;
  • 从Web客户端发往Web服务器的HTTP报文称为请求报文(request message)。从服务器发往客户端的报文称为响应报文(reponse message),请求报文和响应报文格式类似。HTTP报文包括以下三部分:
    (1)start line起始行:请求报文 包括 method + path + version 响应报文 包括 version + status line。
    (2)headers 消息报头,包含了很多键值对,两者之间用冒号(:)分隔。首部以一个空行结束,这里是我们开发主要会用到的地方,特别是缓存。建议大家还是阅读一下连接中给出的相关文章。
    (3)entity / body消息实体,空行之后就是可选的报文主体了,其中包含了所有类型的数据。起始行和首部都是文本形式且都是结构化的,而主体则不同,主体可以包含任意的二进制数据(图片、视频、音频、软件程序)。当然,主体还可以包含文本。
  • 下面给出一组请求和响应的样例:

1.4 为什么volley是轻量级频次高的网络请求框架?

  volley中为了提高请求处理的速度,采用了字节数组byte[]缓冲池ByteArrayPool进行内存中的数据存储的,如果下载大量的数据,这个存储空间就会溢出,所以不适合大量的数据。但是由于他的这个存储空间是内存中分配的,当存储的时候优是从ByteArrayPool中取出一块已经分配的内存区域, 不必每次存数据都要进行内存分配,而是先查找缓冲池中有无适合的内存区域,如果有,直接拿来用,从而减少内存分配的次数 ,所以他比较适合轻量级频次高的网络数据交互情况。源码请看3.2的(2)中的ByteArrayPool。

2 Volley的使用场景和使用方式

  volley使用及其简单,我们只需要创建一个RequestQueue请求队列,然后往队列里面扔http请求即可,volley会不断从队列里面取出请求然后交给一堆工作线程处理。这里的http请求是通过Request类来封装的,我们只用创建Request对象,然后提供诸如url之类的参数即可。网络操作全部是在子线程中处理的,我们不必担心阻塞UI线程。网络请求的结果会异步返回给我们,我们只需要处理Request的回调即可。

2.1 StringRequest的用法

(1)添加Volley.jar包,添加权限.

<uses-permission android:name="android.permission.INTERNET" />  

(2)使用代码如下:

private void fetchData() {
        String url = "http://apis.juhe.cn/cook/query.php";
        // 1.获取到一个RequestQueue对象
        RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());
        // 2.创建一个StringRequest对象
        StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        Log.d("TAG", response);
                     }
                },new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.e("TAG", error.getMessage(), error);
                    }

                }) {
                     @Override
                     protected Map<String, String> getParams() throws AuthFailureError {
                         Map<String,String> map = new HashMap();
                         map.put("menu","龙眼");
                         map.put("key","8fac966b379367b0e6f0527d634324ee");
                         map.put("dtype","");
                         map.put("pn","");
                         map.put("rn","");
                         map.put("albums","");
                         return map;
                    }
        };

        stringRequest.setTag("MAIN_ACTIVITY");
        // 3.将这个StringRequest对象添加到RequestQueue
        mQueue.add(stringRequest);
    }

(3)结果如下:

{
    "resultcode": "200",
    "reason": "Success",
    "result": {
        "data": [
            {
                "id": "1916",
                "title": "龙眼茶碗蒸",
                "tags": "儿童;创意菜;甜品;小吃;早餐;日本料理",
            }
        ]
    }
}

2.2 添加请求头

/**
 * 有时候需要为Request添加请求头,这时候可以去重写Request的getHeaders方法。
 */
JsonObjectRequest request = new JsonObjectRequest(url, null,resplistener,errlistener) {
        @Override
        public Map getHeaders() throws AuthFailureError {
            Map map = new HashMap();
            map.put("header1","header1_val");
            map.put("header2","header2_val");
            return map;
        }
};

2.3 取消请求

/**
 * 当Activity销毁时,我们可能需要去取消一些网络请求,这时候可以通过如下方式
 * 为属于该Activity的请求全部加上Tag,然后需要销毁的时候调用cancelAll传入tag即可
 */
@Override
protected void onDestroy() {
    mQueue.cancelAll("MAIN_ACTIVITY");
    super.onDestroy();
}

2.4 全局共享RequestQueue

  RequestQueue没有必要每个Activity里面都创建,全局保有一个即可。这时候自然想到使用Application了。我们可以在Application里面创建RequestQueue,并向外暴露get方法。

2.5 定制request

/**
 * 定制XMLRequest
 */
public class XMLRequest extends Request<XmlPullParser> {
    private Response.Listener mListener;

    public XMLRequest(int method, String url, Response.Listener listener,
                      Response.ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

    public XMLRequest(String url, Response.Listener listener,
                      Response.ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected Response parseNetworkResponse(NetworkResponse response) {
        try {
            String xmlString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(new StringReader(xmlString));//将返回数据设置给解析器
            return Response.success(parser, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new VolleyError(e));
        } catch (XmlPullParserException e) {
            return Response.error(new VolleyError(e));
        }
    }

    @Override
    protected void deliverResponse(XmlPullParser response) {
        mListener.onResponse(response);
    }
}
/**
     * 使用例子
     */
    private void fetchXml() {
        RequestQueue mQueue = Volley.newRequestQueue(getApplicationContext());
        String url = "http://flash.weather.com.cn/wmaps/xml/china.xml";
        XMLRequest xmlRequest = new XMLRequest(url,
                new Response.Listener() {
                    @Override
                    public void onResponse(XmlPullParser response) {
                        try {
                            int eventType = response.getEventType();
                            while (eventType != XmlPullParser.END_DOCUMENT) {
                                switch (eventType) {
                                    case XmlPullParser.START_TAG:
                                        String nodeName = response.getName();
                                        if ("city".equals(nodeName)) {
                                            String pName = response.getAttributeValue(0);
                                            Log.d("TAG", "pName is " + pName);
                                        }
                                        break;
                                    case XmlPullParser.END_TAG:
                                        break;
                                    default:
                                        break;
                                }
                                eventType = response.next();
                            }
                        } catch (XmlPullParserException | IOException e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.e("TAG", error.getMessage(), error);
            }
        });
        mQueue.add(xmlRequest);
    }

3 Volley源码解析

3.1 整体架构图

(1)官方给出的流程图
Android源码分析之理解Volley_第1张图片

  • Volley 运行的过程中一共有三种线程,包括UI线程、Cache调度线程和NetWork调度线程池。
  • 请求加入优先级队列;CacheDispatcher线程进行筛选,如果命中分发给UI线程。
  • 丢失分发 NetWork 调度线程池处理,取回后更新Cache并分发给UI线程。
  • 每次请求执行过程始于UI线程, 终于UI线程。

(2)总体设计图
Android源码分析之理解Volley_第2张图片

3.2 请求队列(RequestQueue)的创建

  使用Volley的第一步是通过Volley的newRequestQueue方法得到 一个RequestQueue 队列。指定了硬盘缓存的位置为data/data/package_name/cache/volley/…,Network类(具体实现类是BasicNetwork)封装了请求方式,并且根据当前API版本来选用不同的http工具。最后启动了请求队列。

public class Volley {
    private static final String DEFAULT_CACHE_DIR = "volley";

    public Volley() {
    }

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        // 1.在磁盘上创建一块文件
        File cacheDir = new File(context.getCacheDir(), "volley");
        String userAgent = "volley/0";

        try {
            // 2.设置UserAgent,不知道什么是UserAgent去看HTTP协议
            String network = context.getPackageName();
            PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);
            userAgent = network + "/" + queue.versionCode;
        } catch (NameNotFoundException var6) {
            ;
        }

        // 3.根据SDK版本的不同初始化HTTPStack
        if(stack == null) {
            if(VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        // 4.用HTTPStack初始化BasicNetwork
        BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
        // 5.DiskBasedCache(cacheDir):用第1步创建的文件初始化磁盘缓存
        // 6.用磁盘缓存和NetWork创建我们的请求队列RequestQueue
        RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
        // 7.调用RequestQueue的start方法
        queue1.start();
        return queue1;
    }

    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, (HttpStack)null);
    }
}
  • HttpStack、HurlStack、HttpClientStack 分别是啥;
  • NetWork、BasicNetwork分别是啥;
  • DiskBasedCache是啥;
  • RequestQueue是啥,他的start方法做了什么事情(重点)。

(1) HttpStack、HurlStack、HttpClientStack
  HttpStack是一个接口并且只有一个 performRequest 方法。而HurlStack和HttpClientStack分别是基于HttpUrlConnection和HttpClient对HttpStack的实现,是真正用来访问网络的类。

public interface HttpStack {
    HttpResponse performRequest(Request var1, Map var2) throws IOException, AuthFailureError;
}
public class HurlStack implements HttpStack {
    public HttpResponse performRequest(Request request, Map additionalHeaders) throws IOException, AuthFailureError {

            return response;
        }
    }
}
public class HttpClientStack implements HttpStack {
    public HttpResponse performRequest(Request request, Map additionalHeaders) throws IOException, AuthFailureError {

        return this.mClient.execute(httpRequest);
    }
}

(2) NetWork和BasicNetWork
  同样是接口与实现类的关系,内部封装了一个 HttpStack 用来是想网络请求。接口的方法是一样的。

public interface Network {
    NetworkResponse performRequest(Request var1) throws VolleyError;
}
public class BasicNetwork implements Network {
    protected final HttpStack mHttpStack;
    // volley定义了一个byte[]缓冲池,即ByteArrayPool
    protected final ByteArrayPool mPool;

    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        mHttpStack = httpStack;
        mBaseHttpStack = new AdaptedHttpStack(httpStack);
        mPool = pool;
    }
    public BasicNetwork(HttpStack httpStack) {
       this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    public NetworkResponse performRequest(Request request) throws VolleyError {
        while(true) {
            HttpResponse httpResponse = null;
            Object responseContents = null;
            HashMap responseHeaders = new HashMap();

            try {
                HashMap e = new HashMap();
                this.addCacheHeaders(e, request.getCacheEntry());
                httpResponse = this.mHttpStack.performRequest(request, e);
                StatusLine statusCode2 = httpResponse.getStatusLine();
                int networkResponse1 = statusCode2.getStatusCode();
                Map responseHeaders1 = convertHeaders(httpResponse.getAllHeaders());
                byte[] responseContents1 = this.entityToBytes(httpResponse.getEntity());
                return new NetworkResponse(networkResponse1, responseContents1, responseHeaders1, false);
            } 
        }
    }

    private byte[] inputStreamToBytes(InputStream in, int contentLength)
            throws IOException, ServerError {
        // PoolingByteArrayOutputStream其实就是一个输出流,只不过系统的输出流在使用byte[]时,如果大小不够,会自动扩大byte[]的大小。
        // 而PoolingByteArrayOutputStream则是使用了上面的字节数组缓冲池,从池中获取byte[],使用完毕后再归还。 
        PoolingByteArrayOutputStream bytes =
                new PoolingByteArrayOutputStream(mPool, contentLength);
        byte[] buffer = null;
        try {
            if (in == null) {
                throw new ServerError();
            }
            buffer = mPool.getBuf(1024);
            int count;
            while ((count = in.read(buffer)) != -1) {
                bytes.write(buffer, 0, count);
            }
            return bytes.toByteArray();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                VolleyLog.v("Error occurred when closing InputStream");
            }
            mPool.returnBuf(buffer);
            bytes.close();
        }
    }
 }
/** 
 * 字节数组byte[]缓冲池: 
 * https://blog.csdn.net/u010410408/article/details/52037838
 * byte[]的缓存池,可以指定最大的缓存的byte数目。当缓存的byte数目超过指定的最大值时,以FIFO策略对最早添加的数据进行回收。
 * 主要通过一个元素长度从小到大排序的ArrayList作为byte[]的缓存,另有一个按使用时间先后排序的ArrayList属性用于缓存满时清理元素。 
 */  
public class ByteArrayPool {  
    // 按使用的先后时间顺序排序
    private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();  
    // 按大小顺序排序  
    private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);  
    // 池中所有byte[]的长度之和
    private int mCurrentSize = 0;  
    // 池中单个byte[]的最大长度  
    private final int mSizeLimit;  

    /** Compares buffers by size 比较器,用于排序,按byte[]的字节长度进行排序*/  
    protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {  
        @Override  
        public int compare(byte[] lhs, byte[] rhs) {  
            return lhs.length - rhs.length;  
        }  
    };  

    /** 
     * 创建byte[]缓冲池,并设定池中单个byte[]的最大长度 
     */  
    public ByteArrayPool(int sizeLimit) {  
        mSizeLimit = sizeLimit;  
    }  

    /** 
     * 从池中获取一个可用的byte[],如果没有,就创建一个。参数为想要获取多大长度的byte[] 
     */  
    public synchronized byte[] getBuf(int len) {  
        //遍历按长度排序的池  
        for (int i = 0; i < mBuffersBySize.size(); i++) {  
            byte[] buf = mBuffersBySize.get(i);  
            //如果当前的byte[]的长度大于给定的长度,就返回该byte[]  
            if (buf.length >= len) {  
                //池中所有byte[]的长度之和减去返回出去的byte[]的长度  
                mCurrentSize -= buf.length;  
                //从按顺序排列的池中移除该byte[]  
                mBuffersBySize.remove(i);  
                //从按使用顺序排列的池中移除该byte[]表示该byte[]正在使用中,其他不能再使用该byte[]  
                mBuffersByLastUse.remove(buf);  
                return buf;  
            }  
        }  
        //创建一个新的byte[]  
        return new byte[len];  
    }  

    /** 
     * 当使用完一个byte[]后,将该byte[]返回到池中 
     */  
    public synchronized void returnBuf(byte[] buf) {  
        //如果为空或者超过了设定的单个byte[]的最大长度  那么就不再池中保存该byte[]  
        if (buf == null || buf.length > mSizeLimit) {  
            return;  
        }  
        //在按使用时间顺序排序的池中添加该byte[]  
        mBuffersByLastUse.add(buf);  
        //利用二分查找法,找出在按大小排序的池中该byte[]应该存放的位置  
        int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);  
        //如果找不到,则返回-1  
        if (pos < 0) {  
            //当找不到时,也就是在0位置添加  
            pos = -pos - 1;  
        }  
        mBuffersBySize.add(pos, buf);  
        //增加最大长度  
        mCurrentSize += buf.length;  
        //清理,不能超过字节总数最大值  
        trim();  
    }  

    /** 
     * 当现有字节总数超过了设定的界限,那么需要清理  
     */  
    private synchronized void trim() {  
        while (mCurrentSize > mSizeLimit) {  
            //按照使用的先后顺序来倾全力,最新使用的最先被清除  
            byte[] buf = mBuffersByLastUse.remove(0);  
            //同样在该池中也清除  
            mBuffersBySize.remove(buf);  
            //减小现有字节最大长度  
            mCurrentSize -= buf.length;  
        }  
    }  
}  

(3)DiskBasedCache
  接口Cache里面封装了一个静态内部类Entry,Entry里面定义的这些成员变量跟响应报文headers(消息报头)里面关于缓存的标签是一样的。其中还维护了一个map用来保存消息报头中的key/value,data来保存entity消息实体。
  DiskBasedCache默认大小为5M,但是可以自己配置,内部用了一个LinkedHashMap来保存request的CacheHeader,CacheHeader是为了存储Entity中的header和data的size。LinkedHashMap是为了实现 FIFO的缓存替换策略,在空间不足时向HashMap中put数据,就需要删除一些内容用来保证最新put数据的成功。

public class DiskBasedCache implements Cache {
    private final Map mEntries;
    private long mTotalSize;
    private final File mRootDirectory;
    private final int mMaxCacheSizeInBytes;
    private static final int DEFAULT_DISK_USAGE_BYTES = 5242880;
    private static final float HYSTERESIS_FACTOR = 0.9F;
    private static final int CACHE_VERSION = 2;

    public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        this.mEntries = new LinkedHashMap(16, 0.75F, true);
        this.mTotalSize = 0L;
        this.mRootDirectory = rootDirectory;
        this.mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

    public synchronized void put(String key, Entry entry) {
        // pruneIfNeeded()方法是在put方法的第一行执行的,做的就是这件事。
        // Volley并没有使用最近最少使用算法(LRU),而是使用先进先出算法(FIFO)。
        // DiskBasedCache类剩下的就是一些文件操作了,不需要挨着看了。
        this.pruneIfNeeded(entry.data.length);
        // .......
    }

    private void pruneIfNeeded(int neededSpace) {
        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
            return;
        }

        long before = mTotalSize;
        int prunedFiles = 0;
        long startTime = SystemClock.elapsedRealtime();
        Iterator> iterator = mEntries.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = iterator.next();
            CacheHeader e = entry.getValue();
            boolean deleted = getFileForKey(e.key).delete();
            if (deleted) {
                mTotalSize -= e.size;
            } else {
                //print log
            }
            iterator.remove();
            prunedFiles++;
            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                break;
            }
        }
    }

/**
     * Returns a file object for the given cache key.
     */
    public File getFileForKey(String key) {
        return new File(mRootDirectory, getFilenameForKey(key));// 调用了getFilenameForKey()
    }

    /**
     * 缓存文件名的计算 Creates a pseudo-unique filename for the specified cache key.
     * @param key The key to generate a file name for.
     * @return A pseudo-unique filename.
     */
    private String getFilenameForKey(String key) {
        int firstHalfLength = key.length() / 2;
        String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
        localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
        return localFilename;
    }

    private static class CacheHeader {
        public long size;
        public String key;
        public String etag;
        public long serverDate;
        public long ttl;
        public long softTtl;
        public Map responseHeaders;

        private CacheHeader() {
        }

        public CacheHeader(String key, Entry entry) {
            this.key = key;
            this.size = (long)entry.data.length;
            this.etag = entry.etag;
            this.serverDate = entry.serverDate;
            this.ttl = entry.ttl;
            this.softTtl = entry.softTtl;
            this.responseHeaders = entry.responseHeaders;
        }
    }
}
public interface Cache {
    Cache.Entry get(String var1);
    void put(String var1, Cache.Entry var2);
    void initialize();
    void invalidate(String var1, boolean var2);
    void remove(String var1);
    void clear();

    public static class Entry {
        public byte[] data;
        public String etag;
        public long serverDate;
        public long ttl;
        public long softTtl;
        public Map responseHeaders = Collections.emptyMap();

        public Entry() {
        }
        public boolean isExpired() {
            return this.ttl < System.currentTimeMillis();
        }
        public boolean refreshNeeded() {
            return this.softTtl < System.currentTimeMillis();
        }
    }
}

(4)RequestQueue
  RequestQueue中一共有五个主要的方法,分别是 start()、add()、stop()、cancel()、finish(),重点讲创建——–start()。

  • 成员变量
private AtomicInteger mSequenceGenerator;//序列号生成器
private final Map> mWaitingRequests;//hashmap通过method+url为key,重复request组成的queue为value,的等待队列
private final Set mCurrentRequests;//存储包括正在执行和等待所有的request
private final PriorityBlockingQueue mCacheQueue;//优先级阻塞缓存队列
private final PriorityBlockingQueue mNetworkQueue;//优先级阻塞网络请求队列
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;//网络请求线程池大小
private final Cache mCache;//接口,具体实现由构造器传入
private final Network mNetwork;//接口,具体实现由构造器传入
private final ResponseDelivery mDelivery;//结果分发器
private NetworkDispatcher[] mDispatchers;//网络调度线程数组
private CacheDispatcher mCacheDispatcher;//缓存调度线程
  • 构造函数
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
        this.mSequenceGenerator = new AtomicInteger();
        this.mWaitingRequests = new HashMap();
        this.mCurrentRequests = new HashSet();
        this.mCacheQueue = new PriorityBlockingQueue();
        this.mNetworkQueue = new PriorityBlockingQueue();
        this.mCache = cache;
        this.mNetwork = network;
        this.mDispatchers = new NetworkDispatcher[threadPoolSize];
        this.mDelivery = delivery;
    }

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

    public RequestQueue(Cache cache, Network network) {
        this(cache, network, 4);
    }
  • start():先调用了stop,然后分别创建了CacheDispatcher和4个NetworkDispatcher个对象,然后分别启动之。到这里RequestQueue的任务就完成了,以后有请求都会交由这些dispatcher线程处理。
 /**
     * 启动此队列中的调度程序
     */
    public void start() {
        stop();  // 确保当前正在运行的调度程序已停止
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();// 创建缓存调度程序并启动它

        // 创建网络调度程序(和相应的线程)达到池大小
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

Android源码分析之理解Volley_第3张图片

  • stop()
/**
 - 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();
          }
      }
  }

3.3 请求队列(RequestQueue)的添加

  请求的添加是通过RequestQueue的add完成的,add方法的逻辑是这样的:

  • 将请求加入mCurrentRequests集合
  • 为请求添加序列号
  • 判断是否应该缓存请求,如果不需要,加入网络请求队列
  • 如果有相同请求正在被处理,加入到相同请求等待队列中,否则加入缓存请求队列。
    /**
     * 向请求队列添加请求。
     * @param 请求服务请求
     * @return 传入请求
     * /
    public  Request  add(Request  request){
        // 将请求request关联到当前RequestQueue
        request.setRequestQueue(this);
        // 同步操作将request添加到当前RequestQueue对象的mCurrentRequests中。
        synchronized(mCurrentRequests){
            mCurrentRequests.add(request);
        }

        // 设定request加入队列的次序
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

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

        // 如果请求需要缓存
        synchronized(mWaitingRequests) {
            // 同步操作,根据cacheKey判断有无一样的请求在等待?
            String cacheKey = request.getCacheKey();
            if(mWaitingRequests.containsKey(cacheKey)) {
                // 已经有等待的请求
                Queue> stagedRequests = mWaitingRequests.get(cacheKey);
                if(stagedRequests == null){
                    stagedRequests = new LinkedList >();
                }
                // 加入已有的队列,并存入mWaitingRequests中
                stagedRequests.add(request);
                mWaitingRequests.put(cacheKey,stagedRequests);
                if(VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // 没有等待的请求(即是第一个),存入mWaitingRequests,value为null;同时添加请求到缓存队列
                mWaitingRequests.put(cacheKey,null);
                mCacheQueue.add(request);
            }
            return request;
        }
}

  通过这一方法,请求就被分发到两个队列中分别供CacheDispatcher和NetworkDispatcher处理。类图如下:
Android源码分析之理解Volley_第4张图片

  • Request是啥?Request是一个抽象类,是所有请求的基类。来看Requst的构造器,method、url分别对应Http协议报文里面的method和url。以及Request的类图。

public abstract class Request<T> implements Comparable<Request<T>> {
    private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
    private final MarkerLog mEventLog;
    private final int mMethod;
    private final String mUrl;
    private final int mDefaultTrafficStatsTag;
    private final ErrorListener mErrorListener;
    private Integer mSequence;
    private RequestQueue mRequestQueue;
    private boolean mShouldCache;
    private boolean mCanceled;
    private boolean mResponseDelivered;
    private long mRequestBirthTime;
    private static final long SLOW_REQUEST_THRESHOLD_MS = 3000L;
    private RetryPolicy mRetryPolicy;
    private Entry mCacheEntry;
    private Object mTag;

    public Request(int method, String url, ErrorListener listener) {
        this.mEventLog = MarkerLog.ENABLED?new MarkerLog():null;
        this.mShouldCache = true;
        this.mCanceled = false;
        this.mResponseDelivered = false;
        this.mRequestBirthTime = 0L;
        this.mCacheEntry = null;
        this.mMethod = method;
        this.mUrl = url;
        this.mErrorListener = listener;
        this.setRetryPolicy(new DefaultRetryPolicy());
        this.mDefaultTrafficStatsTag = TextUtils.isEmpty(url)?0:Uri.parse(url).getHost().hashCode();
    }
}

3.4 请求(Request)的处理——CacheDispatcher

  请求的处理是由CacheDispatcher和NetworkDispatcher来完成的,它们的run方法通过一个死循环不断去从各自的队列中取出请求,进行处理,并将结果交由ResponseDelivery。本节以缓存调度CacheDispacher为主,CacheDispacher继承了Thread,是一个线程类,他的run方法是一个 while true死循环,有一个标记位mQuit来退出循环。
  

public class CacheDispatcher extends Thread {
    private static final boolean DEBUG;
    private final BlockingQueue mCacheQueue;// 阻塞队列
    private final BlockingQueue mNetworkQueue;// 阻塞队列
    private final Cache mCache;
    private final ResponseDelivery mDelivery;// 接口用来post response 或者 error
    private volatile boolean mQuit = false;

    static {
        DEBUG = VolleyLog.DEBUG;
    }

    public CacheDispatcher(BlockingQueue cacheQueue, BlockingQueue networkQueue, Cache cache, ResponseDelivery delivery) {
        this.mCacheQueue = cacheQueue;
        this.mNetworkQueue = networkQueue;
        this.mCache = cache;
        this.mDelivery = delivery;
    }

    public void quit() {
        this.mQuit = true;
        this.interrupt();
    }

    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        // 设置进程优先级
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // 进行阻塞调用以初始化缓存
        mCache.initialize();

        Request request;
        // 开启while (true)死循环
        while (true) {
            // 释放先前的请求对象,以避免在排队时排除请求对象
            request = null;
            try {
                // 从队列中获取请求
                request = mCacheQueue.take();
            } catch (InterruptedException e) {
                // 如果中断取消,退出循环
                if (mQuit) {
                    return;
                }
                // 如果没中断取消,只是退出一次循环
                continue;
            }
            try {
                request.addMarker("cache-queue-take");

                // 如果请求已被取消,退出一次循环
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 根据CacheKey查询缓存中有没有对应的Entry对象?
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {
                    // 缓存未命中,发送到网络请求队列,退出一次循环
                    request.addMarker("cache-miss");
                    mNetworkQueue.put(request);
                    continue;
                }

                // 如果它完全过期,重新封装request,送到网络请求队列,退出一次循环
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // OK------>我们有一个缓存命中
                request.addMarker("cache-hit");
                // 解析其数据得到Response,以传送回请求
                Response response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                // 是否需要刷新entry?(这个需要请求的页面指定了Cache-Control或者Last-Modified/Expires等字段,并且Cache-Control的优先级比Expires更高。否则请求一定是过期的)
                if (!entry.refreshNeeded()) {
                    // 完全未到达的缓存命中,不需要刷新entry。mDelivery分发结果将Response传递给request 
                    mDelivery.postResponse(request, response);
                } else {

                    // 软过期缓存命中,需要刷新entry。
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);
                    // 将响应标记为中间(在ExecutorDelivery中处理)
                    response.intermediate = true;
                    // 先将Response先将结果返回到request,再将重新封装的请求发送到网络请求队列
                    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());
            }
        }
    }
}

Android源码分析之理解Volley_第5张图片

3.5 请求(Request)的处理——NetworkDispatcher

public class NetworkDispatcher extends Thread {
    private final BlockingQueue mQueue;// 阻塞队列
    private final Network mNetwork;// NetWork接口
    private final Cache mCache;//  Cache接口
    private final ResponseDelivery mDelivery;// 结果分发器
    private volatile boolean mQuit = false;

    public NetworkDispatcher(BlockingQueue queue, Network network, Cache cache, ResponseDelivery delivery) {
        this.mQueue = queue;
        this.mNetwork = network;
        this.mCache = cache;
        this.mDelivery = delivery;
    }

    public void quit() {
        this.mQuit = true;
        this.interrupt();
    }

    public void run() {
        // 设置进程的优先级,声明一个请求
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request request;
        // 开启while (true) 循环 
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            // 释放先前的请求对象,以避免在排队时排除请求对象
            request = null;
            try {
                // 从队列中获取请求
                request = mQueue.take();
            } catch (InterruptedException e) {
                // 如果中断取消,退出循环
                if (mQuit) {
                    return;
                }
                // 如果没中断取消,只是退出一次循环
                continue;
            }

            try {
                request.addMarker("network-queue-take");
                // 如果请求已被取消,退出一次循环
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                // 没被取消,addTrafficStatsTag()统计一下请求的流量
                addTrafficStatsTag(request);

                // 核心:调用Network的performRequest执行网络请求
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // 是否返回304状态码,并且我们已经发送了一个响应?
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    // 是返回304,退出一次循环
                    request.finish("not-modified");
                    continue;
                }

                // request解析响应的networkResponse
                Response response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // request需要缓存,而且response不为空?
                if (request.shouldCache() && response.cacheEntry != null) {
                    // 是,将response写入存入Cache
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // 不是,将response传递给request,标记request为已交付。结束一次循环。
                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);
            }
        }
    }
}

  这里的逻辑跟CacheDispatcher类似,也是构造Response对象,然后交由ResponseDelivery处理,但是这里的Response对象是通过NetworkResponse转化的,而这个NetworkResponse是从网络获取的,这里最核心的一行代码就是:

NetworkResponse networkResponse = mNetwork.performRequest(request);
public interface Network {
    NetworkResponse performRequest(Request var1) throws VolleyError;
}
public class BasicNetwork implements Network {
    protected static final boolean DEBUG;
    private static int SLOW_REQUEST_THRESHOLD_MS;// 最长请求时间
    private static int DEFAULT_POOL_SIZE;// 线程池大小
    protected final HttpStack mHttpStack;// HttpStack接口,真正执行网络请求的类
    protected final ByteArrayPool mPool;// 二进制数组池,一个工具类

    static {
        DEBUG = VolleyLog.DEBUG;
        SLOW_REQUEST_THRESHOLD_MS = 3000;
        DEFAULT_POOL_SIZE = 4096;
    }

    public BasicNetwork(HttpStack httpStack) {
        this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
    }

    public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {
        this.mHttpStack = httpStack;
        this.mPool = pool;
    }

    public NetworkResponse performRequest(Request request) throws VolleyError {
        // 记录系统从启动到现在的时间
        long requestStart = SystemClock.elapsedRealtime();
        // 开启while (true)循环
        while (true) {
            // 声明变量httpResponse、responseContents、responseHeaders
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map responseHeaders = Collections.emptyMap();
            try {
                // 从request中获取CacheEntry,封装成一个header
                Map headers = new HashMap();
                addCacheHeaders(headers, request.getCacheEntry());
                // 核心:调用HttpStack的performRequest访问网络,获取httpResponse响应
                httpResponse = mHttpStack.performRequest(request, headers);
                // 获取状态码和响应Headers
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());

                // 状态码是否是304
                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);
                    }

                    // HTTP 304响应没有所有标题字段。我们必须使用缓存条目中的头字段添加新的NetworkResponse。
                    entry.responseHeaders.putAll(responseHeaders);
                    // 是,请求完成,返回NetworkResponse
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // 不是
                // 处理修改的资源
                if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
                    String newUrl = responseHeaders.get("Location");
                    request.setRedirectUrl(newUrl);
                }

                // 判断httpResponse有没有内容,204s没内容
                if (httpResponse.getEntity() != null) {
                    responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                    // 添加0字节响应作为无内容请求
                    responseContents = new byte[0];
                }
                // 计算时间,打印log
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);

                // 状态码statusCode < 200 || statusCode > 299?
                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();// 是,抛出异常
                }

                // 不是,请求成功,返回最终的NetworkResponse
                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);
                }
            }
        }
    }
}

  这里最核心的是这一句:

httpResponse = mHttpStack.performRequest(request, headers);

  最终的网络执行还是在 HttpStack 中,而我们又知道,HttpStack 是一个接口,在 Volley 中他有两种实现:基于 HttpClient的HttpClientStack;基于 HttpURLConnection的HurlStack。

public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
    }
public class HurlStack implements HttpStack {
    private static final String HEADER_CONTENT_TYPE = "Content-Type";
    private final HurlStack.UrlRewriter mUrlRewriter;
    private final SSLSocketFactory mSslSocketFactory;

    public HurlStack() {
        this((HurlStack.UrlRewriter)null);
    }

    public HurlStack(HurlStack.UrlRewriter urlRewriter) {
        this(urlRewriter, (SSLSocketFactory)null);
    }

    public HurlStack(HurlStack.UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) {
        this.mUrlRewriter = urlRewriter;
        this.mSslSocketFactory = sslSocketFactory;
    }

     @Override
    public HttpResponse performRequest(Request request, Map additionalHeaders)
            throws IOException, AuthFailureError {
        // 从request中提取url
        String url = request.getUrl();
        HashMap map = new HashMap();
        // 从request中提取headers(消息报头)与参数中给的additionalHeaders,存入到map中
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            // mUrlRewriter不为空时,拦截替换url
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        // 用url创建一个URL对象
        URL parsedUrl = new URL(url);
        // 调用openConnection()建立连接
        HttpURLConnection connection = openConnection(parsedUrl, request);
        // 将map中的键值对依次添加到connection中
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        // 根据请求的method(get、post)设置connection的method和body(请求实体)
        setConnectionParametersForRequest(connection, request);
        // 使用HttpURLConnection中的数据初始化HttpResponse,默认是HTTP 1.1
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);

        // 获取响应码
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 向呼叫者发出连接发生错误的信号。抛出新的IOException(“无法从HttpUrlConnection检索响应代码”)
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        // 获取response,设置消息实体
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }
        for (Entry> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                // 添加消息报头
                response.addHeader(h);
            }
        }
        // 将HttpResponse的response返回
        return response;
    }

    protected HttpURLConnection createConnection(URL url) throws IOException {
        return (HttpURLConnection)url.openConnection();
    }

    private HttpURLConnection openConnection(URL url, Request request) throws IOException {
        HttpURLConnection connection = this.createConnection(url);
        int timeoutMs = request.getTimeoutMs();
        connection.setConnectTimeout(timeoutMs);
        connection.setReadTimeout(timeoutMs);
        connection.setUseCaches(false);
        connection.setDoInput(true);
        if("https".equals(url.getProtocol()) && this.mSslSocketFactory != null) {
         ((HttpsURLConnection)connection).setSSLSocketFactory(this.mSslSocketFactory);
        }
        return connection;
    }

    static void setConnectionParametersForRequest(HttpURLConnection connection, Request request) throws IOException, AuthFailureError {
        switch(request.getMethod()) {
        case -1:
            byte[] postBody = request.getPostBody();
            if(postBody != null) {
                connection.setDoOutput(true);
                connection.setRequestMethod("POST");
                connection.addRequestProperty("Content-Type", request.getPostBodyContentType());
                DataOutputStream out = new DataOutputStream(connection.getOutputStream());
                out.write(postBody);
                out.close();
            }
            break;
        case 0:
            connection.setRequestMethod("GET");
            break;
        case 1:
            connection.setRequestMethod("POST");
            addBodyIfExists(connection, request);
            break;
        case 2:
            connection.setRequestMethod("PUT");
            addBodyIfExists(connection, request);
            break;
        case 3:
            connection.setRequestMethod("DELETE");
            break;
        default:
            throw new IllegalStateException("Unknown method type.");
        }
    }
}

Android源码分析之理解Volley_第6张图片

3.6 请求结果的分发与处理——ResponseDelivery

  请求结果的分发处理是由ResponseDelivery实现类ExecutorDelivery完成的,ExecutorDelivery是在RequestQueue的构造器中被创建的,并且绑定了UI线程的Looper。

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
public interface ResponseDelivery {
    void postResponse(Request var1, Response var2);

    void postResponse(Request var1, Response var2, Runnable var3);

    void postError(Request var1, VolleyError var2);
}

  ExcutorDelivery对应的构造函数,内部有个自定义Executor,传入了一个主线程的Looper创建的 Handler,然后有一个Executor的成员变量,用来向UI线程传递 response。

public class ExecutorDelivery implements ResponseDelivery {
    private final Executor mResponsePoster;

    public ExecutorDelivery(final Handler handler) {
        this.mResponsePoster = new Executor() {
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    public ExecutorDelivery(Executor executor) {
        this.mResponsePoster = executor;
    }

    /**
     * ExcutorDelivery最重要的方法就是PostResponse了
     */
    public void postResponse(Request request, Response response) {
        this.postResponse(request, response, (Runnable)null);
    }

    public void postResponse(Request request, Response response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
    }

    public void postError(Request request, VolleyError error) {
        request.addMarker("post-error");
        Response response = Response.error(error);
        this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, (Runnable)null));
    }

    /**
     * 最终执行的是ResponseDeliveryRunnable这个Runnable
     */ 
    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) {
            this.mRequest = request;
            this.mResponse = response;
            this.mRunnable = runnable;
        }

        public void run() {
            // 如果此请求已取消,请求完成不再传递数据
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            if (mResponse.isSuccess()) {
                // 响应成功,调用request的deliverResponse方法分发结果(这是个抽象方法,需要子类实现)
                mRequest.deliverResponse(mResponse.result);
            } else {
                // 响应失败,调用request的deliverError方法分发错误
                mRequest.deliverError(mResponse.error);
            }

            // 处理响应需要刷新情况(CacheDispacher的run()方法的软过期缓存命中,需要刷新entry情况)
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }

            // mRunnable不为空,则执行
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
    }
}

Android源码分析之理解Volley_第7张图片

  响应分发重担就传递到了request的身上:

public abstract class Request<T> implements Comparable<Request<T>> {
    private final ErrorListener mErrorListener;

    protected abstract void deliverResponse(T var1);

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

  deliverResponse是一个抽象类,StringRequest里面是这样实现的。Listener 是在我们使用的时候才创建传入到 request 中的,接口中的回调函数自然也是开发者来实现。也就是说我们添加到 requestqueue 中的请求经过一系列的处理得到最终的 response 或者 error,交给开发者来处理。到这里结果分发就分析结束了。

public class StringRequest extends Request<String> {
    private final Listener mListener;

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

    public StringRequest(String url, Listener listener, ErrorListener errorListener) {
        this(0, url, listener, errorListener);
    }

    protected void deliverResponse(String response) {
        this.mListener.onResponse(response);
    }

    protected Response parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException var4) {
            parsed = new String(response.data);
        }

        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}

  都是调用的接口的方法,那谁来实现接口的方法呢。当然是使用 Volley 的我们,请看:

StringRequest stringRequest = new StringRequest("http://www.jianshu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });

3.7 请求完成

  调用RequestQueue#stop可以终止整个请求队列,并终止缓存请求线程与网络请求线程:

public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();
        }
        for (int i = 0; i < mDispatchers.length; i++) {
            if (mDispatchers[i] != null) {
                mDispatchers[i].quit();
            }
        }
    }

  XXXDispatcher的quit方法会修改mQuit变量并调用interrupt使线程抛Interrupt异常,而Dispatcher捕获到异常后会判断mQuit变量最终while循环结束,线程退出。

public void run() {
        Process.setThreadPriority(10);

        while(true) {
            Request request;
            while(true) {
                try {
                    request = (Request)this.mQueue.take();
                    break;
                } catch (InterruptedException var4) {
                    if(this.mQuit) {
                        return;
                    }
                }
            }
}

Android源码分析之理解Volley_第8张图片

3.8 请求取消

  调用Request的cancel可以取消一个请求。cancel方法很简单,仅将mCanceled变量置为true。而CacheDispatcher/NetworkDispatcher的run方法中在取到一个Request后会判断是否请求取消了:

public void run() {
    Process.setThreadPriority(10);
    while(true) {
        Request request;
        if(request.isCanceled()) {
            request.finish("network-discard-cancelled");
        } else {
        }
    }
}

  如果请求取消就调用Request#finish,finish方法内部将调用与之绑定的请求队列的finish方法,该方法内部会将请求对象在队列中移除。
  Android源码分析之理解Volley_第9张图片

3.9 总结

  到这里 Volley 的主要流程就走了一遍了,继续加油。

4 实践

4.1 自定义网络请求框架实现Json文本请求

  参考了github上某个开源项目,在此基础上重新封装修改,实现了此类Volley网络请求框架,目前第一版本支持Json和String文本请求,支持扩展。
  
(1)使用方法,在 gradle 中引入:

compile 'com.guan.codelibs:volleyhttp:1.0.2'
public void login(View view) {
        String url = "http://apis.juhe.cn/cook/query.php";
        String APPKEY ="8fac966b379367b0e6f0527d634324ee";
        Map<String,String> map = new HashMap();//请求参数
        map.put("menu","龙眼");//需要查询的菜谱名
        map.put("key",APPKEY);//应用APPKEY(应用详细页查询)
        map.put("dtype","");
        map.put("pn","");
        map.put("rn","");
        map.put("albums","");

        Volley.sendRequest(map, url,
                new Response.Listener<String>() {
                    @Override
                    public void onSuccess(String response) {
                        Log.e("tag", "onSuccess:" + response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onError(String error) {
                        Log.e("tag", "onError:" + error);
                    }
                });
    }

(2)源码地址
  github项目地址:CodeLibs/lib-volleyhttp/
  
(3)类图分析

Android源码分析之理解Volley_第10张图片
  
(4)使用到的知识点

  • 泛型
  • 请求队列
  • 阻塞队列
  • 线程池拒绝策略

(5)设计模式

  • 模板方法模式:实现IHttpService接口方法。
  • 单例模式
  • 策略模式:HttpTask对应Context类。
  • 生产者消费者模式:生产一个网络请求,消费(处理)一个网络请求。

4.2 实现基本的下载功能

  
(1)使用方法

compile 'com.guan.codelibs:volleyhttp:1.0.2'
public void down(View view) {
        DownFileRequest.downRequest("http://gdown.baidu.com/data/wisegame/8be18d2c0dc8a9c9/WPSOffice_177.apk", new IDownloadResponse() {
            @Override
            public void onDownloadStatusChanged(DownloadItemInfo downloadItemInfo) {
                Log.e("tag", "下载状态:" + downloadItemInfo.getStatus());
            }

            @Override
            public void onTotalLengthReceived(DownloadItemInfo downloadItemInfo) {
                Log.e("tag", "下载接收");
            }

            @Override
            public void onCurrentSizeChanged(DownloadItemInfo downloadItemInfo, double downLenth, long speed) {
                Log.e("tag", "下载长度:" + downLenth + "-----速度:" + speed/1000 +"kb/s");
            }

            @Override
            public void onDownloadSuccess(DownloadItemInfo downloadItemInfo) {
                Log.e("tag", "下载成功" + "路径:" + downloadItemInfo.getFilePath() + "-----url:" + downloadItemInfo.getUrl());
            }

            @Override
            public void onDownloadPause(DownloadItemInfo downloadItemInfo) {
                Log.e("tag", "下载暂停:" + downloadItemInfo.getStatus());
            }

            @Override
            public void onDownloadError(DownloadItemInfo downloadItemInfo, int var2, String var3) {
                Log.e("tag", "下载出错");
            }
        });
    }

(2)类图分析

Android源码分析之理解Volley_第11张图片

5 面试题

5.1 Volley的磁盘缓存

  Volley 会将每次通过网络请求到的数据,采用FileOutputStream,写入到本地的文件中。这个缓存文件,是声明在一个SD卡文件夹中的(也可以是getCacheFile())。如果不停的请求网络数据,这个缓存文件夹将无限制的增大,最终达到SD卡容量时,会发生无法写入的异常(因为存储空间满了)?
  翻看代码 #3Volley源码解析#3.2请求队列(RequestQueue)的创建#(3)DiskBasedCache #put(String key, Entry entry)#pruneIfNeeded()。其中mMaxCacheSizeInBytes是构造方法传入的一个缓存文件夹的大小,如果不传默认是5M的大小。

private void pruneIfNeeded(int neededSpace) {
    if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
        return;
    }

    long before = mTotalSize;
    int prunedFiles = 0;
    long startTime = SystemClock.elapsedRealtime();
    Iterator> iterator = mEntries.entrySet().iterator();
    while (iterator.hasNext()) {
        Map.Entry entry = iterator.next();
        CacheHeader e = entry.getValue();
        // 调用了getFileForKey()
        boolean deleted = getFileForKey(e.key).delete();
        if (deleted) {
            mTotalSize -= e.size;
        } else {
            //print log
        }
        iterator.remove();
        prunedFiles++;
        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
            break;
        }
    }
}

  通过这个方法可以发现,每当被调用时会传入一个neededSpace,也就是需要申请的磁盘大小(即要新缓存的那个文件所需大小)。首先会判断如果这个neededSpace申请成功以后是否会超过最大可用容量,如果会超过,则通过遍历本地已经保存的缓存文件的header(header中包含了缓存文件的缓存有效期、占用大小等信息)去删除文件,直到可用容量不大于声明的缓存文件夹的大小。

5.2 Volley缓存命中率的优化

  如果让你去设计Volley的缓存功能,你要如何增大它的命中率?
  还是上面的代码,在缓存内容可能超过缓存文件夹的大小时,删除的逻辑是直接遍历header删除。这个时候删除的文件有可能是我们上一次请求时刚刚保存下来的,屁股都还没坐稳呢,现在立即删掉,不好啊。如果遍历的时候,判断一下,首先删除超过缓存有效期的(过期缓存),其次按照LRU算法,删除最久未使用的,更合适。

5.3 Volley缓存文件名的计算

  为什么getFilenameForKey()会要把一个key分成两部分,分别求hashCode,最后又做拼接?

/**
     * Returns a file object for the given cache key.
     */
    public File getFileForKey(String key) {
        return new File(mRootDirectory, getFilenameForKey(key));// 调用getFilenameForKey()
    }

/**
     * Creates a pseudo-unique filename for the specified cache key.
     * @param key The key to generate a file name for.
     * @return A pseudo-unique filename.
     */
    private String getFilenameForKey(String key) {
        int firstHalfLength = key.length() / 2;
        String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
        localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
        return localFilename;
    }

  先来看一下 String#hashCode() 的实现。String的hashcode是根据字符数组中每个位置的字母的int值再加上上次hash值乘以31,这种算法求出来的,至于为什么是31,我也不清楚。但是可以肯定一点,hashcode并不是唯一的。

@Override public int hashCode() {
    int hash = hashCode;
    if (hash == 0) {
        if (count == 0) {
            return 0;
        }
        final int end = count + offset;
        final char[] chars = value;
        for (int i = offset; i < end; ++i) {
            hash = 31*hash + chars[i];
        }
        hashCode = hash;
    }
    return hash;
}

  目的是为了尽可能避免hashcode重复造成的文件名重复(求两次hash两次都与另一个url重复的概率总要比一次重复的概率小)。不过概率小并不代表不存在,但是Java计算hashcode的速度是很快的,应该是在效率和安全性上取舍的结果。

你可能感兴趣的:(源码分析)