Volley -- 源码分析

Posted Y_ZhiWen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Volley -- 源码分析相关的知识,希望对你有一定的参考价值。

简介

  • 关于Volley封装性跟实用性是毋庸置疑的,本篇文章是争对上一篇文章
    Volley – 基本用法做出比较详细的过程分析,分析Volley请求的流程,缓存的策略,工作线程的执行分配,接口回调的机制,代码的封装等相关进行分析,涉及到Volley的相关类有Request、Response、NetworkDispatcher、CacheDispatcher、Cache、Network等。
  • 本篇文章通过提问方式展示上面提及的相关知识点,你可以当成一次复习,也可以当成一次面试,ヽ(^0^)ノ一次不正式的面试,哈哈,接下来开始吧。
  • 现在还写了关于Volley的图片处理源码分析,分析关于Volley对图片的压缩,请求的处理等Volley – 图片处理方式源码分析

问题

  • 对于请求结果(包括错误结果),是怎么被传递到UI线程,也就是说怎么回调到了Response.Listener、Response.ErrorListener接口的。
  • 请求(Request)是如何得到结果(Response)的。
  • Volley缓存机制
  • ……

解答问题一:请求结果怎么回调到Response.Listener、Response.ErrorListener接口

咚,看到ResponseDelivery源码

public interface ResponseDelivery 
    /**
     * Parses a response from the network or cache and delivers it.
     */
    public void postResponse(Request<?> request, Response<?> response);

    /**
     * Parses a response from the network or cache and delivers it. The provided
     * Runnable will be executed after delivery.
     */
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);

    /**
     * Posts an error for the given request.
     */
    public void postError(Request<?> request, VolleyError error);

发现它是一个接口,用于传送结果的

看一下它的子类ExecutorDelivery

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

    /**
     * Creates a new response delivery interface.
     * @param handler @link Handler to post responses on
     */
    public ExecutorDelivery(final Handler handler) 
        // Make an Executor that just wraps the handler.
        mResponsePoster = new Executor() 
            @Override
            public void execute(Runnable command) 
                handler.post(command);
            
        ;
    

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

    @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));
    
    // 还有一个内部类ResponseDeliveryRunnable下面讲
    ...

通过构造函数,

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

可以发现ExecutorDelivery成员变量Executor的执行操作是通过handler.post(command)将command操作传递到与Handler的相关联的线程去执行。而ExecutorDelivery的三个post操作实际上是调用mResponsePoster.execute(command)。因此也许能够猜到构造函数中的Handler极有可能是关联着UI线程,查找一下ExecutorDelivery是在哪里构造即可证实,通过RequestQueue的构造函数可以发现

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

有了一点思路,那么通过UI线程Handler做什么呢,通过ExecutorDelivery的三个Post方法可以发现,传递的都是其内部类ResponseDeliveryRunnable的实例,看一下其源码:

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

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

        @SuppressWarnings("unchecked")
        @Override
        public void run() 
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) 
                mRequest.finish("canceled-at-delivery");
                return;
            

            // Deliver a normal response or error, depending.
            if (mResponse.isSuccess()) 
                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 
                mRequest.finish("done");
            

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

可以发现ResponseDeliveryRunnable有三个成员变量,分别是Request,Response和Runnable,也许你会想这里的Runnable有什么用,在哪被使用了,下面再揭晓,重点看一下其run方法,发现它的逻辑是通过判断request是否取消,结果是否成功,来看是否执行mRequest.deliverResponse(mResponse.result);和mRequest.deliverError(mResponse.error);问题来了上面这两个方法是干什么的??

查看Request的代码,

/**
     * Subclasses must implement this to perform delivery of the parsed
     * response to their listeners.  The given response is guaranteed to
     * be non-null; responses that fail to parse are not delivered.
     * @param response The parsed response returned by
     * @link #parseNetworkResponse(NetworkResponse)
     */
    abstract protected void deliverResponse(T response);
/**
     * Delivers error message to the ErrorListener that the Request was
     * initialized with.
     *
     * @param error Error details
     */
    public void deliverError(VolleyError error) 
        if (mErrorListener != null) 
            mErrorListener.onErrorResponse(error);
        
    

发现deliverResponse是一个抽象方法,而deliverError是调用mErrorListener.onErrorResponse(error)传递VolleyError,而这里的mErrorListener则是Response.ErrorListener接口。

到这里有没有一种恍然大悟的感觉,你应该猜到deliverResponse是通过调用Response.Listener接口传递结果的,那么继续查看Request子类中方法deliverResponse的实现

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

可以发现子类中该方法写法基本一样,只是结果的参数不一样而已。

现在小结一下,一个Request是怎么在Response.Listener、Response.ErrorListener接口得到结果的
通过ExectorDelivery.post...操作,在UI线程中执行request.deliverResponse、request.deliverError方法,
而这两个方法有通过调用Request其Response.Listener、Response.ErrorListener将结果传递出来。

那么问题又来了,ExectorDelivery.post…操作在哪里被执行,通过RequestQueue的成员mDelivery发现,在构造Dispatcher(NetworkDispatcher、CacheDispatcher)时mDelivery被传递过去,Dispatcher是一个工作线程,不断获取RequesetQueue队列中的Requeset,并执行得到结果,再通过Delivery返回数据到UI线程。

问题又来了又来了,工作线程怎么得到Request的结果Response。详情请看下面问题二。

解答问题二:如何得到Request的结果Response

这里涉及到的相关类有Network、跟NetWorkResponse,Cache


看到这里应该明白Network通过HttpStack将Request转换成HttpResponse,再将HttpResponse中的数据包装成NetworkResponse

而这里的HttpStack的两个子类其实就是我们使用的HttpClient和HttpURLConnection。(下篇VolleyHTTP篇时讲解)

那么NetwoResponse是什么?
先来了解一下Cache


可以看到Entry是存储缓存数据的实体类,而NetworkResponse是存储网络数据结果的实体类

回到原来的问题,如何得到Request的结果Response,
现在有了NetworkResponse,怎么实现呢?

看一下Requset源码,可以发现

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

将NetworkResponse解析成Response是个抽象方法,也就是说在结果数据已经有了的前提下(NetworkResponse 对象的data数据,byte[]形式),对于具体怎么样的请求,那就有其自己去解析。比如StringRequest的parseNetworkResponse方法

parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));

这样子我们也就可以自定义Request。

问题三:Volley缓存机制:CacheDispatcher及DiskBaseCache

CacheDispatcher是一个线程类,而上面讲解的都是NetworkDispatcher这个线程类执行的操作:将Request的结果Response。

这里提一下Volley在分配时默认分配有一个CacheDispatcher缓存操作线程和四个NetworkDispatcher网络操作线程。

而NetworkDispatcher线程的工作是不断的读取RequsetQueue请求队列中的Request并获去结果。

那么CacheDispatcher是干嘛的呢??

根据前面两个问题的解决思路,先看一下RequestQueue中的CacheDispatcher,发现其构造函数与NetworkDispatcher的差别在于CacheDispatcher多了一个mCacheQueue,其他对象完全一样。

那么这个mCacheQueue是什么?应该是存储正在缓存的Request的缓存队列。

主要看一下run方法,看其是怎么的缓存机制

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

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

        Request<?> request;
        while (true) 
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try 
                // Take a request from the queue.
                request = mCacheQueue.take();
             catch (InterruptedException e) 
                // We may have been interrupted because it was time to quit.
                if (mQuit) 
                    return;
                
                continue;
            
            try 
                request.addMarker("cache-queue-take");

                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) 
                    request.finish("cache-discard-canceled");
                    continue;
                

                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) 
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                

                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) 
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                

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

                if (!entry.refreshNeeded()) 
                    // Completely unexpired cache hit. Just deliver the response.
                    mDelivery.postResponse(request, response);
                 else 
                    // Soft-expired cache hit. We can deliver the cached response,
                    // but we need to also send the request to the network for
                    // refreshing.
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

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

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

根据其run方法可以看到,其缓存原理:

  • 不断在缓存队列mCacheQueue中获取Request。
    • 如果无缓存(为null或者过时),则添加到mNetworkQueue中等待从服务器中获取数据
    • 如果有缓存,判断是否需要刷新数据
      • 不需要则直接通过没Delivery.post..方法传结果递到UI线程
      • 需要刷新的话,则先通过Delivery.post…方法传递结果到UI线程,并且将请求添加到没NetworkQueue中,即通过Delivery.post..( .. , .. , Runnable runnable)方法,这时候这个方法在这里调用,突然觉得这个方法的神奇

在这里也许你会发现通过 mCache.get(request.getCacheKey())获取缓存,其实mCache的真实对象是Cache的子类DiskBaseCache,其功能类似于LruCache,内部是通过LinkedHashMap来保存缓存文件数据,读取时对缓存文件进行读取操作,这里就不多讲。

前天晚上写项目写到3点,昨晚回来写这篇文章写到了快3点,而且越写越精神,哈哈,最重要的是坚持。

现在还写了关于Volley的图片处理源码分析,有兴趣的博友可以看看Volley – 图片处理方式源码分析

最后,如哪里不足或者分析错误,非常欢迎指正,谢谢。

以上是关于Volley -- 源码分析的主要内容,如果未能解决你的问题,请参考以下文章

android-----Volley框架使用ImageLoader加载图片源码分析

Volley -- 源码分析

Volley 源码解析(转)

android-----Volley框架源码分析

Volley源码分析面向接口编程的典范

Volley -- 网络请求源码分析