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框架的源码算是解析完毕。那么如何在实际开发项目中正确使用它呢,我会在下一篇中叙述。

  

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值