Android之Volley框架源码分析

  临近毕业,各种事情各种忙。我也没有认真专注写过博客,最近仔细看了Volley框架的使用及其源码,思前想后,想挑战一下自己,还是写一篇博客来分享,如有错误,欢迎吐槽。

Volley简介

  网络请求是一个App很重要的一部分,android系统只是提供了一个平台,而android应用则是基于这个平台上进行展示数据,起到与用户进行交互的作用,数据来源于服务端,而二者之间必须通过互联网进行传输数据,在Android系统发布初期,很多开发者都是在Apache协会的Http协议的基础上进行网络请求方法的封装,当然有小白和大神,难免会出现一些意想不到的问题。谷歌也是考虑到了这一点,在2013年I/O大会上发布了一个基于HttpURLConnection的网络请求框架–Volley。

  Volley把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络上的图片。

  Volley适用于请求一些数据量不大且频繁的数据,不适合请求一些大数据。

  

  Volley的使用方法可以看郭神的博客 ,在此我们就直接分析源码了。

Volley源码分析

  我们知道Volley所有的请求都是基于Request这个抽象类,我们来看看这个类中的主要方法:

/**
     * Creates a new request with the given method (one of the values from {@link Method}),
     * URL, and error listener.  Note that the normal response listener is not provided here as
     * delivery of responses is provided by subclasses, who have a better idea of how to deliver
     * an already-parsed response.
     */
    public Request(int method, String url, Response.ErrorListener listener) {
        mMethod = method;
        mUrl = url;
        mErrorListener = listener;
        setRetryPolicy(new DefaultRetryPolicy());

        mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
    }

  这里Request的构造方法,没什么特别,就是把传入的参数传递给对应的对象,以及设置了一个请求的优先级,这里为默认。

    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

  这是Request中最主要的两个方法之一,每个继承的类都必须具体实现这两个方法。parseNetworkResponse方法中response是最后服务端返回的字节码数据之后在子线程中被调用,进行解析数据,我们可以将这个字节码数据转换成我们想要的类型,比如StringRequest中,parseNetworkResponse方法的实现如下:

@Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

  很明显,这个将字节码以指定的编码格式转换成了String字符串。

  abstract protected void deliverResponse(T response);

  看着方法名就可以知道,这个是将最后解析过的请求数据交付给主线程,让主线程进行处理,还记得我们在构造函数中传入一个Listener的对象吗,对,就是它。我们可以通过接口回调的方式将最终数据返回给主线程使用。还是一样,我们看看StringRequest中的实现:

 @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

  Request抽象类中还有一些其他的属性,比如:

public Request<?> setTag(Object tag) {
        mTag = tag;
        return this;
    }

  给每个Request附加一个标签,便于管理。

 public Request<?> setRetryPolicy(RetryPolicy retryPolicy) {
        mRetryPolicy = retryPolicy;
        return this;
    }

  设置一个自定义的在请求失败的情况下重新请求的规则。

public void addMarker(String tag) {
        if (MarkerLog.ENABLED) {
            mEventLog.add(tag, Thread.currentThread().getId());
        } else if (mRequestBirthTime == 0) {
            mRequestBirthTime = SystemClock.elapsedRealtime();
        }
    }

  这个方法主要是打印日志的方式展示请求的过程进度。

/**
     * Returns the URL of this request.
     */
    public String getUrl() {
        return mUrl;
    }

    /**
     * Returns the cache key for this request.  By default, this is the URL.
     */
    public String getCacheKey() {
        return getUrl();
    }

  这里要注意的是把url作为一个缓存的Key。



  我们知道我们需要把Request添加到RequestQueue中才能完成网络请求,我们一般通过静态方法

RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);

  创建一个RequestQueue对象,那么我们看看RequestQueue的代码:

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) {
            if (Build.VERSION.SDK_INT >= 9) {
            //sdk版本高于9时,创建一个HrlStack对象,里面是网络请求的具体逻辑
                stack = new HurlStack();
            } else {
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
        //创建一个Network对象,里面进行了网络请求数据方法的二次封装
        Network network = new BasicNetwork(stack);
        //传入一个缓存对象以及netWork对象
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        //启动队列
        queue.start();

        return queue;
    }

  可以看出,在这个方法中,最后在创建RequestQueue的时候,传入了一个缓存对象以及封装好的网络请求方法的对象,最后调用一个start方法,现在我们还没有看它的具体实现,但是我们知道现在是万事俱备,只欠东风了。就像一台榨汁机接通了电源并且开启了开关,我们只需要扔进水果就行了。当然,我们这里不能扔进水果…我们需要做的就是:

requestQueue.add(request);

  把Request对象加入进去,让它自己去跑,我们已经在Request中两个抽象方法中写好了解析以及最终数据的交付逻辑。我们来看看RequestQueue.add()中的代码:

  

public <T> Request<T> add(Request<T> request) {
        // 在此request设置一个标记,指明此request属于的RequestQueue
        request.setRequestQueue(this);
        synchronized (mCurrentRequests) {
        //将请求添加到队列中
            mCurrentRequests.add(request);
        }

        // 处理请求按顺序添加
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");

        // 如果request不需要缓存,直接加入mNetworkQueue队列,等待处理
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }

        // Insert request into stage if there‘s already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {
                // 等待缓存的队列中已经有了此请求
                Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request<?>>();
                }
                stagedRequests.add(request);
                //将新增加的request的集合对象覆盖进去
                mWaitingRequests.put(cacheKey, stagedRequests);
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                //如果等待缓存队列中只有一个请求,那么value的值就为null
                mWaitingRequests.put(cacheKey, null);
                //将request添加到缓存工作队列中
                mCacheQueue.add(request);
            }
            return request;
        }
    }

  可能一部分读者看到这里就有点晕了,怎么这么多的队列。我们一个个解释。

  

  mCurrentRequests,它的对象声明是这样的:

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

  Set集合中存放的是无序且不重复的子对象。mCurrentRequests 在这里包含了所有我们通过requestQueue.add(request); 方法添加的request,这个集合主要起到管理request的作用,在request请求在NetworkDispatcher线程中处理结束之后,无论请求成功或者失败,都会调用Request的finish方法:

  

void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);
        }

很明显,Request中调用了mRequestQueue的finish方法,让我们看下代码:

<T> void finish(Request<T> request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }

从这里我们可以看到,在request响应结束之后,我们将request从mCurrentRequests移除,这里我只贴出了部分代码。



  

  mNetworkQueue和mCacheQueue,它们的声明是这样的:

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

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

  先来说下PriorityBlockingQueue这个类吧,这个类实现了implements BlockingQueue<E> 这个接口,BlockingQueue是jdk中Concurrent包中新增的用于解决了多线程中,如何高效安全“传输”数据的问题,并且它能保证线程安全。所以将mNetworkQueue和mCacheQueue传入子线程中,让子线程从中获取request进行处理。

mWaitingRequests:

private final Map<String, Queue<Request<?>>> mWaitingRequests =

new HashMap<String, Queue<Request<?>>>();

  mWaitingRequests是一个Map集合,里面保存着键值对,以request的url为key。


接着往下我们看`queue.start();`这个方法中的代码:
 /**
     * Starts the dispatchers in this queue.
     */
    public void start() {
        stop();  // 如果队列在开启状态,关闭
        //创建一个缓存分发线程,并且启动它
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // 创建多个网络请求的线程,并且启动它们
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

  NetworkDispatcher和CacheDispatcher都是继承于Thread类。从上面的源码可知,我们将默认启动是4个子线程,这4个子线程一直在跑,执行RequestQueue中的Request。而mCacheDispatcher只开启一个线程,用于缓存响应数据。



  接着我们来看在NetworkDispatcher线程中是如何处理请求的:

  

public class NetworkDispatcher extends Thread {
    /** 需要处理的请求队列 */
    private final BlockingQueue<Request<?>> mQueue;
    /** 封装好了的用来处理网络请求的接口 */
    private final Network mNetwork;
    /** 用于写入缓存的对象 */
    private final Cache mCache;
    /** 用来请求之后的接口回调 */
    private final ResponseDelivery mDelivery;
    /** 用于退出此线程 */
    private volatile boolean mQuit = false;

    public NetworkDispatcher(BlockingQueue<Request<?>> queue,
            Network network, Cache cache,
            ResponseDelivery delivery) {
        mQueue = queue;
        mNetwork = network;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 结束此线程
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    private void addTrafficStatsTag(Request<?> request) {
        //Api>=14以上调用此方法(if API >= 14)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {                TrafficStats.setThreadStatsTag(
        request.getTrafficStatsTag());
        }
    }

    @Override
    public void run() {
    //设置线程为后台线程
 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        //全真循环,不断获取mQueue中的request进行处理
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            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;
                }

                addTrafficStatsTag(request);

                // 执行请求
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // 如果我们已经递交过同样的响应数据,那么不会再次递交
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // 在子线程中解析字节码为我们想要的数据类型
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // request需要缓存,并且response的缓存入口不为空
                if (request.shouldCache() && response.cacheEntry != null) {
                //以request的url为key,以response的缓存入口为value进行缓存
                    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);
            }
        }
    }
/**
*解析错误的时候,接口回调对应的方法
*/
    private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
        error = request.parseNetworkError(error);
        mDelivery.postError(request, error);
    }
}

  Volley默认是开启四个NetworkDispatcher这个线程,用于不断从mQueue中获取request进行处理请求。以mNetwork为处理器,并且在获取成功之后回调在request中parseNetworkResponse方法进行解析成我们想要的数据。这个方法是不是很熟悉,没错,它就是我们在自定义Request中所要重新的抽象方法之一。在解析失败时也会有相应的接口回调。

  在请求需要进行缓存并且响应数据缓存入口不为空的情况下,我们将以request的url为key,以response的缓存入口为value进行缓存。

  最后将解析好的数据通过 mDelivery.postResponse(request, response); 方法抛给主线程处理。

  



CacheDispatcher是一个缓存线程,让我们看看它的代码实现:

public class CacheDispatcher extends Thread {

    private static final boolean DEBUG = VolleyLog.DEBUG;

    /** 缓存队列*/
    private final BlockingQueue<Request<?>> mCacheQueue;

    /** 网络请求队列*/
    private final BlockingQueue<Request<?>> mNetworkQueue;

    /** 缓存对象 */
    private final Cache mCache;

    /** 用于将最终数据传递的接口*/
    private final ResponseDelivery mDelivery;

    /** 线程是否存活 */
    private volatile boolean mQuit = false;

    public CacheDispatcher(
            BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
            Cache cache, ResponseDelivery delivery) {
        mCacheQueue = cacheQueue;
        mNetworkQueue = networkQueue;
        mCache = cache;
        mDelivery = delivery;
    }

    /**
     * 结束线程
     */
    public void quit() {
        mQuit = true;
        interrupt();
    }

    @Override
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");                  Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // 初始化线程对象
        mCache.initialize();
        //全真循环,不断获取mCacheQueue中的request进行处理
        while (true) {
            try {
                //从mCacheQueue中获取request对象,如果mCacheQueue为空,那么会堵塞暂停,直至mCacheQueue添加了对象
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
                // 取消了缓存要求
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }

                // 获取缓存的条目
            Cache.Entry entry =mCache.get(request.getCacheKey());
                if (entry == null) {
                    request.addMarker("cache-miss");
                        // 如果缓存中找不到缓存条目,那么把请求扔mNetworkQueue,让它去通过网络请求数据
                    mNetworkQueue.put(request);
                    continue;
                }

                //如果缓存条目过期的话,那么把request加入mNetworkQueue让它去通过网络重新请求数据
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }

                //有了有效的缓存对象,将缓存对象通过Request中定义的解析方法解析成我们想要的数据类型
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
  new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");

                if (!entry.refreshNeeded()) {
                    // 不需要刷新的数据,可以直接递交给主线程使用了
                    mDelivery.postResponse(request, response);
                } else {
                   //需要刷新的数据,将它它去通过网络重新请求数据
                    request.addMarker("cache-hit-refresh-needed");
                    request.setCacheEntry(entry);

                    // 因为只是响应数据需要刷新,所以可以把response作为媒介,标记这个为true
                    response.intermediate = true;

                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }

            } catch (InterruptedException e) {
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
}

Volley官网的框架结构流程图:

  总结一下:Volley框架中封装了网络请求的具体方法在HurlStack类中,它就相当于一个加载器,BasicNetwork类对它进行了二次封装。CacheDispatcher的目的就是从mCacheQueue中获取request,然后再从缓存中判断是否有对应的缓存,如果没有或者过期等原因,会将此request抛给mNetworkQueue队列,让mNetworkQueue在NetworkDispatcher中,而在NetworkDispatcher中通过获取mNetworkQueue的每个request,使用默认的HurlStack加载器(当然也可以自定义)向服务端请求数据,最后将请求成功的响应数据再进行缓存。

  

  至此。Volley框架的源码算是解析完毕。那么如何在实际开发项目中正确使用它呢,我会在下一篇中叙述。

  

  这是我第一次花这么多心思写博客,花了一下午和一晚上的时间,希望大家能有所收获。

时间: 2024-10-22 11:12:48

Android之Volley框架源码分析的相关文章

Android网络通信Volley框架源码浅析(三)

尊重原创 http://write.blog.csdn.net/postedit/26002961 通过前面浅析(一)和浅析(二)的分析,相信大家对于Volley有了初步的认识,但是如果想更深入的理解,还需要靠大家多多看源码. 这篇文章中我们主要来研究一下使用Volley框架请求大量图片的原理,在Android的应用中,通过http请求获取的数据主要有三类: 1.json 2.xml 3.Image 其中json和xml的获取其实原理很简单,使用Volley获取感觉有点大财小用了,了解Volle

Volley框架源码分析

Volley框架分析Github链接 Volley框架分析 Volley源码解析 为了学习Volley的网络框架,我在AS中将Volley代码重新撸了一遍,感觉这种照抄代码也是一种挺好的学习方式.再分析Volley源码之前,我们先考虑一下,如果我们自己要设计一个网络请求框架,需要实现哪些事情,有哪些注意事项? 我的总结如下: 需要抽象出request请求类(包括url, params, method等),抽象出request请求类之后,我们可以对其继承从而实现丰富的扩展功能. 需要抽象出resp

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包,在初始

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

Volley框架源码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<

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

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

Android 上千实例源码分析以及开源分析

Android 上千实例源码分析以及开源分析(百度云分享) 要下载的直接翻到最后吧,项目实例有点多. 首先 介绍几本书籍(下载包中)吧. 01_Android系统概述 02_Android系统的开发综述 03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多媒体系统 10_

深度理解Android InstantRun原理以及源码分析

深度理解Android InstantRun原理以及源码分析 @Author 莫川 Instant Run官方介绍 简单介绍一下Instant Run,它是Android Studio2.0以后新增的一个运行机制,能够显著减少你第二次及以后的构建和部署时间.简单通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的让你看到你修改的效果.而在没有Instant Run之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果. 传统的代