Android Volley源码解析

写在前面

最近在整理之前写的项目,当时开发的时候对于Volley没有深入的研究过,这次拿出来进行下源码分析。虽然Volley框架目前已经有些过时,但是里面的思想对于个人来说还是很有成长价值。


整体过程描述

我们使用Volley进行联网获取接口数据的整体流程是怎样的呢?我们很有必要在深入代码细节讲解之前从整体上把握下,这样将有助于我们更好的理解volley的源码

首先,我们要使用volley进行网络访问获取数据,那么volley就需要先做一些初始化的工作:

  1. 创建缓存文件“volley”,并设置最大为5M的大小。

  2. 根据我们手机当前版本,选择合适的网络操作类(httpClient、httpUrlConnection)进行网络操作类的一系列的封装和初始化。

  3. 开启一个缓存线程和四个网络线程,对任务请求队列一直进行监听,当有任务到来的时候,它们就会按照逻辑执行。对于缓存线程而言,它需要初始化缓存文件夹volley,将当前文件夹中的缓存加载到内存;对于网络线程而言,它还构建了从主线程到子线程通讯的通道(Handler)。

以上就是volley的初始化做的主要工作了。之后,我们肯定要实例化一个request类,将这个request添加到上述讲到的任务等待队列进行执行。那么后续就是缓存线程和网络线程之间的操作了:

  1. 首先判断该请求是否设置了缓存选项(默认为应该缓存),如果设置了不缓存,那么该请求被放置到网络访问等待执行序列。如果是可以进行缓存,那再看是否是第一次发出,如果是第一次发出,那么volley将会对该请求缓存。

  2. 如果1中的request属于不缓存的request,那么在网络线程将会在网络访问等待执行序列中取出request进行执行,那么就可以得到网络访问之后的结果。其结果通过Handler提供给request抽象类的实现类,例如StringRequest,我们就可以在UI线程中获取到网络执行之后的结果了;

  3. 如果1中的request属于可缓存:

    • 但是是第一次访问的情况的话,那么在缓存线程会将该request直接加入到网络访问等待执行序列。之后得到结果之后首先进行缓存,之后通过handler返回结果给UI线程。
    • 如果request不是第一次访问,那么要检查缓存中保存的该request是否过期,是否“新鲜”;如果已经过期或者不“新鲜”,那么会将该request直接加入到网络访问等待执行序列;反之的话,将使用缓存中保存的数据返回给UI线程。

文字也许看着比较累,下面我们使用图例来展示下Volley的初始化和整体流程

Volley初始化

Volley整体流程图


Volley源码细节分析

初始调用

首先对于Volley的使用大家应该比较熟悉了,我们分析源码就从Volley暴露出来的接口开始即可,下面是一个很简单的Volley请求代码:

RequestQueue requestQueue = Volley.newRequestQueue(MainActivity.this);

        StringRequest stringRequest = new StringRequest("https://www.baidu.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);
            }
        });
        requestQueue.add(stringRequest);

暴露的Volley类

下面让我们看下Volley具体的执行过程:
对于第一行代码而言,最重要的就是volley的newRequestQueue方法了,我们看下源码:

/** Default on-disk cache directory. */
private static final String DEFAULT_CACHE_DIR = "volley";


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

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//初始化名字为“Volley”的缓存文件
        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;
        } catch (NameNotFoundException e) {
        }
        //初始化网络访问类
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
            //当前SDK版本大于9,则内部由封装HttpUrlConnection类的HurlStack实现
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                //低版本SDK,则由封装HttpClient类的HttpClientStack实现
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
//进一步封装网络请求类,得到BasicNetwork类实例,该类实例用于进行处理网络访问
        Network network = new BasicNetwork(stack);

        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();

        return queue;
    }
HurlStack && HttpClientStack介绍

上述根据SDK版本不同而进行区分使用的实质在于使用网络访问类的不同,即HttpClient类与HttpUrlConnection类使用差别,本文不展开讲述,可自行查阅。

DiskBasedCache类介绍

下面我们查看第35行的源码,首先是DiskBasedCache构造函数:

/** Default maximum disk usage in bytes. */
    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

public DiskBasedCache(File rootDirectory) {
        this(rootDirectory, DEFAULT_DISK_USAGE_BYTES);
    }

    public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) {
        mRootDirectory = rootDirectory;
        mMaxCacheSizeInBytes = maxCacheSizeInBytes;
    }

由上可知:缓存文件最大可支持5M的大小,并且实质上DiskBasedCache类就是Volley中唯一实现Cache接口的缓存类

 //根据URL得到缓存文件夹中的entry数据
 @Override
    public synchronized Entry get(String key) {
        CacheHeader entry = mEntries.get(key);
        // if the entry does not exist, return.
        if (entry == null) {
            return null;
        }

        File file = getFileForKey(key);
        CountingInputStream cis = null;
        try {
            cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file)));
            CacheHeader.readHeader(cis); // eat header
            byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead));
            return entry.toCacheEntry(data);
        } catch (IOException e) {
            VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
            remove(key);
            return null;
        } finally {
            if (cis != null) {
                try {
                    cis.close();
                } catch (IOException ioe) {
                    return null;
                }
            }
        }
    }
//将本地缓存文件加载到内存
    @Override
    public synchronized void initialize() {
        if (!mRootDirectory.exists()) {
            if (!mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
            }
            return;
        }

        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            BufferedInputStream fis = null;
            try {
                fis = new BufferedInputStream(new FileInputStream(file));
                CacheHeader entry = CacheHeader.readHeader(fis);
                entry.size = file.length();
                putEntry(entry.key, entry);
            } catch (IOException e) {
                if (file != null) {
                   file.delete();
                }
            } finally {
                try {
                    if (fis != null) {
                        fis.close();
                    }
                } catch (IOException ignored) { }
            }
        }
    }
    //向缓存中写入数据(根据URL创建缓存文件,将entry实体进行保存)
    @Override
    public synchronized void put(String key, Entry entry) {
        pruneIfNeeded(entry.data.length);
        File file = getFileForKey(key);
        try {
            BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file));
            CacheHeader e = new CacheHeader(key, entry);
            boolean success = e.writeHeader(fos);
            if (!success) {
                fos.close();
                VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
                throw new IOException();
            }
            fos.write(entry.data);
            fos.close();
            putEntry(key, e);
            return;
        } catch (IOException e) {
        }
        boolean deleted = file.delete();
        if (!deleted) {
            VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
        }
    }
    //根据缓存key值进行缓存子文件名称定义:
    //1.对url前半段的串进行hash计算
    //2.对url后半段的串进行hash计算
    //cacheFileName = 1+2的值
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;
    }

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

RequestQueue类介绍

接下来要讲的就是RequestQueue类和他的start方法了:

//设定网络线程个数
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
//存放request的缓存型优先堵塞队列
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =
        new PriorityBlockingQueue<Request<?>>();

    //存放request的网络执行型优先堵塞队列
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<Request<?>>();


public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
//ExecutorDelivery类实现了ResponseDelivery接口
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));

public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // 实例化缓存线程,并执行
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // 实例化4个网络线程并开始执行
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

RequestQueue这个类除了start方法比较重要,另外一个重要的地方在哪里呢?我们还记得在activity中最后需要把具体的request加入到RequestQueue中吧?让我们来看下RequestQueue类的add方法实现

//Request任务等待序列,存储的是具有相同url请求地址的Request对象
private final Map<String, Queue<Request<?>>> mWaitingRequests =
            new HashMap<String, Queue<Request<?>>>();
//当前正在执行的Request序列集合
    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
    //缓存线程的核心处理数据源:Request缓存优先阻塞队列
    private final PriorityBlockingQueue<Request<?>> mCacheQueue = 
    new PriorityBlockingQueue<Request<?>>();
//网络线程的核心处理数据源:Request的缓存优先阻塞队列
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
        new PriorityBlockingQueue<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.setRequestQueue(this);
        synchronized (mCurrentRequests) {
       //将新加进来的request添加到当前任务集合
            mCurrentRequests.add(request);
        }

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

        // 如果request要求不进行缓存,
        //则直接将该request加入"网络型"优先阻塞队列
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            //对于正在执行的request的时候又加入了同样的request请求的话
            //会将后续的request添加到等待序列,直到当前request执行完毕
            //执行完毕之后,会调用finish方法,将该类request清空
            if (mWaitingRequests.containsKey(cacheKey)) {
                // 属于同一类request,则统一放在一个队列里(LinkedList)
                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 {
                // 当前等待序列并没有返现有执行该类request
                //则将该request加入等待序列,并加入到“缓存型”优先阻塞队列,等待缓存线程来执行。
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }
            return request;
        }
    }

<T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
        //将执行完毕的request在当前执行request集合中清除
            mCurrentRequests.remove(request);
        }
        synchronized (mFinishedListeners) {
          for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
          }
        }

        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);
                if (waitingRequests != null) {
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    mCacheQueue.addAll(waitingRequests);
                }
            }
        }
    }

add方法思路:
1. 查看request是否需要被缓存,如果不需要则直接交给网络优先阻塞队列去执行
2. 对于多个相同request的请求的操作处理:

  • 将加进来的request放在一个hashSet的集合中,该set集合表示肯定要执行的request
  • 接着request会被加到一个waitingRequest,类型为hashMap。key为request的url,value为linkedList,linkedlist保存相同key的request。

    以上保证了相同的request不会执行多次,只是会执行一次。

  • 当前一个request执行完毕,会将set集合中的request该实例删除,并将根据url删除waitingRequest中对应的value值。最后将linkedList中的所有request添加进入缓存阻塞队列

ExecutorDelivery类介绍

在上述代码中比较好理解,我们先来看下ExecutorDelivery类,ExecutorDelivery类是一个很重要的类,它主要实现了缓存线程与UI线程切换与信息发送、网络线程与UI线程切换与信息发送的功能:

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

    //ExecutorDelivery构造方法之一,主要完成子线程与UI线程的切换
    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);
            }
        };
    }

    /**
     * Creates a new response delivery interface, mockable version
     * for testing.
     * @param executor For running delivery tasks
     */
    public ExecutorDelivery(Executor executor) {
        mResponsePoster = executor;
    }

//以下Override标识的三个方法,为ResponseDelivery接口的实现方法
    @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));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        Response<?> response = Response.error(error);
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
    }

    /**
     * A Runnable used for delivering network responses to a listener on the
     * main thread.
     */
    @SuppressWarnings("rawtypes")
    //该Runnable对象实质上已经是被切换到UI线程执行
    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;
            }

            // 将调用Request类的具体实现类方法deliverResponse,将结果返回到最上层
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                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 {
            //普通request请求将在这里被清除出当前任务执行序列和任务等待序列
            //Request.finish最后调用RequestQueue的finish方法
            //具体查看上述RequestQueue中finish源码
                mRequest.finish("done");
            }

            // If we have been provided a post-delivery runnable, run it.
            if (mRunnable != null) {
                mRunnable.run();
            }
       }
    }
}

上述代码的核心也就是使用handler进行线程的切换了,如果对于handler机制不太了解,可以查看我的这篇handler的博文:Android消息机制讲解

需要指出的是第73和第75行代码中执行的deliverResponse()和deliverError()方法。实质上这两个方法都是抽象类Request中的方法:

    //具体实现Request抽象类的request实现该方法(例如StringRequest)
    abstract protected void deliverResponse(T response);

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

随便查找一个实现Request抽象类的实体类,比如StringRequest,它的deliverResponse()实现如下:

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

到了这里,是不是感觉代码变得有点眼熟?我们在创建一个StringRequest实例的时候,构造方法实现中就使用到了mListener和mErrorListener。这样我们就可以得到我们发送request之后得到的结果了。

介绍完ExecutorDelivery类,再让我们回到RequestQueue类的start方法上:接下来的关注核心就是两个类型的线程了:即缓存线程CacheDispatcher类和网络线程NetworkDispatcher类,两个类都继承自Thread类,下面我们依次进行源码剖析:

CacheDispatcher类介绍

CacheDispatcher继承了Thread类,主要工作就是加载本地缓存文件,将UI线程请求的Request的情况进行筛选比较,来决定Request的走向:可以是将请求加入到网络执行等待队列去执行、也可以是拿出缓存中该Request的应答response直接返回UI线程等等…我们接下来看他的run方法的实现(已经去掉不重要的部分代码):

 @Override
    public void run() {
        //设置当前线程级别:后台线程
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // 将本地的缓存文件加载到内存
        mCache.initialize();

        while (true) {
            try {
                // 一直从“缓存型”优先阻塞队列中获取请求
                final Request<?> request = mCacheQueue.take();
                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                //如果执行过程中取消该request,则将request在当前执行序列中除去并且在等待序列中除去具有相同URL的队列
                //(在讲解完毕网络线程源码之后会详细介绍)
                    request.finish("cache-discard-canceled");
                    continue;
                }
                //先检查本地缓存,如果本地缓存中没有对应的已经封装好response的Entry类
                //则将该request加入“网络型”优先阻塞队列 
                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;
                }

                // 如果本地有缓存,但是已经过期,则将该request加入“网络型”优先阻塞队列
                //(可以结合下面的网络线程保存response缓存一起看)
                if (entry.isExpired()) {
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                // 根据本地缓存生成一个response回应实体类
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                if (!entry.refreshNeeded()) {
                    // 是否需要重新刷新数据,不需要则将该缓存生成的response返回给UI线程
                    mDelivery.postResponse(request, response);
                } else {
                //需要重新刷新数据
                    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.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                             //将该request加入到“网络型”优先阻塞队列
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

以上就是缓存线程的run方法的全部实现了,该方法的涉及到的技术点总结如下:

  • 基于阻塞队列实现的“生产者”与“消费者”线程模型

  • 基于Http缓存机制实现的判断本地缓存是否过期等问题

以上这两个知识点是理解缓存线程实现机制的前提条件,由于篇幅有限,本文不再过多涉及这两个方面知识点,请读者自行查阅。 了解Http缓存机制的知识可以点击这里


以上CacheDispatcher类的源码已经基本剖析完毕,接下来我们看下NetworkDispatcher类的具体源码

NetworkDispatcher类介绍

NetworkDispatcher类也是继承自Thread,同样最重要的方法就是里面的run方法了:

  @Override
    public void run() {
    //设置当前线程为后台线程
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // 从阻塞队列中拿出一个请求,如果队列为空,则阻塞等待
                request = mQueue.take();
            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                if (request.isCanceled()) {
                //请求被取消,在当前执行序列和等待序列中删除该request
                    request.finish("network-discard-cancelled");
                    continue;
                }

                addTrafficStatsTag(request);

                // 根据请求得到networkResponse对象
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                //返回304响应,并且之前已经处理过该请求,则略过
                    request.finish("not-modified");
                    continue;
                }

                // 根据networkResponse中数据生成response.cacheEntry,并且返回response实例
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

               //将request和对应的response相应保存到本地缓存
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // 标记该request已经被处理
                request.markDelivered();
                //调用ExecutorDelivery.postResponse方法,相应源码在上面已经贴出
                //即将处理结果返回给UI线程
                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);
            }
        }
    }

NetworkDispatcher类的主要功能如上,已经在代码中较为详细的做了解释说明。需要指出的是第28行代码中mNetwork.performRequest方法和第37行的request.parseNetworkResponse方法,下面我们依次来分析下他们的源码。mNetwork是BasicNetwork类的实例,那么我们先来看下BasicNetwork的源码实现:

BasicNetwork源码介绍

BasicNetwork类是NetWork接口的唯一实现,主要用来进行网络操作的封装和网络响应的处理(各种返回码的判断)。核心方法就是performRequest了:

    @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>();
                //将该请求加上"If-None-Match"、"If-Modified-Since"
                //此处属于进行http的对比缓存策略
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers);
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();

                responseHeaders = convertHeaders(httpResponse.getAllHeaders());

                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);
                    }

                   //如果是304响应,而且request保留着entry缓存,
                   //则直接使用本地缓存构建一个NetworkResponse返回
                    entry.responseHeaders.putAll(responseHeaders);
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
                            entry.responseHeaders, true,
                            SystemClock.elapsedRealtime() - requestStart);
                }

                // Some responses such as 204s do not have content.  We must check.
                if (httpResponse.getEntity() != null) {
                //将httpResponse值写入
                  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();
                }
                //一般的新请求从这里进行返回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);
                }
                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 {
                        // TODO: Only throw ServerError for 5xx status codes.
                        throw new ServerError(networkResponse);
                    }
                } else {
                    throw new NetworkError(networkResponse);
                }
            }
        }
    }
Request抽象类

Request类是一个抽象类,实现它的子类主要要实现parseNetworkResponse抽象方法。我们这里使用的时StringRequest类,那么我们看下StringRequest的实现:

@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));
    }

上述代码比较简单,根据response的编码方式对response数据进行编码并返回String类型的数据源。之后调用了Response.success方法,第二个形参执行了HttpHeaderParser.parseCacheHeaders函数:

 public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
        long now = System.currentTimeMillis();

        Map<String, String> headers = response.headers;

        long serverDate = 0;
        long lastModified = 0;
        long serverExpires = 0;
        long softExpire = 0;
        long finalExpire = 0;
        long maxAge = 0;
        long staleWhileRevalidate = 0;
        boolean hasCacheControl = false;
        boolean mustRevalidate = false;

        String serverEtag = null;
        String headerValue;

        headerValue = headers.get("Date");
        if (headerValue != null) {
            serverDate = parseDateAsEpoch(headerValue);
        }

        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",");
            for (int i = 0; i < tokens.length; i++) {
                String token = tokens[i].trim();
                if (token.equals("no-cache") || token.equals("no-store")) {
                    mustRevalidate = true;
                } else if (token.startsWith("max-age=")) {
                    try {
                        maxAge = Long.parseLong(token.substring(8));
                    } catch (Exception e) {
                    }
                } else if (token.startsWith("stale-while-revalidate=")) {
                    try {
                        staleWhileRevalidate = Long.parseLong(token.substring(23));
                    } catch (Exception e) {
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    mustRevalidate = true;
                }
            }
        }

        headerValue = headers.get("Expires");
        if (headerValue != null) {
            serverExpires = parseDateAsEpoch(headerValue);
        }

        headerValue = headers.get("Last-Modified");
        if (headerValue != null) {
            lastModified = parseDateAsEpoch(headerValue);
        }

        serverEtag = headers.get("ETag");

        // Cache-Control takes precedence over an Expires header, even if both exist and Expires
        // is more restrictive.
        if (hasCacheControl) {
            softExpire = now + maxAge * 1000;
            finalExpire = mustRevalidate
                    ? softExpire
                    : softExpire + staleWhileRevalidate * 1000;
        } else if (serverDate > 0 && serverExpires >= serverDate) {
            // Default semantic for Expire header in HTTP specification is softExpire.
            softExpire = now + (serverExpires - serverDate);
            finalExpire = softExpire;
        }

        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = finalExpire;
        entry.serverDate = serverDate;
        entry.lastModified = lastModified;
        entry.responseHeaders = headers;

        return entry;
    }

看到上面的代码我们是不是就豁然开朗了?这个方法执行的就是将response中所有的重要数据进行转换生成Volley自己的缓存对象Entry的过程:包括Http缓存字段以及请求应答Data在内的所有数据都进行了保存。如果对Http缓存策略还是比较陌生的话可以查看这篇博文:彻底弄懂HTTP缓存机制及原理

以上,基本上完成了Volley框架的核心模块的介绍,包含:Volley的网络线程执行原理、缓存线程执行原理、本地缓存情况介绍、http网络缓存的处理(先强制缓存策略后对比缓存策略)等等。

总结

以上源码可能一时看上去似懂非懂,那么我们来总结下:

  1. Volley初始化:首先创建缓存文件,之后初始化网络访问类BasicNetwork,构建一个缓存线程CacheDispatcher和四个网络线程NetworkDispatcher,并开始执行他们的run方法,一直等待request到来。

  2. 一个request被加入到RequestQueue中,首先判断该Request是否需要缓存,如果不需要则直接进入网络线程NetworkDispatcher等待执行;否则进入缓存线程CacheDispatcher等待执行。

  3. 对于2中被加入缓存线程CacheDispatcher的Request来说,如果该Request在本地没有缓存,那么CacheDispatcher就会把它交给NetworkDispatcher去处理;如果该Request有缓存,那么CacheDispatcher会检查该Request对应的缓存是否过期是否需要刷新资源(Http的强制缓存策略),如果没有过期不需要刷新资源的话,CacheDispatcher线程会自己构建一个response通过ExecutorDelivery的postResponse方法将结果返回UI线程;如果缓存已经过期,或者是需要刷新资源,那么CacheDispatcher线程会将Request交给NetworkDispatcher;
  4. 对于NetworkDispatcher来说,根据request的来源不同有如下逻辑:

    • 如果Request不需要被缓存,则Request将被执行并且得到返回结果返回UI线程

    • 如果Request是从CacheDispatcher而来,并且本地没有缓存过,那么NetworkDispatcher执行完该Request,会将该Request的URL和对应的Respone以Cache.Entry实体类(包含Http缓存策略以及应答数据等信息)的形式保存在本地缓存

    • 如果Request是从CacheDispatcher而来,本地缓存有但是已经过期,那么会将本地缓存http缓存数据加入到header中,也就是加入”If-None-Match”、”If-Modified-Since”数据,发送给服务器进行对比缓存过程。如果服务器返回304,那么代表该资源还可以继续使用,则将该request和对应的缓存再次更新到本地缓存文件中,之后将缓存中的Respone返回UI线程。如果不可用,则返回200,接下来照样是覆盖本地缓存文件,将结果返回给UI线程

终于,Volley框架的大概流程已经讲述完毕,虽然我们只使用了StringRequest来进行发送网络访问的演示,但是其他的继承Request类的具体实体类的核心逻辑过程也是一样的。

特殊说明

需要特别指出的是:imageLoader的使用,通过阅读Volley源码,我们知道Volley已经为我们做了外存层面的缓存,我们需要自己根据业务逻辑是否来实现内存缓存,如果我们自己实现内存缓存的话(当然自己也可以实现外存的缓存,但是2个外存缓存那就没啥意思了= =),那么Volley框架优先使用我们自己定义的内存缓存,具体的源码不在展示,很简单(最后的Request也是被加入到RequestQueue中)。内存缓存的接口Volley已经为我们写好:

//ImageLoader的第二个形参就是我们自己实现的内存缓存(基于LRU)类
ImageLoader imageLoader = new ImageLoader(requestQueue, new BitmapCache());
ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView,
                R.mipmap.ic_launcher, R.mipmap.ic_launcher);

        imageLoader.get("https://img-my.csdn.net/uploads/201404/13/1397393290_5765.jpeg", listener);

BitmapCache.java

public class BitmapCache implements ImageLoader.ImageCache {
    private LruCache<String, Bitmap> mCache;

    public BitmapCache() {
        int maxSize = 10 * 1024 * 1024;
        mCache = new LruCache<String, Bitmap>(maxSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight();
            }
        };
    }

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

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

}

如上,我们的处理image的逻辑已经完美。

另外一点需要指出的就是缓存OOM的问题:毕竟Volley的本地缓存是有限的,一直使用总有缓存爆满的情况,那么我们需要手动清理缓存(也算是比较略坑的地方)

我们可以通过Cache.remove(String url)来移除对应url的缓存文件,如:

mRequestQueue.getCache().remove(url);

clear()清空总缓存,我们可以通过调用Cache.clear();来清空缓存,如下:

mRequestQueue.getCache().clear();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值