关于 Volley 的最简单讲解,你想知道的都在这儿了

RakeeBwk 8年前
   <p>众所周知,Volley是google在2013年开源的一款异步异步异步http网络请求库,采用Volley进行网络请求非常简单。那么怎样个异步法?最简单的解释就是:</p>    <p>使用Volley你不用再像原生的HttpUrlConnection一样new Thread(new runable());新开工作线程这些事Volley都帮你做了。</p>    <p>Volley为http请求提供了如下的保障:</p>    <ol>     <li>response缓存。Volley内部维护了一个cache,用来对网络请求的结果进行缓存,再次发起该请求时会首先访问cache,当请求内容和上次相同时会直接从cache返回数据,避免重复的网络请求。</li>     <li>request请求队列,Volley会将通过request.add()方法添加的request放置在请求队列,并从该队列中逐一取出请求进行请求。并支持请求优先级的设置。</li>     <li>response分发。Volley采用异步的请求方式,请求成功并获得返回值时会在工作线程中对结果进行解析,并将response分发到主线程,意味着在Volley的回调方法中,代码直接运行在主线程,方便了UI更新等操作。这一点和okHttp有很大区别,okHttp异步方式的回调仍然运行在工作线程。这样不同的实现也各有各的好处。</li>     <li>支持request的自定义,Volley原生request包括了JsonRequest、JsonArrayReuqest、ImageRequest和StringResquest四种,分别用于json对象、json数组以及字符串类型的返回值请求,除此以外,Volley允许继承Request类实现自己的请求,比如你可以实现返回一个JavaBean的请求。</li>    </ol>    <p>Volley适合频繁但数据量不大的网络请求,例如常见API调用,并不适合大文件的下载。Volley将整个response加载到内存并进行操作(可以是解析等操作)大文件可能会引起OOM</p>    <p><a href="/misc/goto?guid=4959739463053640074" rel="nofollow,noindex">官网对Volley的介绍</a></p>    <h2>Volley简单使用</h2>    <p>最简单Volley的使用包括三个步骤:</p>    <ol>     <li>创建RequestQueue</li>     <li>创建Request</li>     <li> <p>将Request添加到RequestQueue</p> <pre>  RequestQueue requestQueue = Volley.newRequestQueue(context);  StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {      @Override      public void onResponse(String response) {       //这里可以更新UI      }          }, new Response.ErrorListener() {      @Override      public void onErrorResponse(VolleyError error) {        }  });  requestQueue.add(stringRequest);</pre> </li>    </ol>    <p>本文的重点是分析Volley的工作流程,各种请求的具体使用可以参考以下链接:</p>    <h2>Volley原理解析</h2>    <p>先来一张Volley工作流程图镇楼:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/4a8d12811ffd5fe806e3cf35a19ec58e.png"></p>    <p>Volley中包括以下几种线程:</p>    <ul>     <li>缓冲读取线程(cacheDispatcher)——用于从缓冲中获取数据(继承自Thread)</li>     <li>网络请求线程(networkDispatcher)——用于发送网络请求来获取数据(继承自Thread)</li>     <li>response分发线程(mResponsePoster)——用于将请求结果(可能来自cache和网络)分发到主线程(Executor对象)</li>    </ul>    <h3>1.RequestQueue的创建</h3>    <p>既然使用Volley首先创建的是RequestQueue对象,那我们就从Volley.newRequestQueue(context)方法开始。该静态方法通过调用重载的静态方法,内部调用构造方法创建一个requestQueue对象:</p>    <pre>  public static RequestQueue newRequestQueue(Context context, HttpStack stack) {          //创建缓存      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) {          //API9及以上则使用HttpUrlConnection          if (Build.VERSION.SDK_INT >= 9) {              stack = new HurlStack();          } else {              //小于9则使用HttpClient              // Prior to Gingerbread, HttpUrlConnection was unreliable.              stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));          }      }      //用于后面的网络请求      Network network = new BasicNetwork(stack);      //根据设置的缓存目录创建请求队列      RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);      //启动请求队列      queue.start();      return queue;  }</pre>    <p>从上面的代码可以发现,Volley内部是采用HttpUrlConnection或者HttpClient进行网络请求的(HttpClient已经废弃不再维护)。</p>    <p>我们重点关注一下这一行代码:</p>    <pre>  RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);</pre>    <p>以上代码创建了一个新的请求队列。并传入DiskBasedCache(cacheDir)来根据相应目录初始化缓存。</p>    <p>跟踪上述构造方法,发现调用了另一个三个参数的构造方法:</p>    <pre>  public RequestQueue(Cache cache, Network network) {      //调用三个参数的构造方法      this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);  }</pre>    <p>DEFAULT_NETWORK_THREAD_POOL_SIZE是声明的常量=4,所以我们知道Volley的网络请求线程数量默认为4个。上述构造方法调用了四个参数的构造方法:</p>    <pre>  public RequestQueue(Cache cache, Network network, int threadPoolSize) {      //调用四个参数对的构造方法      this(cache, network, threadPoolSize,              new ExecutorDelivery(new Handler(Looper.getMainLooper())));  }</pre>    <p>注意以上方法的最后一个参数,这是一个绑定了主线程handler的ExecutorDelivery对象,用处在后文进行说明</p>    <p>四个参数的构造方法的实现:</p>    <pre>  public RequestQueue(Cache cache, Network network, int threadPoolSize,          ResponseDelivery delivery) {      mCache = cache;      mNetwork = network;      mDispatchers = new NetworkDispatcher[threadPoolSize];      mDelivery = delivery;  }</pre>    <p>这样requestqueue就创建完成,</p>    <h3>2.网络请求的执行</h3>    <p>创建的requestqueue.queue.start()方法在创建的最后一行得到了调用。</p>    <p>queue.start()的具体实现:</p>    <pre>  public void start() {      stop();  // 确保现有的dispatcher停止工作      // 创建cacheDispatcher并启动      mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);      mCacheDispatcher.start();        //根据线程池(mDispatchers)的大小创建网络请求线程。      for (int i = 0; i < mDispatchers.length; i++) {          NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);          mDispatchers[i] = networkDispatcher;          //按创建顺序启动对应的线程          networkDispatcher.start();      }  }</pre>    <p>正如代码的注释一样,先创建并启动缓存线程用来读取缓存内容,再创建网络请求线程用于网络请求。mDispatchers为线程数组,默认大小为4,networkDispatcher网络请求执行线程。</p>    <p>我们重点分析一下网络请求线程的创建:</p>    <pre>  NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);</pre>    <p>我们着重关注一下mDelivery对象,它是一个绑定了主线程handler的ExecutorDelivery对象</p>    <p>由于NetworkDispatcher本身是一个线程,调用start()方式实际会执行它的run()方法,我们来到run()方法内部:</p>    <pre>  @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) {              // We may have been interrupted because it was time to quit.              if (mQuit) {                  return;              }              continue;          }            try {              request.addMarker("network-queue-take");              //判断请求是否取消,取消则不执行该请求              if (request.isCanceled()) {                  request.finish("network-discard-cancelled");                  continue;              }                addTrafficStatsTag(request);                // 发送网络请求              NetworkResponse networkResponse = mNetwork.performRequest(request);              ...               // 在子线程解析返回结果              Response<?> response = request.parseNetworkResponse(networkResponse);                 ...              省略的代码用于请求成功的结果进行缓存              ...              //将请求的结果发送出去              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);          }      }  }</pre>    <p>代码比较多,由于主要研究网络请求部分,上述代码的请求结果缓存部分我进行了省略,你可以自行查看源码。</p>    <p>上述代码的核心在于以下三个方法:</p>    <pre>  mNetwork.performRequest()  Response<?> response = request.parseNetworkResponse()  mDelivery.postResponse(request, response);</pre>    <p>避免代码过多我就不贴源代码了,作用:</p>    <ul>     <li>mNetwork.performRequest()——最终调用HurlStack().performRequest()方法采用httpUrlConnection的方式进行网络请求。</li>     <li>request.parseNetworkResponse()——根据parseNetworkResponse()的具体逻辑实现在子线程对结果进行解析( 这也是自定义Request需要实现的方法之一,比如需要返回javaBean就需要该方法中进行解析 )</li>     <li>mDelivery.postResponse(request, response)——将解析结果分发到 <strong>主线程</strong></li>    </ul>    <h3>3.Response的分发</h3>    <p>最后我们来看看Response的分发。</p>    <p>mDelivery是一个ExecutorDelivery对象,内部存在一个成员对象叫做mResponsePoster(为Executor类型),最终调用这个mResponsePoster.execute()方法对response进行分发。</p>    <p>具体过程如下:</p>    <p>mDelivery.postResponse(request, response)方法怎么把结果分发到主线程:</p>    <p>该方法实现如下,调用了重载的postResponse(request, response, null)方法:</p>    <pre>  public void postResponse(Request<?> request, Response<?> response) {      postResponse(request, response, null);  }</pre>    <p>源码如下:</p>    <pre>  public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {      request.markDelivered();      request.addMarker("post-response");      //执行分发      mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));  }</pre>    <p>mResponsePoster也是一个Executor对象,通过execute()方法来执行Runnable对象的run()方法:它的execute()方法实现如下:</p>    <pre>  public ExecutorDelivery(final Handler handler) {      // 采用handler.post()方法将runnable对象运行在主线程      mResponsePoster = new Executor() {          @Override          public void execute(Runnable command) {              handler.post(command);          }      };  }</pre>    <p>可以看到这里使用了handler.post()来使得runnable对象运行在与该handler所绑定的线程中</p>    <p>那么这个handler是在哪里赋值的呢?</p>    <p>往上翻,那就是在前文中两次注明的mDelivery对象,它是一个绑定了主线程handler的ExecutorDelivery对象,所以这里的handler是和主线程绑定的,在分发response的时候,我们的回调方法最终会运行在主线程。</p>    <p>ResponseDeliveryRunnable类的run()方法部分代码实现如下:</p>    <pre>  // 分发请求结果或者错误消息  if (mResponse.isSuccess()) {      mRequest.deliverResponse(mResponse.result);  } else {      mRequest.deliverError(mResponse.error);  }</pre>    <p>deliverResponse(mResponse.result)也是在自定义Request的时候可以覆盖的方法。在Volley自带的StringRequest类中,该方法实现如下:</p>    <pre>  @Override  protected void deliverResponse(String response) {      mListener.onResponse(response);  }</pre>    <p>这里就调用了我们在使用Volley创建StringRequest对象时设置的监听器的onResponse(reponse)方法。然后就可以在改方法中更新UI了。</p>    <h2>Volley用到线程池了吗?</h2>    <p>直观地看来,Volley是没有用到线程池的,它采用的是一个默认长度为4的线程数组(名为mDispatcher),在具体的网络请求线程(networkDispatcher)的run()方法中,Volley采用了如下的结构:</p>    <pre>  public void run(){      while(true){      //从请求队列中取出请求      requestQueue.take();      ...      }  }</pre>    <p>这个死循环使得run()方法永远不会结束,对应的线程也就不会被销毁,而当requestQueue.take()没有获取到请求的时候(即请求队列已空),该线程就会被阻塞而暂时停止运行。有新的请求的时候又得到运行进而发送新的网络请求。</p>    <p>可以发现,Volley没有使用线程池来管理网络请求线程,而是采用死循环的方式来避免了不断地创建、销毁线程带来的开销,达到了和线程池同样的效果。</p>    <h2>最后</h2>    <p>这里只是简单梳理了一遍Volley的网络请求流程,对缓存部分没有作过多的讲解,若想了解更多,还请读者自行查看源码。</p>    <p> </p>    <p>来自:http://www.distancelin.cn/2017/02/28/你想知道的volley使用及原理/</p>    <p> </p>