Volley 源码笔记(2)

36 篇文章 0 订阅
24 篇文章 0 订阅
  1. DiskBasedCache implements Cache:

    • 顾名思义,这个Cache系统是基于磁盘的,cache文件都保存在磁盘上.
    • DiskBasedCache接受一个File对象(cache文件存放的磁盘目录)和一个int(最大的cache字节数)作为自己的构造参数.
    • initialize()会对cache目录进行初始化,如果cache目录不存在,那么就会创建一个, 然后开始扫描cache目录下的文件, 获取每个文件所包含的cache header信息并保存在CacheHeader对象中, 然后使用CacheHeader的key作为key将CacheHeader加入到mEntries这个cacheHeader内存索引缓存的Map中, 这个Map只保存header信息.
    • get(String key): 就是根据跟定的cache的key获得相应的cache磁盘文件内容映射成的内存数据结构. 首先在内存中的cacheheader索引表中查找key对应的CahceHeader, 得到相应的CacheHeader, 然后根据key构造出cache在disk上的文件名,并根据这个文件名构造一个File对象,有了对应于磁盘文件的File,就可以将磁盘缓存file的内容读取出来并且转换为byte数组了,最后调用CacheHeader的toCacheEntry(data)将body信息加入到entry中,返回一个已经填充了所有信息的cache内存结构体. 如果中间出了任何的IO异常,那么就认为这个磁盘上的cache文件已经失效了,会将其删除.
    • put(String key, Entry entry): 就内存中的cache以key作为索引持久化到磁盘上, 之前会先检测一次当前缓存的内容大小是否已经超限,如果超限,会删除一些现有的缓存,然后根据key为新的cache生成一个磁盘文件名并构造File对象,再根据key和entry构造一个CacheHeader,随后将其信息写入到file中,然后就是将entry的data全部写入到file中,最后将Cacheheader加入到内存的CacheHeaderMap中.中间有任何异常都会删除这个新的cache文件.
    • remove(String key): 很简单,删除key对应的file以及在CacheHeaderMap中的CacheHeader.
    • getFilenameForKey(String key): 就是根据给的key值生成一个独特的文件名,很简单,基本就是两段hashcode的拼接.
    • CountingInputStream extends FilterInputStream是内部使用的一个读取文件的stream类,很简单,就是在read(….)的时候会记录下已经read了多少个byte并更新在一个内部变量中, 这个变量会被调用者使用.
    • 基本上涉及到这种cache存取文件的,都会自定义一堆 的write/readItn/Long/…等函数,因为int/long这种的不能直接写入,都要规定好其占用的byte数,然后将其拆为byte序列化然后分别写入. 对于String这一种的,需要在最开始就写入string在某种charset下所占据的byte数(其实就是String的getBytes(Charset)函数),然后才能将其对应的byte[]写入. 并且一般来说,甚至不同类型的写入顺序和读取顺序也是hardcode和数据结构绑定的.
    • CacheHeader除了保存response的header外,还会保存跟cache-control有关的信息: 比如etag/last-modified等. 以及该CacheHeader所标识的entry占用的byte大小.
  2. RequestQueue: 也是一个很简单的类:

    • RequestQueue接受一个Cache和Network对象作为构造参数, 并可以选择性的定制线程池的大小(不指定则为默认的DEFAULT_NETWORK_THREAD_POOL_SIZE). 最完整的构造函数则还接受一个ResponseDelivery对象(负责将dispatcher得到的response投递出去, 算是一种通用设计,worker线程在做完某个task以后,本身不负责task结果的投递,而是由专门的一个类来统一的进行结果的投递),在构造时还会根据theadPoolSize来构造一个NetworkDispatcher数组(真正进行网络交互的线程单元). 这些都会维护在RequestQueue的m成员变量内.
    • start()函数在最开始会先stop来确保之前正在run的dispatcher都被停止. 根据传入的信息重新构造一个CacheDispatcher并将其start(CacheDispatcher也是一个Dispatcher extends Thread, 和正常的处理网络请求的dispatcher的区别在于,它负责针对那些有可能直接从Cache中取出信息回复的request, 这些Request会先加入到这个Dispatcher中进行取Cache的尝试). 最后根据NetworkDispatcher数组d的size构造相应数量的NetworkDispatcher(这些dispatcher就是真正的负责将request发出并处理response的thread)并填入到数组中然后start.
    • stop()函数会将mCacheDispatcher quit然后将全部的NetworkDispatcher 进行quit.
    • mCacheQueue是一个优先级阻塞队列,负责将需要cache的Request进行cache..
    • mNetworkQueue是一个优先级阻塞队列,为了支持Request的优先级, 里面存放的是要发送出去被处理的Request对象, 这类Request还没有被Dispatcher take出来进行处理, add操作以及dispatcher的take操作针对的都是这个Queue.
    • mCurrentRequests是一个set,其代表的更为广泛,包括了还在Queue中等待的Request以及正在被Dispatcher处理并且没有finish的Request, 只有finish()才会将Request从这个set中remove.
    • mWaitingRequests则是针对那些有可能直接使用cache做为Response的Request来说的,这个Map中的某个Key的成员代表的是这些具有相同的CacheKey的Request,当前有正在被处理的的相同CacheKey的Request在进行中(in flight), 那么这些Request会先保存在这个Map中(如果一个Key有多个,会组成List), 这么做的原因是对于这种情况: 一个request的response正在被CacheDispacther/NetworkDispatcher进行处理(这一步包括了Cache的生成和更新), 这时候又来了相同cacheKey的request,那么应该等当前正在进行(in-flight)的request完成,Cache生成了,才能为下一个相同CacheKey的Request 直接使用这个Cache,才可以将其放入mCacheQueue中,所以会将其加入这个临时的等待队列.
    • add(Request): 首先会将request的RequestQueue设置为该queue(setRequestQueue(this)), 然后将该request加入到mCurrentRequests中, 为该request设置一个独特的递增SeqNumber(getSequenceNumber(), 其实是一个AtomicInteger的incrementAndGet()), 并为其增加一个 “add-to-queue”的marker. 如果这个reuqest是不可以cache(shouldCache()),那么就可以直接加入到mNetworkQueue中然后返回即可,因为没有cache可用,所以就老老实实的请求吧. 否则这个Request是可以cache的,那么就可以尝试看看能不能直接用之前cache的信息直接作为response, 那么会获取request的cacheKey, 如果在当前的mWaitingRequests已经有了这个key, 那么会将这个request加入到这个key对应的List中. 否则直接将这个reuqest加入到mCacheQueue中, 并且在mWaitingRequests加入这个CacheKey来表示当前已经有了一个这样CacheKey的Request在进行中.
    • cancelAll(final Object tag)接受一个filter(RequestFilter)或者一个tag作为过滤条件,会遍历mCurrentRequests, 将满足条件的request直接调用cancel().
  3. NetworkDispatcher extends Thread负责就是dispacth以及执行 queue中的Request, 其实dispatcher的工作做的很简单,就是一堆的thread一起竞争式的从一个blocingList中take元素,谁take到了谁来做处理:

    • 其在构造时会被传入RequestQueue的mNetworkQueue/mNetwork/mCache/mDelivery.
    • 其主体自然就是run()函数,因为需要处理源源不断的request,所以在最外边的是一个while true, 通过对mNetworkQueue.take()来进行同步阻塞式的取出**Request, 中间如果被Interrupt,会check是否是被**Quit()引起的(mQuit, 如果是,会直接return,否则这次中断会被忽略), 然后为request增加一个”network-queue-take”的marker, 如果的这时候发现request已经被cancel了,那么会调用reuqest的finish函数并附带被cancel这个信息. 为了统计,还会为其加上流量统计的tag, 至于真正的执行网络请求和回复的处理,则是直接由传入的mNetwork来代劳了,后者会在处理完以后返回一个NetworkResponse或者抛出一个异常, 对于正常完成的,会为request加上”network-http-complete”的marker, 如果发现返回的是304并且之前也有cache可以用,那么就直接finish(“”not-modified”“), 进一步使用request自己的parseNetworkResponse来对mNetwork返回的response进行分析得到一个Response(这是调用者自由发挥的一个地方),如果标识了这个request应该被cache, 那么机会将这个respone和reuqets的cacheKey一起加入传入的mCache中. 最后将requestmark为delivered, 然后才会将Respone通过传入的mDelivery传递出去. 中间有任何VolleyError异常都会调用parseAndDeliverNetworkError(…), 而对于其他的Exception, 则是根据其再构造一个VolleyError然后交给mDelivery投递出去.
    • parseAndDeliverNetworkError()这个函数其实只是给了Request一次自己parse并重新定义error的回调(request.parseNetworkError(error)), 然后将reuqest重定义的VolleyError通过mDelivery投递出去.
  4. ResponseDelivery是一个接口,其定义的方法也主要就是postResponse和postError,在RequestQueue中实际使用的是ExecutorDelivery(new Handler(Looper.getMainLooper())), 可以看到这里的respone的delivery都是在主线程进行的.

    • ExecutorDelivery内部会有一个Executor: mResponsePoster,构造时被创建 而这个Executor的execute()函数很简单,在构造时传入的handler中post Runnable
    • postResponse/Error其实就是调用这个Executor的execute(),就是将传入的Runnable post到构造时制定的handler中(这里是主线程的handler).
    • 在postResponse/Error时会为request增加一个”post-response”/”post-error”的marker.
    • 这里execute(…)传入的Runnable是自定义的ResponseDeliveryRunnable:
      • 其接受的构造参数有Request/Response/Runnable.
      • run()(运行在构造传入的handler中,在这里是主线程): 如果发现Request被cancel了,那么会调用那个Request的finish(“canceled-at-delivery”), 如果Response是success那么mRequest.deliverResponse(mResponse.result),失败则mRequest.deliverError(mResponse.error), 最后如果传入了一个有效的Runnale,那么会执行这个Runnable.
  5. Request:

    • 看样子addMarker纯粹是为了记录log,搞个这样的方法的作用是打log比较方便,会自动将this打进去, 是个值得学习的trick.
    • setShouldCache(boolean shouldCache): 这个request的response是否可以被cache.
    • 定义了enum的Priority,四档, 这也是RequestQueue**使用PriorityBlockingQueue的原因, 并且为了实现这个差异化,Request还实现了Comparable这个接口**.
    • Request默认的编码使用的是 UTF-8.
    • Request要求在构造的时候就提供method和url, 以及一个可选的errorListener, 在构造时就会为自己设置一个DefaultRetryPolicy.
    • Request的finish()函数其实还是调用了自己所在的RequestQueue(setRequestQueue(…)设置的)来将自己finish的, 后面的全部是记录log.
    • getParams()这里返回的是null,留待子类做自己的扩展.
    • getBody()返回Post或者Put请求的原生body内容,对getParams()的结果进行urlencoding(这里使用的是默认的encoding: DEFAULT_PARAMS_ENCODING = “UTF-8”).
    • encodeParameters(…)就是根据各处的param 参数map和encoding方案对每个param进行urlencode,并且按照url的参数形式拼接起来.
    • 还有一堆getPostBody()/ContentType()但应全部deprecated, 因为加入了对PUT的支持,所以都被getBody()/ContentType()…..这类函数代替.
    • Request的mShouldCache标识了这个Request是否可以使用之前Cache的内容,默认值为true,可以设置
  6. StringRequest extends Request String:

    • 这里的String指的是其parseNetworkResponse中会将输入的NetworkResponse转化为String, 转化也很简单,直接将response的data作为内容,按照response的header中制定的Charset构造一个String.
    • 外部调用者想实现自己的回调就是通过构造时传入的Listener和errorListener实现的.
  7. RequestQueue的finish()的作用就是将某个Request**从mCurrentRequests这个代表当前所有还没有被处理或者等待处理的Request的队列中remove**,并且调用相关的listener的onRequestFinished, 最后check这个request的response是否是被cache了,如果被Cache了,那么就可以处理在waitingRequests中等待这个Cache完成的Request了,waitingRequests会被加入到mCacheQueue中来尝试直接使用这个Request返回的Response的Cache作为自己的Response .

  8. CacheDispatcher extends Thread, 这个dispatcher之前已经解释过了,两重作用,尝试为某个request取cache以及万一取不到的planB.

    • CacheDispatcher与NetworkDispacther在构造和流程上基本是一样,差别的只是run的while true循环里的具体操作:
      • 首先会从mCacheQueue中阻塞take出一个Request进行操作,首先检查是否已经被Cancel, 如果被Cancel, 那么直接调用其finish.
      • 然后根据request的cacheKey获得一个Cache Entry, 如果没有对应的Cache, 那么就需要老老实实的发起网络请求了,会把这个request**加入到mNetworkQueue**中来进行http请求.
      • 如果发现取得的Cache已经过期了,那么也需要重新发起httpRequest,加入到mNetworkQueue中.
      • 如果Cache是可用的,那么会基于Cache构造一个Response,然后直接交给Delivery进行结果的投递.
      • Volley在这里还加入了一个soft-expire的概念,这种情况说的是在返回Cache的同时还会返回发起一个网络请求来验证Cache的有效性. 这是由Cache的refreshNeeded()来决定的.
  9. 从线程结构上看,Volley的线程模型还是很简单的,就是一个典型的Woker模式,并且CacheDispather还只有一个(不知道会不会成为性能瓶颈, 不过考虑到它完成的一般是本地磁盘IO,比网络IO还是快很多的,因此一个应该够用),多个NetworkDispather线程之间的其实是没有所谓的”负载平衡的”,只是很朴素的抢占式竞争任务. 而从网络方面看,Volley是直接用了现成的HttpUrlConnection完成了http请求的发出和接收(够用了),在Cache管理方面也是一个基础的实现,所以Volley可以认为还是一个很简单的结构, 本质上讲, volley是一个类似于squid, pollip这样的代理服务器,但是复杂性显然差远了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值