Android网络框架源码分析一---Volley

转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium=mobile_author_hots&utm_source=recommendation

公司最近新起了一个项目,对喜欢尝鲜的我们来说,好处就是我们可以在真实的项目中想尝试一些新技术,验证想法。新项目对网络框架的选取,我们存在三种方案:

1.和我们之前的项目一样,使用Loader + HttpClient + GreenDao + Gson + Fragment,优点是可定制性强,由于使用Google家自己的LoaderLoaderManager,代码健壮性强。
缺点是整套代码学习成本较高,使用过程中样板代码较多,(比如每一个Request都需要产生一个新类)
2.Volley,作为GoogleIO大会上得瑟过的一个网络库,其实不算什么新东西(2013 IO发布),使用较为简单,请求可以取消,可以提供优先级请求,看起来还是不错的。
3.Retrofit,一款为了使请求极度简单化的REST API Client,呼声也很高,使用门槛几乎是小白型。

如何选择呢?首先干掉1,因为对新人的学习成本确实太高,如果要快速开发一个项目,高学习成本是致命的,同时使用起来样板代码很多。

那么如何在VolleyRetrofit中选择呢?尽管网上有很多文章在介绍两个框架的使用方法,而对于其原理,特别是对比分析较少,如果你手里有一个项目,如何选择网络模块呢?
这里将分两篇文章从源码的角度对比分析这两个开源框架,希望能对你有所帮助。这是上篇,下篇Retrofit在这里,其中有干货总结哦~。

需要注意的是,这两篇文章并不会是一个入门使用的帮助文档,建议你在看本文的时候,最好看过官方文档和DEMO。

首先说明一下这两个网络框架在项目中的层次:

Paste_Image.png

从上图可知,不管Volley还是Retrofit,它们都是对现有各种方案进行整合,并提供一个友好,快速开发的方案,在整合过程中,各个模块都可以自行定制 或者替换。比如反序列化的工作,再比如HttpClient。

需要注意一点的是,这两个开源框架都没有实现Http栈,Http栈作为一个可替换的模块在两个框架中存在(Retrofit 2.0版本仅支持OkHttpClient)。Http栈在客户端常见的实现有 apacheHttpClient 和 squareOkHttpClient,其中apache HttpClient不开源,而OkHttpClient是开源的,GitHub地址

Volley 源码分析

Volley现在已经被官方放到AOSP里面,已经逐步成为Android官方推荐的网络框架。

类抽象

  1. Http协议的抽象
    Requeset
    顾名思义,对请求的封装,实现了Comparable接口,因为在Volley中是可以指定请求的优先级的,实现Comparable是为了在Request任务队列中进行排序,优先级高的Request会被优先调度执行。
    NetworkResponse
    Http响应的封装,其中包括返回的状态码 头部 数据等。
    Response
    给调用者返回的结果封装,它比NetworkResponse更加简单,只包含三个东西:数据 异常 和 Cache数据。
    Network
    HttpClient的抽象,接受一个Request,返回一个NetworkResponse
  2. 反序列化抽象
    所谓反序列化,就是将网络中传输的对象变成一个Java对象,Volley中是通过扩展Request类来实现不同的反序列化功能,如JsonRequest StringRequest,我们也可以通过自己扩展一些Request子类,来实现对请求流的各种定制。
  3. 请求工作流抽象

RequestQueue
用来管理各种请求队列,其中包含有4个队列
a) 所有请求集合,通过RequestQueue.add()添加的Request都会被添加进来,当请求结束之后删除。
b) 所有等待Request,这是Volley做的一点优化,想象一下,我们同时发出了三个一模一样的Request,此时底层其实不必真正走三个网络请求,而只需要走一个请求即可。所以Request1add之后会被调度执行,而Request2 和Request3被加进来时,如果Request1还未执行完毕,那么Request2和 Request3只需要等着Request1的结果即可。
c) 缓存队列,其中的Request需要执行查找缓存的工作
d) 网络工作队列 其中的Request需要被执行网络请求的工作

NetworkDispatcher
执行网络Request的线程,它会从网络工作队列中取出一个请求,并执行。Volley默认有四个线程作为执行网络请求的线程。

CacheDispatcher
执行Cache查找的线程,它会从缓存队列中取出一个请求,然后查找该请求的本地缓存。Volley只有一个线程执行Cache任务。

ResponseDelivery
请求数据分发器,可以发布Request执行的结果。

Cache
Cache的封装,主要定义了如何存储,获取缓存,存取依据Request中的getCacheKey()来描述。

提交请求

Volley通过RequestQueue.add(Request)来往任务队列中增加请求:

Paste_Image.png

一个Request被提交之后有几个去处:

1.Set<Request<?>> mCurrentRequests对应所有请求队列。所有调用addRequest必然都会添加到这里面来。
2.PriorityBlockingQueue<Request<?>> mNetworkQueue 对应网络队列。如果一个Request不需要缓存,那么add之后会被直接添加到网络队列中。
3.PriorityBlockingQueue<Request<?>> mCacheQueue对应缓存请求。如果一个Request需要缓存,并且当前的RequestQueue中并没有一个RequestgetCacheKey和当前Request相同(可以认为一个请求),那么加入缓存队列,让缓存工作线程来处理。
4.Map<String, Queue<Request<?>>> mWaitingRequests对应等待队列。如果RequestQueue中已经有一个相同请求在处理,这里只需要将这个Request放到等待队列中,等之前的Request结果回来之后,进行处理即可。

Volley提交任务到队列中是不是很简单?下面来说说优先级请求的事情吧,你可能已经注意到了,上面两个存放需要执行任务的队列都是PriorityBlockingQueue,前面说了Request现实了Comparable,看看这个方法:

@Override
public int compareTo(Request<T> other) {
    Priority left = this.getPriority();
    Priority right = other.getPriority();
      //mSequence表示请求序列号,add时,会通过一个计数器来指定
       return left == right ?
        this.mSequence - other.mSequence :
        right.ordinal() - left.ordinal();
}

所以,如果我们的工作线程(NetworkDispatcher,CacheDispatcher)取任务时,自然会从头部开始取。

这里的优先级,仅仅是保证一个请求比另外一个请求先处理,而并不能保证一个高优先级请求一定会比低优先级的请求先回来

缓存工作线程处理

 @Override
  public void run() {
    //初始化Cache
    mCache.initialize();
    Request<?> request;
    while (true) {
            //阻塞  获取一个Cache任务
            request = mCacheQueue.take();
        try {
            //已经被取消
             if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                continue;
            }
            //如果拿cache未果,放入网络请求队列
            Cache.Entry entry = mCache.get(request.getCacheKey());
            if (entry == null) {
                request.addMarker("cache-miss");
                mNetworkQueue.put(request);
                continue;
            }
            //缓存超时,放入网络请求队列
            if (entry.isExpired()) {
                request.addMarker("cache-hit-expired");
                request.setCacheEntry(entry);
                mNetworkQueue.put(request);
                continue;
            }
            //根据Cache构造Response
            Response<?> response = request.parseNetworkResponse(
                    new NetworkResponse(entry.data, entry.responseHeaders));
            //是否超过软过期
            if (!entry.refreshNeeded()) {
                // 直接返回Cache
                mDelivery.postResponse(request, response);
            } else {
                request.setCacheEntry(entry);
                //设置中间结果
                response.intermediate = true;
                //发送中间结果
                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) {

        }
    }
}

这里可以看到Volley确实对缓存封装很到位,各种情况都考虑到了,其中比较重要的两点:

  1. 取出来的Cache并不仅仅是数据,同时还包括这次请求的一些Header
  2. 硬过期 软过期
    我们可以看到Cache中有两个字段来描述缓存过期: Cache.ttl vs Cache.softTtl。什么区别呢?如果ttl过期,那么这个缓存永远不会被使用了;如果softTtl没有过期,这个数据直接返回;如果softTtl过期,那么这次请求将有两次返回,第一次返回这个Cahce,第二次返回网络请求的结果。想想,这个是不是满足我们很多场景呢?先进入页面展示缓存,然后再刷新页面;如果这个缓存太久了,可以等待网络数据回来之后再展示数据,是不是很赞?

NetworkDispatcher

执行网络请求的工作线程,默认有4个线程,它不停地从网络队列中取任务执行。

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) {
            if (mQuit) {
                return;
            }
            continue;
        }

        try {
            request.addMarker("network-queue-take");
            //取消
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                continue;
            }
            //通过Http栈实现客户端发送网络请求
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");

            // 如果缓存软过期,那么会重新走网络;如果server返回304,表示上次之后请求结果数据本地并没有过期,所以可以直接用本地的,因为之前Volley已经发过一次Response了,所以这里就不需要再发送Response结果了。
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                continue;
            }

            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");
            //更新缓存
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }
            //发送结果
            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);
        }
    }
}

Request

Request中主要封装了一个请求的各类Http协议信息,比如 URL,请求方法,请求的优先级,请求重试的策略,缓存策略等。

这里说一下其中比较有意思的重发策略,如果一次请求发生超时异常,比如SocketTimeoutException ConnectTimeoutException ,我们可以为Request配置一个RetryPolicy,你可以指定重发这个Request的次数,以及每次失败之后重新设置这个请求的超时时间(第一次失败之后,你可以调整第二次请求的超时时间增加,以减少失败的可能性)。

反序列化

Request最重要的功能就是提供了内容的反序列化,通过不同的子类来实现不同的序列化功能。比如,如果请求结果是一个Json的对象,我们可以使用JsonObjectRequest,如果是一个普通字符,使用StringRequest,同时,我们也可以很方便的定制自己的Request,通过复写Response<T> parseNetworkResponse(NetworkResponse response);方法即可。

默认的JsonRequest使用org.json中的Json解析,我们使用Gson来进行解析能够构造一个更加通用的处理json返回的Request

  public class JsonGRequest<T> extends Request<T> {

private static Gson gson = new Gson();

private Response.Listener<T> mListener;

public JsonGRequest(String url, Response.ErrorListener listener,Response.Listener responseListener) {
    super(url, listener);
    this.mListener = mListener;
}

public JsonGRequest(int method, String url, Response.ErrorListener listener) {
    super(method, url, listener);
}

@Override
protected Response<T> parseNetworkResponse(NetworkResponse response) {
    return Response.success(gson.fromJson(new InputStreamReader(new ByteArrayInputStream(response.data)),getType()), HttpHeaderParser.parseCacheHeaders(response))
}

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

//获取指定的泛型类型
 protected Type getType() {
    Type superclass;
    for(superclass = this.getClass().getGenericSuperclass(); superclass instanceof Class && !superclass.equals(JsonGRequest.class); superclass = ((Class)superclass).getGenericSuperclass()) {
        ;
    }

    if(superclass instanceof Class) {
        throw new RuntimeException("Missing type parameter.");
    } else {
        ParameterizedType parameterized = (ParameterizedType)superclass;
        return parameterized.getActualTypeArguments()[0];
    }
}
}

ImageRequest

Volley专门为图片请求提供了ImageRequest,主要是反序列化了一下数据流到BitMap,还可以制定图片的大小,质量等参数。

ImageLoaderVolley提供的一个用来加载图片的工具,它的内部还是使用ImageRequest来实现的,主要新加的功能是增加了内存缓存,你可以通过配置ImageCache来设置内存缓存。

小结

Volley整体代码还是比较简单,思路明确,而且提供了不错的可扩展性,而且各个方面考虑得较为全面。下面我们分析一下Retrofit的源码。

推荐拓展阅读

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持

喜欢

52

分享到微博分享到微信

更多分享

×

喜欢的用户

5条评论 ( 按时间正序· 按时间倒序· 按喜欢排序 )添加新评论

曾樑

2 楼 · 2016.01.03 23:25

喜欢(0)回复

程序员小海

3 楼 · 2016.01.09 23:47

学习学习

喜欢(0)回复

我能想到的大家都想到了

4 楼 · 2016.01.10 02:03

apache不来源?你确定?

喜欢(0)回复

wphper

5 楼 · 2016.01.11 09:01

厉害

喜欢(0)回复

背靠背

6 楼 · 2016.01.21 18:49

我一直用这个框架,确实很好用

喜欢(0)回复

登录后发表评论

被以下专题收入,发现更多相似内容:
  • 首页投稿

    添加关注

    玩转简书的第一步,从这个专题开始。 想上首页热门榜么?好内容想被更多人看到么?来投稿吧!如果被拒也不要灰心哦~入选文章会进一个队...

    31450篇文章 · 68773人关注

  • 程序员

    添加关注

    简书程序员大本营 投稿须知: 1.本专题仅收录与程序有关的文章。 2.请在代码框里写代码,尽量保证可看性。

    5641篇文章 · 53158人关注

  • Android开发

    添加关注

    Android系列文章,与大家一起交流。

    136篇文章 · 751人关注

时间: 2024-10-19 19:17:41

Android网络框架源码分析一---Volley的相关文章

android 网络框架 源码分析

android 网络框架 源码分析 导语: 最近想开发一个协议分析工具,来监控android app 所有的网络操作行为, 由于android 开发分为Java层,和Native层, 对于Native层我们只要对linux下所有网络I/O接口进行拦截即可,对于java 层,笔者对android 网络框架不是很了解,所以这个工具开发之前,笔者需要对android 的网络框架进行一个简单的分析. 分析结论: 1. android 的网络框架都是基于Socket类实现的 2. java 层Socket

Android Small插件化框架源码分析

Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github.com/wequick/Small 插件化的方案,说到底要解决的核心问题只有三个: 1.1 插件类的加载 这个问题的解决和其它插件化框架的解决方法差不多.Android的类是由DexClassLoader加载的,通过反射可以将插件包动态加载进去.Small的gradle插件生成的是.so包,在初始

Android 开源项目源码分析第一期正式发布

由 Trinea 发起.几十名 Android 开发者参与的Android 开源项目源码分析第一期正式发布. 从简介.总体设计.流程图.详细设计全方面分析开源库源码,第一期包括 10 个著名开源库及 5 个公共技术点的全面介绍. 分析文档 作者 Volley 源码解析 grumoon Universal Image Loader 源码分析 huxian99 Dagger 源码解析 扔物线 EventBus 源码解析 Trinea xUtils 源码解析 Caij ViewPagerindicat

携程DynamicAPK插件化框架源码分析

携程DynamicAPK插件化框架源码分析 Author:莫川 插件核心思想 1.aapt的改造 分别对不同的插件项目分配不同的packageId,然后对各个插件的资源进行编译,生成R文件,然后与宿主项目的R文件进行id的合并. 要求:由于最终会将所有的资源文件id进行合并,因此,所有的资源名称均不能相同. 2.运行ClassLoader加载各Bundle 和MultiDex的思路是一样的,所有的插件都被加载到同一个ClassLoader当中,因此,不同插件中的Class必须保持包名和类名的唯一

子墨庖丁Android的ActionBar源码分析 (一)实例化

如果你从事过Android客户端开发,相信你对ActionBar这套框架并不陌生,或者说你并不了解它,但是你应该时不时的要跟它打交道.抛开ActionBar的实现不说,ActionBar实际上是对Android的TitleBar行为的抽象,这种框架可以适用于这种模式的应用,是对需要的行为视图的抽象.当然或许你也和我一样,对ActionBar的实现效率并不满意,因为你打开它的视图,你会发现它的实现非常的ugly.不过我们庆幸的看到的是,ActionBar在设计的时候就并不是以一个强类型的姿态存在,

iOS常用框架源码分析

SDWebImage NSCache 类似可变字典,线程安全,使用可变字典自定义实现缓存时需要考虑加锁和释放锁 在内存不足时NSCache会自动释放存储的对象,不需要手动干预 NSCache的key不会被复制,所以key不需要实现NSCopying协议 第三方框架 网络 1.PPNetworkHelper 对AFNetworking 3.x 与YYCache的二次封装 简单易用,包含了缓存机制,控制台可以直接打印json中文字符 2..YTKNetwork 猿题库研发团队基于AFNetworki

YII框架源码分析(百度PHP大牛创作-原版-无广告无水印)

                        YII 框架源码分析             百度联盟事业部--黄银锋   目 录 1. 引言 3 1.1.Yii 简介 3 1.2.本文内容与结构 3 2.组件化与模块化 4 2.1.框架加载和运行流程 4 2.2.YiiBase 静态类 5 2.3.组件 6 2.4.模块 9 2.5 .App 应用   10 2.6 .WebApp 应用   11 3.系统组件 13 3.1.日志路由组件  13 3.2.Url 管理组件  15 3.3.异常

Android -- 消息处理机制源码分析(Looper,Handler,Message)

android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因此我没将其作为核心类.下面一一介绍: Looper Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程.所谓Looper线程就是循环工作的线程.在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Lo

介绍开源的.net通信框架NetworkComms框架 源码分析

原文网址: http://www.cnblogs.com/csdev Networkcomms 是一款C# 语言编写的TCP/UDP通信框架  作者是英国人  以前是收费的 售价249英镑 我曾经花了2千多购买过此通讯框架, 目前作者已经开源  许可是:Apache License v2 开源地址是:https://github.com/MarcFletcher/NetworkComms.Net 这个框架给我的感觉是,代码很优美,运行很稳定,我有一个项目使用此框架已经稳定运行1年多.这个框架能够