Volley源码解析(三)网络请求流程

Volley的简单介绍http://blog.csdn.net/chengshuyuan_uestc/article/details/51755189
Volley源码解析(一)Volley中乱码问题及解决方案http://blog.csdn.net/chengshuyuan_uestc/article/details/51755191
Volley源码分析(二)-Volley中的Request类http://blog.csdn.net/chengshuyuan_uestc/article/details/51755197
Volley源码解析(三)网络请求流程http://blog.csdn.net/chengshuyuan_uestc/article/details/51755201
volley封装的很好,使用起来很方便,在一篇博客中,我们学些了volley进行网络请求的三个步骤,以及请求不同格式定义不同的Request,但是对Volley的源码我们还不是很清楚,就让我们一起来阅读以下Volley的源码,将Volley的工作流程和工作原理梳理以下。

首先我们看一下Volley官方文档给我们的Volley工作流程图

image

OK,我们就根据这张图,从我们使用Volley进行网络请求的步骤来阅读源代码。

在使用Volley进行网络请求时,我们首先要得到一个RequestQueue

RequestQueue requestQueue = Volley.newRequestQueue(this);

看下一newRequestQueue()函数的实现,

    //Volley类中
    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }

这个方法调用了同在Volley类中的重载函数,继续看

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

同样是调用了重载函数

//Volley类中
    public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }

        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }

        Network network = new BasicNetwork(stack);
        RequestQueue queue;
        if (maxDiskCacheBytes <= -1)
        {
            // No maximum size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        }
        else
        {
            // Disk cache size specified
            queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
        }
        queue.start();
        return queue;
    }

OK,在这个方法中,我们终于看到了函数的实现部分,首先得到userAgent,我们知道在http请求报文中,请求行的四个部分(HOST、Connection、user-agent、agent-language)就包括user-agent。

接下来,如果HttpStack的对象为空,则判断系统版本号,在9之前使用HttpClientStack实例,在后边的版本则创建爱你一个HurlStack实例。其实HttpClientStack中使用的是Apache提供的HttpClient来进行网络的请求,而HurlStack使用的是HttpUrlConnection来进行网络请求。这时因为在2.3之前,HttpUrlConnection存在一个bug,而在2.3之后,在修复bug之后,HttpUrlConnction因为支持压缩等原因,更被推荐使用。HttpStack接口只有一个方法,performRequest()。HurlStack类和HttpClientStack类都实现了这个接口。HttpUrlConnection进行网络请求比较简单一点,我们看一下HurlStack类中的performRequest()实现。

//HurlStack类中
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request); //根据url打开一个连接
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));    //添加请求参数
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);  //设置http版本号 为http/1.1
        int responseCode = connection.getResponseCode();                     // 得到返回状态码
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);  // 得到Response
        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }
        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }
        return response;
    }

就是我们熟悉的使用HttpUrlConnection进行网络请求。我添加了部分注释。

接着返回Volley类的newRequestQueue()函数,接着得到一个Network实例,Network接口中,只有一个方法performRequest(), BasicNetwork实现了这个接口,看源码后我们发现BasicNetWork中的performRequest()方法调用了HttpStack类中的performRequest()方法。

接着我们得到ReuqestQueue实例,并执行了其start()方法,我们看一下RequestQueue类中start()方法执行了什么动作。

RequestQueue中维护了两个基于优先级的Request队列, 缓存队列和网络请求队列。放在缓存队列中的Request,将通过缓存获取数据,放在网络请求队列中的Request,将通过网络获取数据。

private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();

RquestQueue中还和维护了一个正在进行,尚未完成的请求集合。

private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();

维护了一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续相同的url请求将进入此队列。

private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();

那么在创建RequestQueue之后调用的start()方法呢

//RequestQueue类中
    public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

这里先是创建了一个CacheDispatcher的实例,然后调用了它的start()方法,接着在一个for循环里去创建NetworkDispatcher的实例,并分别调用它们的start()方法。这里的CacheDispatcher和NetworkDispatcher都是继承自Thread的,而默认情况下for循环会执行四次,也就是说当调用了Volley.newRequestQueue(context)之后,就会有五个线程一直在后台运行,不断等待网络请求的到来,其中CacheDispatcher是缓存线程,NetworkDispatcher是网络请求线程。

我们分别看一下CacheDispatcher类和NetworkDispatcher类的run()方法

    //CacheDispatcher类
    @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());
            }
        }
    }

当没有cache或者cache过期时,需要将请求放到网络请求队列NetworkQueue中,如果存在cache且有效,则调用Request的parseNetworkResponse()方法,进行Response的解析工作。
run()方法执行的流程图如图所示:

image

NetworkDispatche类的run()方法代码如下:

    //NetworkDispatche类
    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            // release previous request object to avoid leaking request object when mQueue is drained.
            request = null;
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }

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

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

                addTrafficStatsTag(request);

                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

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

                // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // Write to cache if applicable.
                // TODO: Only update cache metadata instead of entire record for 304s.
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // Post the response back.
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

我们看到调用了Network类的performRequest()方法得到Response,而在刚才的分析中,我们知道BasicNetwork类中的perforrequest()方法调用的HttpStack类中的performRequest()方法,即使用HttpUrlConnection或者HttpClient来进行网络请求并得到Response。在得到Response后,如果需要加入缓存,则执行加入缓存的put操作。在得到Response之后,又执行了下面的语句:

//NetworkDispatche类
mDelivery.postResponse(request, response);

我们查看实现ResponseDelivery接口的ExecutorDelivery类的实现方法

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

在postResonse()函数中,需要实现ResponseDeliveryRunnable类,这个类的run()方法如下:

    //ExecutorDelivery类中
        @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();
            }
       }
    }

不用看太多,我们看到调用了我们熟悉的Request类的deliverResponse(mResponse.result)方法。
NetworkDispatche类run()方法的流程图如下图所示:

image

OK 到现在我们分析完了用Volley进行网络请求的原理、Request请求的发出(缓存或者从网络获取)、Response的处理等等过程。

需要注意的地方

另外分析网络请求时一个重要的点需要我们注意, 网络请求是一个耗时操作,肯定不会在UI主线程中进行网络请求的操作,Volley在请求网络或者Cache时都是在一个新的Thread类中,然而我们可以可以有如下操作。

    StringRequest stringRequest = new StringRequest("http://www.baidu.com",
            new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {
                    tv_string.setText(response.toString());
                }
            },
            new Response.ErrorListener() {
                @Override
                 public void onErrorResponse(VolleyError error) {
                    tv_string.setText("error");
                }
             }
    );

看到我们直接更新了UI,这是怎么实现的呢?

其实在ExecutorDelivery类的构造函数中,我们传入了一个Handler对象:

    //ExecutorDelivery类中
    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类的过程中,我们说明了在ResponseDeliveryRunnable中调用了Request类的deliverResponse方法,而上边execute()函数的参数正是ResponseDeliveryRunnable类的对象。我们知道handler.post(Runnable),Runnable就和handler相关的线程是同一个线程。那么该handler是和哪个线程关联呢?

查看调用关系,在RequestQueue类的构造函数时有如下代码:

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

一切水落石出,handler是和主线程关联的,所以是可以直接进行UI更新的。

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
一、 方案议题综述 移动互联网的普及以及智能家居的发展,物联网的发展,越来越多的小设备,脱机设备亟待接入互联网形成互联互通,与服务提供商连接,提供更新更强服务。但是普通小设备都是低成本非常简单的单片机为主控芯片的,不具备上网功能,更不可能在单片机里面跑TCP/IP协议栈,由此,3G模块具备嵌入式TCP/IP协议栈并提供串口供单片机收发数据,单片机只需提供一路串口,并且把要发送的数据通过串口发送出去,3G模块负责把数据传输到公网的指定主机上,这样单片机就接入互联网了。 今天的课题,我们选用IM506P作为核
毕加索的艺术——Picasso,一个强大的Android图片下载缓存库,OkHttpUtils的使用,二次封装PicassoUtils实现微信精选 官网: http://square.github.io/picasso/ 我们在上篇OkHttp的时候说过这个Picasso,学名毕加索,是Square公司开源的一个Android图形缓存库,而且使用起来也是非常的简单,只要一行代码就轻松搞定了,你会问,为什么不介绍一下Glide?其实Glide我有时间也是会介绍的,刚好上篇我们用到了Picasso,所以就聊下

手把手教你解析Resources.arsc - 2016-06-24 18:06:23

一、前言 对于APK里面的Resources.arsc文件大家应该都知道是干什么的(不知道的请看我的另一篇文章 Android应用程序资源文件的编译和打包原理 ),它实际上就是App的资源索引表。下面我会结合实例对它的格式做一下剖析,读完这篇文章应该能够知道Resources.arsc的格式,并可以从二进制的文件中查找到资源的相关信息,或者根据资源的id可以定位到二进制文件中的位置。不过本人对Android资源文件的有一些相关概念并不是特别熟悉,所以文章中有很多地方也并不明白,如有错误欢迎指正! 二、R.
一、IMS开机初始化 (如果图片看不清的话,可以右键选择在查看图片,或者把图片另存到自己电脑再查看。) 本 文 来 自 http://blog.csdn.net/linyongan , 转 载 请 务 必 注 明 出 处 。 1.1 监控IMS Service PhoneApp进程是在系统开机时启动的,Phone进程初始化的时候(步骤1~6),在创建GSMPhone或者CDMAPhone之后,会执行监控IMS Service的流程,也就是流程图上的 步骤7 ,在PhoneFactory. Java 的ma
怎样防止App在后台运行,点击App桌面的图标重新启动?            在项目中,遇到一个问题百思不得其解,那就是:我在app使用过程中,点击了home键,然后去看看微信之类的其他应用,这个时候再点击app桌面的图标,这个时候app是重新启动的,而不是从上次停止的界面开始的。            对于上面的情况,我觉得既然我的app已经在后台还运行着,为什么就不能继续重上一个界面继续运行,非得从新运行呢。然后我就去查资料解决了这个问题。首先讲讲这个现象的本质。            原因:当点击
1、ZIP文件目录遍历简介 因为ZIP压缩包文件中允许存在“../”的字符串,攻击者可以利用多个“../”在解压时改变ZIP包中某个文件的存放位置,覆盖掉应用原有的文件。如果被覆盖掉的文件是动态链接so、dex或者odex文件,轻则产生本地拒绝服务漏洞,影响应用的可用性,重则可能造成任意代码执行漏洞,危害用户的设备安全和信息安全。比如近段时间发现的“寄生兽”漏洞、海豚浏览器远程命令执行漏洞、三星默认输入法远程代码执行漏洞等都与ZIP文件目录遍历有关。 阿里聚安全的应用漏洞扫描服务,可以检测出应用的ZIP文

浅谈Android中的MVP - 2016-06-24 17:06:24

转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/51745798 本文出自: 【顾林海的博客】 前言 为什么使用MVP,网上有很多说法,最主要就是减轻了Activity的责任,相比于MVC中的Activity承担的责任太多,因此有必要讲讲MVP。 MVP入门 在MVC框架中,View是可以直接读取Model模型中的数据的,Model模型数据发生改变是会通知View数据显示发生相应的改变。而在MVP中Model和View之间的没有
阅读此文前请先阅读 Retrofit+okhttp网络框架介绍 从上文中我们已经了解通过如下代码即可得到返回给我们call 以及 response对象,今天我们通过源码来分析这个过程是如何实现的。 /** * 获取天气数据 * @param cityname * @param key * @return */ @GET ( "/weather/index" ) CallWeatherData getWeatherData( @Query ( "format" ) String format, @Query
一、Block 的类型 根据 Block 在内存中的位置分 为三种类型 NSGlobalBlock , NSStackBlock, NSMallocBlock 。 NSGlobalBlock :类似函数,位于 text 段; NSStackBlock :位于 栈内存,函数返回后 Block 将无效; NSMallocBlock :位于堆内存。 二、Block 的 copy 、 retain 、 release 操作   不同于 NSObjec 的 copy 、 retain 、 release 操作: B
前言 module 怎能少得了动画呢~ 代码解读 weex code API 接口 transition (node, options, callback) Arguments 参数node(Node):将要动画的元素。options( object ):操作选项styles( object ):指定要应用的过渡效果的样式的名称和值。color( string ):色彩的元素时,animaiton完成。transform( object ):变换函数被应用到元素。支持下列值。translate/ tran