Volley的使用以及源码分析(二)

二、Volley源码解析

2.1 Volley框架结构

根据Volley的框架结构,在上一章可以看出所有的Request请求都是按照这个流程处理,那么我将从请求流程进行源代码分析.

其框架主要分为3部分:

(1)Main Thread中创建Request以及解析显示请求返回结果;

(2)Cache Thread在Cache中处理请求,若请求的内容在缓存中已存在,则从缓存中取出并返回;

(3)NetWork Thread,当请求在缓存中找不到时,则需要从访问网络获取数据.

主线程和Cache线程都只有一个,而NetWork Thread线程可以有很多个(默认是4个),这样能够解决并行问题.

2.2 Volley之RequestQueue

RequestQueue请求队列作为Volley框架使用过程第一个需要创建目标,其内部通过调用Volley类的静态函数进行创建.

public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR)
        ...//省略代码:主要内容
        //1、创建UserAgent用来封装应用的包名跟版本号,提供给服务器
        //2、根据当前系统的版本号来选择HttpStack,若版本大于等于9(Android 2.2以上),使用HttpURLConnection,所以使用一个HurlStack;若小于9,则使用HttpClient.
        //3、创建一个NetWork,调用其构造函数并传入参数stack,去跟网络进行通信.
        //4、创建一个DiskBasedCache对象,和Network一起,传给RequestQueue作为参数,创建RequestQueue对象.
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);//创建RequestQueue
        queue.start();//调用RequestQueue的start方法
  }

请求队列作为Volley中所有请求的存储器,其内部使用Set集合来存储创建的Request,所有在队列中或者正在被处理的请求都会在这个集合中.

private final Set

  public RequestQueue(Cache cache, Network network, int threadPoolSize,
            ResponseDelivery delivery) {
        mCache = cache;//缓存
        mNetwork = network;//网络
        mDispatchers = new NetworkDispatcher[threadPoolSize];//网络访问线程池
        mDelivery = delivery;//派送Response的结果给主线程
      }
  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, DEFAULT_NETWORK_THREAD_POOL_SIZE);//默认DEFAULT_NETWORK_THREAD_POOL_SIZE=4
      }  

其主要工作有:初始化Disk Cache缓存路径、执行网络请求接口NetWork、网络请求调度器NetworkDispatcher和请求结果派送者ResponseDelivery.

在上述创建过程中,无论是CacheDispatcher还是NetworkDispatcher都是继承至Thread.

public class NetworkDispatcher extends Thread {
  ...//省略代码
}
public class CacheDispatcher extends Thread {
  ...//省略代码
}

ResponseDelivery其实是一个接口,其具体实现类似ExecutorDelivery,其构造函数的参数是一个Handler,而Handler的构造函数参数则是Lopper.getMainLooper(),所以这里其实是应用的主线程Looper,也就是说handler其实就是主线的Handler,其作用就是将请求的结果(正确的或者错误的)传输给主线程.

/**
 * 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);
            }
        };
    }
    ...//省略代码
  }

在创建完成RequestQueue对象之后会调用start方法启动所有的dispatcher(CacheDispatcher和NetworkDispatcher):

  /**
  * start方法的作用就是启动所有的dispatcher(CacheDispatcher和NetworkDispatcher)
  */
 public void start() {
     stop();  //保证当前所有的正在运行的Dispatcher都停止
     //创建缓存的调度器(也是一个线程),并启动线程
     mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
     mCacheDispatcher.start();

     //根据线程池的大小,创建相对应的NetworkDispatcher(线程),并启动所有的线程
     for (int i = 0; i < mDispatchers.length; i++) {
         NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                 mCache, mDelivery);
         mDispatchers[i] = networkDispatcher;
         networkDispatcher.start();
     }
 }

对应start方法当然就有stop方法,用于停止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();
           }
       }
   }

在start过程中,出现mCacheQueue, mNetworkQueue,这两个的定义如下:

//带有优先级cache请求队列
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
   new PriorityBlockingQueue<Request<?>>();

/** The queue of requests that are actually going out to the network. */
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
   new PriorityBlockingQueue<Request<?>>();

可以看到它们其实都是Java并发(Concurrent)包中提供的利用优先级来执行的阻塞队列PriorityBlockingQueue.显然它们就应该是用来放置从外面传进来的请求,比如JsonRequest,ImageRequest和 StringRequest.

在上述分析了怎么启动怎么停止,那么当创建好Request对象时,怎么样才能添加到请求队列中呢?RequestQueue提供了一个add方法,用于将创建好的Request添加到请求队列中.并判断请求的是否存储于缓存中来进行分类.

public <T> Request<T> add(Request<T> request) {
  // 将请求所在队列设置为当前队列,并将请求添加到mCurrentRequests中,表明是正在处理中,并且这里用synchronized来同步
  request.setRequestQueue(this);
  synchronized (mCurrentRequests) {
      mCurrentRequests.add(request);
  }
   //在这里会设置序列号,保证每个请求都是按顺序被处理的。
  request.setSequence(getSequenceNumber());
  request.addMarker("add-to-queue");

  // 如果这个请求是设置不缓存的,那么就会将其添加到mNetworkQueue中,直接去网络中获取数据
  if (!request.shouldCache()) {
      mNetworkQueue.add(request);
      return request;
  }

  //到这里,表明这个请求可以去先去缓存中获取数据。
  synchronized (mWaitingRequests) {
      String cacheKey = request.getCacheKey();//key是一个字符串,由Method + ":" + Url组成,默认是Url作为cacheKey
      if (mWaitingRequests.containsKey(cacheKey)) {.
          //如果这个请求已经有一个相同的请求(相同的CacheKey)在mWatingRequest中,
          //那么就要将相同CacheKey的请求用一个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 {
          //如果mWaitingRequest中没有,那么就将其添加到集合中,将添加到mCacheQueue队列中,表明现在这个cacheKey的请求已经在处理了.
          mWaitingRequests.put(cacheKey, null);
          mCacheQueue.add(request);
      }
      return request;
  }
}

当mCacheQueue或者mNetworkQueue利用add方法添加请求之后,在运行的线程就会接收到请求,从而去处理相对应的请求,最后将处理的结果由mDelivery来发送到主线程进行更新.

当请求在缓存线程中或者是在网络线程中处理完成后,每个Request都会去调用对应的finish方法,

 void finish(final String tag) {
   if (mRequestQueue != null) {
           mRequestQueue.finish(this);
           onFinish();
       }
    ...//省略代码
 }

下一步会调用RequestQueue的finish()方法:

<T> void finish(Request<T> request) {
     //1、从当前队列中移除对应的请求
     synchronized (mCurrentRequests) {
         mCurrentRequests.remove(request);
     }
     synchronized (mFinishedListeners) {
       for (RequestFinishedListener<T> listener : mFinishedListeners) {
         listener.onRequestFinished(request);//请求结束监听
       }
     }
     //2->
     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);
             }
         }
     }
 }

第2步中,就是判断这个请求有没有缓存,1)如果有,那么我们这个时候,将前面mWaitingQueue中相同CacheKey的一大批请求再扔到mCacheQueue中,为什么现在才扔呢?因为前面我们不知道相同CacheKey的那个请求到底在缓存中有没有;2)如果没有,它需要去网络中获取,那就等到它从网络中获取之后,放到缓存中后,它结束后且已经缓存了,这个时候,我们就可以保证后面那堆相同CacheKey的请求可以在缓存中去取到数据了,而不需要再去网络中获取了.

最后在RequestQueue中还提供了2个方法用于用户自己随时可控地取消请求:

//根据顾虑规则进行请求的取消
public void cancelAll(RequestFilter filter) {
       synchronized (mCurrentRequests) {
           for (Request<?> request : mCurrentRequests) {
               if (filter.apply(request)) {
                   request.cancel();
               }
           }
       }
   }

 /**
  * 根据请求的Tag进行取消
  */
 public void cancelAll(final Object tag) {
     if (tag == null) {
         throw new IllegalArgumentException("Cannot cancelAll with a null tag");
     }
     cancelAll(new RequestFilter() {
         @Override
         public boolean apply(Request<?> request) {
             return request.getTag() == tag;
         }
     });
 }

RequestFilter是RequestQueue的内部接口,没有具体实现,上面第二个cancleAll方法的过滤规则就是看Tag是否相等.

到此RequestQueue的源代码核心内容就已经分析完成了.

2.3 Volley之Request

Volley提供的Request有:StringRequest、JsonArrayRequest、JsonObjectRequest、ImageRequest和JsonRequest,其中JsonArrayRequest、JsonObjectRequest继承至JsonRequest,StringRequest、ImageRequest、JsonRequest继承至Request.Volley还可以自定义Request.

Request是一个抽象类.提供了很多的方法,子类需要实现的两个抽象方法为:

    /**
      子类必须实现这个方法,用于将解析response后的数据传递给request的监听者,解析失败则不会传递.
     */
    abstract protected void deliverResponse(T response);

    /**
    子类必须实现该方法,用于解析网络请求返回结果response并返回一个恰当的response-type,这个方法将被一个工作线程调用.
    * @return The parsed response, or null in the case of an error
    */
   abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

具体使用已在前一章讲过,无论是Volley中提供的request还是我们自定义的request,都会通过各自的解析方法解析自定义的数据.

2.4 HttpStack

前面准备了那么多,为了就是进行网络请求,那具体的网络请求是怎么样子的呢?

具体的网络请求实现是在HurlStack/HttpClientStack中实现的,还记得在Volley中创建请求队列时对当前系统版本进行判断,为了就是获取不同网络请求框架:

if (stack == null) {
   if (Build.VERSION.SDK_INT >= 9) {//若版本大于9(Android2.3)则使用HurlStack,其中使用的是HttpURLConnection进行网络请求
       stack = new HurlStack();
   } else {
      ////若版本小于9(Android2.3)则使用HttpClientStack其中使用的是HttpClient进行网络请求
       stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
   }
}
//再通过NetWork类进行调用具体的网络请求实现类
Network network = new BasicNetwork(stack);

其中NetWork是一个接口,它只包含一个方法: public NetworkResponse performRequest(Request

public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) {//...省略代码}
//具体网络请求实现调用
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
   long requestStart = SystemClock.elapsedRealtime();
   //...省略代码
   // 添加头部信息
   Map<String, String> headers = new HashMap<String, String>();
   addCacheHeaders(headers, request.getCacheEntry());
   httpResponse = mHttpStack.performRequest(request, headers);//调用真正的网络请求类
   //各种网络请求返回值判断

   //结果返回没有出错的网络请求返回数据
   return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                      SystemClock.elapsedRealtime() - requestStart);
}

NetworkResponse相当于一个Bean,其作用是将网络请求返回的数据、各种状态码、请求消耗时间等进行封装.

2.5 NetworkDispatcher

网络请求了解了,那管理网络请求的线程呢?其实就是NetworkDispatcher,当用户提交request之后,若CacheDispatcher中没有对应的请求缓存,就必须要进行网络请求,那这时的就需要NetworkDispatcher线程来进行管理了.

@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 {
            // 从队列中获取一个请求,若没有则一直阻塞
            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);

            // 调用mNetwork去跟网络打交道
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");

           // 如果服务器返回一个未修改(304)的响应,并且这个请求已经发送过响应对象,不需要再继续,因为没改过
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            // 解析响应的数据,返回Response对象
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

          // 根据request的shouldCache字段来判断是不是需要缓存,如果需要,则将其放到mCache中。
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

           // 调用 mDelivery将Response对象传回主线程进行UI的更新。
            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,将错误信息传回到主线程,进行提示
            mDelivery.postError(request, volleyError);
        }
    }
}

NetworkDispatcher线程主要完成了:

1) 调用 mQueue的take()方法从队列中获取请求,如果没有请求,则一直阻塞在那里等待,直到队列中有新的请求到来。

2) 判断请求有没有被取消,如果被取消,则重新获取请求。

3) 调用Network对象将请求发送到网络中,并返回一个 NetworkResponse对象。

4) 调用请求的pareseNetworkResonse方法,将NetworkResponse对象解析成相对应的Response对象。

5) 判断请求是否需要缓存,如果需要缓存,则将其Response中cacheEntry对象放到缓存mCache中。

6) 调用 mDelivery将Response对象传到主线程中进行UI更新。

Volley框架中主要的类和流程就如上面所述,其中还有很多小细节没有描述,但是根据Volley使用的流程完全能够看得懂其源代码.

时间: 2024-10-10 21:13:26

Volley的使用以及源码分析(二)的相关文章

[Android]Volley源码分析(二)Cache

Cache作为Volley最为核心的一部分,Volley花了重彩来实现它.本章我们顺着Volley的源码思路往下,来看下Volley对Cache的处理逻辑. 我们回想一下昨天的简单代码,我们的入口是从构造一个Request队列开始的,而我们并不直接调用new来构造,而是将控制权反转给Volley这个静态工厂来构造. com.android.volley.toolbox.Volley: public static RequestQueue newRequestQueue(Context conte

netty 源码分析二

以服务端启动,接收客户端连接整个过程为例分析, 简略分为 五个过程: 1.NioServerSocketChannel 管道生成, 2.NioServerSocketChannel 管道完成初始化, 3.NioServerSocketChannel注册至Selector选择器, 4.NioServerSocketChannel管道绑定到指定端口,启动服务 5.NioServerSocketChannel接受客户端的连接,进行相应IO操作 Ps:netty内部过程远比这复杂,简略记录下方便以后回忆

哇!板球 源码分析二

游戏主页面布局 创建屏下Score标签 pLabel = CCLabelTTF::create("Score", "Arial", TITLE_FONT_SIZE); //分数标签 //设置标签字体的颜色 pLabel->setColor (ccc3(0, 0, 0)); //设置文本标签的位置 pLabel->setPosition ( ccp ( SCORE_X, //X坐标 SCORE_Y //Y坐标 ) ); //将文本标签添加到布景中 this

baksmali和smali源码分析(二)

这一节,主要介绍一下 baksmali代码的框架. 我们经常在反编译android apk包的时候使用apktool这个工具,其实本身这个工具里面对于dex文件解析和重新生成就是使用的baksmali 和smali这两个jar包其中 baksmali是将 dex文件转换成便于阅读的smali文件的,具体使用命令如下:java -jar baksmali.jar classes.dex -o myout其中myout是输出的文件夹 而smali是将smali文件重新生成回 dex文件的具体使用的命

【梦幻连连连】源码分析(二)

转载请注明出处:http://blog.csdn.net/oyangyufu/article/details/24736711 GameLayer场景界面效果: 源码分析: GameLayer场景初始化,主要是初始化加载界面及背景音乐 bool GameLayer::init() { float dt=0.0f; if ( !CCLayerColor::initWithColor(ccc4(255, 255, 255, 255))) { return false; } this->initLoa

[Android]Fragment源码分析(二) 状态

我们上一讲,抛出来一个问题,就是当Activity的onCreateView的时候,是如何构造Fragment中的View参数.要回答这个问题我们先要了解Fragment的状态,这是Fragment管理中非常重要的一环.我们先来看一下FragmentActivity提供的一些核心回调: @Override protected void onCreate(Bundle savedInstanceState) { mFragments.attachActivity(this, mContainer,

JAVA Collection 源码分析(二)之SubList

昨天我们分析了ArrayList的源码,我们可以看到,在其中还有一个类,名为SubList,其继承了AbstractList. // AbstractList类型的引用,所有继承了AbstractList都可以传进来 private final AbstractList<E> parent; // 这个是其实就是parent的偏移量,从parent中的第几个元素开始的 private final int parentOffset; private final int offset; int s

Tomcat源码分析二:先看看Tomcat的整体架构

Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Connector.Container等组件,接下来我们一起去大致的看看这些组件的作用和他们之间的相互联系.在这之前,我们先补充一个知识点,也就是Tomcat它实现的功能点是什么呢?通过查找一些资料,这里参考下极客时间<深入拆解Tomcat_Jetty>中的总结,即Tomcat 要实现 2 个核心功能:

Volley源码分析二

在前两天我发布的文章:Volley源码分析一 中我较为详细的分析了Volley,今天继续,这篇文章会讲一些上一篇没有提到的比较细节的点,以及对于Volley源码中一些可以优化的实现的一些思考 ByteArrayPool的分析 byte[] 的回收池,用于 byte[] 的回收再利用,减少了内存的分配和回收.主要通过一个元素长度从小到大排序的ArrayList作为 byte[] 的缓存,另有一个按使用时间先后排序的ArrayList属性用于缓存满时清理元素. public synchronized

3.算子+PV&amp;UV+submit提交参数+资源调度和任务调度源码分析+二次排序+分组topN+SparkShell

1.补充算子 transformations ?  mapPartitionWithIndex 类似于mapPartitions,除此之外还会携带分区的索引值. ?  repartition 增加或减少分区.会产生shuffle.(多个分区分到一个分区不会产生shuffle) 多用于增多分区. 底层调用的是coalesce ?  coalesce(合并) coalesce常用来减少分区,第二个参数是减少分区的过程中是否产生shuffle. true为产生shuffle,false不产生shuff