Volley源码解析(一)——发送请求与结束请求

来源:互联网 发布:手机淘宝旺旺号是什么 编辑:程序博客网 时间:2024/05/22 06:40

Volley是一个Android HTTP库,只支持异步方式。

发送请求样例

final TextView mTextView = (TextView) findViewById(R.id.text);... // Instantiate the RequestQueue. RequestQueue queue = Volley.newRequestQueue(this);String url ="http://www.google.com";// Request a string response from the provided URL. StringRequest stringRequest = new StringRequest(Request.Method.GET, url,            new Response.Listener<String>() {    @Override     public void onResponse(String response) {        // Display the first 500 characters of the response string.         mTextView.setText("Response is: "+ response.substring(0,500));    } }, new Response.ErrorListener() {    @Override     public void onErrorResponse(VolleyError error) {        mTextView.setText("That didn't work!");    } }); // Add the request to the RequestQueue. queue.add(stringRequest);

发送请求源码分析

一个请求的生命周期如下图:
Request生命周期
从上图可以看出,在Volley中会有三个线程:UI线程负责发请求和收响应;缓存分发器;网络分发器。
Volley类中只负责一件事情,就是创建一个RequestQueue对象,用于存放请求,该对象最好是单例的,供整个APP使用。
其具体实现如下:

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {        //创建缓存目录        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);        String userAgent = "volley/0";        //更新userAgent字段        try {            String packageName = context.getPackageName();            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);            userAgent = packageName + "/" + info.versionCode;        } catch (NameNotFoundException e) {        }        //对HttpStack赋值        if (stack == null) {            //如果SDK大于8,使用HurlStack,否则使用HttpClientStack            if (Build.VERSION.SDK_INT >= 9) {                stack = new HurlStack();            } else {                // Prior to Gingerbread, HttpUrlConnection was unreliable.                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));            }        }        Network network = new BasicNetwork(stack);        //创建RequestQueue对象        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);        queue.start();        return queue;    }

RequestQueue的构造方法中有两个参数,一个是Cache,负责将响应保存到磁盘,一个是Network,负责执行HTTP操作。
下面先看一下RequestQueue的内部定义,RequestQueue内部主要有四个字段是在初始化的时候指定的,分别是:

    private final Cache mCache;    private final Network mNetwork;    /** 分发HTTP响应 */    private final ResponseDelivery mDelivery;    /** 网络分发器 */    private NetworkDispatcher[] mDispatchers;

RequestQueue的构造方法一共有三个,但最终都会调用下面的这个构造器,如下:

 public RequestQueue(Cache cache, Network network, int threadPoolSize,            ResponseDelivery delivery) {        mCache = cache;        mNetwork = network;        mDispatchers = new NetworkDispatcher[threadPoolSize];        mDelivery = delivery;    }

RequesteQueue#start()

当创建好RequestQueue之后,调用了start()方法,start()方法如下:

    public void start() {        stop();  // Make sure any currently running dispatchers are stopped.        // 创建缓存分发器,然后启动        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();        }    }

可以看到RequestQueue的start方法就是启动其内部的分发器,主要包括一个缓存分发器和多个网络分发器,网络分发器的数量实在构造方法中设置的,默认为4个。
前面的例子中,当创建好Request和RequestQueue之后,就将Request放进RequestQueue中就可以了。

RequestQueue#add()方法

add()方法实现如下:

public <T> Request<T> add(Request<T> request) {        //Request和RequestQueue关联        request.setRequestQueue(this);        //将请求加入到Set中        synchronized (mCurrentRequests) {            mCurrentRequests.add(request);        }        // Process requests in the order they are added.        request.setSequence(getSequenceNumber());        request.addMarker("add-to-queue");        // 如果这个请求不应该被缓存,那么直接添加进网络队列中        if (!request.shouldCache()) {            mNetworkQueue.add(request);            return request;        }        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);                mWaitingRequests.put(cacheKey, stagedRequests);                if (VolleyLog.DEBUG) {                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);                }            } else {                mWaitingRequests.put(cacheKey, null);                mCacheQueue.add(request);            }            return request;        }    }

这里面涉及到RequestQueue中的多个个集合,定义分别如下:

    //如果当前请求已经在执行了,那么将会加入到该集合中    private final Map<String, Queue<Request<?>>> mWaitingRequests =            new HashMap<String, Queue<Request<?>>>();    //当前正在执行的请求    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();    //缓存请求优先级队列    private final PriorityBlockingQueue<Request<?>> mCacheQueue =        new PriorityBlockingQueue<Request<?>>();    //网络请求优先级队列    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =        new PriorityBlockingQueue<Request<?>>();

可以看到,add()方法主要做的就是根据不同情况将请求加入到不同的队列中。由于缓存请求队列和网络请求队列都是使用的优先级队列,所以可以给Request设置优先级。
1. 如果请求不应该从缓存中得到,那么直接加入到网络请求队列中;
2. 如果请求已经在执行了,那么将其加入到正在执行的一个HashMap中,其中key是请求,值是一个请求的队列;
3. 如果请求没有在执行,那么将其加入到缓存队列中。

在这里,我们已经将一个请求提交了,下面看一下,Request的缓存键值是如何得到的。

Request#getCacheKey()

public String getCacheKey() {        return getUrl();    }public String getUrl() {        return mUrl;    }

可以看到一个Request的键值就是其URL。
现在考虑一个问题:创建了同一个相同URL的两个Request,或者说同一个Request添加到RequestQueue两次,情况是怎么样的呢?下面分别分析:

RequestQueue添加同一相同URL的两个Request对象

设为Request1和Request2,添加时,由于Request没有重写equals方法和hashCode方法,所以mCurrentRequests会人为这是两个不同的请求,都添加进Set,然后比如说Request1首先获得了mWaitingRequests的锁,由于mWaitingRequests中还没有该URL,所以被添加进mWaitingRequests和放到了缓存队列中,然后当Request2再获取到mWaitingRequests时候,由于已经有了URL,所以会在mWaitingRequests中创建一个链表并把该请求放入链表中,从而可以看出同一时刻相同URL的请求只会被执行一次。不过具体放在链表中的请求在Request1被处理之后是如何处理的,下面一篇博客会分析到。

Request添加同一相同的Request两次

经过前面的分析,可以知道,在mCurrentRequests中后一个Request会代替前一个Request,而后一个Request会被放入mWaitingRequests的链表中。可以发现同一个Request对象即在缓存队列中,又在待处理的队列中。

下面就Request被执行完之后,看是怎样操作的来解释上面两个问题。

结束请求源码分析

当想取消一个Request的执行时,可以调用RequestQueue的finish()来主要取消执行,也可以在Request被正常执行完之后自己调用finish()方法,下面先从Request的finish()方法看起,其实现如下:

Request#finish()

 void finish(final String tag) {        if (mRequestQueue != null) {            mRequestQueue.finish(this);        }        if (MarkerLog.ENABLED) {            final long threadId = Thread.currentThread().getId();            if (Looper.myLooper() != Looper.getMainLooper()) {                // If we finish marking off of the main thread, we need to                // actually do it on the main thread to ensure correct ordering.                Handler mainThread = new Handler(Looper.getMainLooper());                mainThread.post(new Runnable() {                    @Override                    public void run() {                        mEventLog.add(tag, threadId);                        mEventLog.finish(this.toString());                    }                });                return;            }            mEventLog.add(tag, threadId);            mEventLog.finish(this.toString());        } else {            long requestTime = SystemClock.elapsedRealtime() - mRequestBirthTime;            if (requestTime >= SLOW_REQUEST_THRESHOLD_MS) {                VolleyLog.d("%d ms: %s", requestTime, this.toString());            }        }    }

可以看到,首先也是调用了RequestQueue的finish()方法,下面再来分析RequestQueue的finish()方法。

RequestQueue#finish()

void finish(Request<?> request) {        // 首先从当前执行集合中删除        synchronized (mCurrentRequests) {            mCurrentRequests.remove(request);        }        //如果请求应该被缓存        if (request.shouldCache()) {            synchronized (mWaitingRequests) {                //得到请求的键值,即URL                String cacheKey = request.getCacheKey();                //得到与URL关联的等待队列                Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey);                //如果队列不为null                if (waitingRequests != null) {                    //将所有请求都加入到缓存队列中                    mCacheQueue.addAll(waitingRequests);                }            }        }    }

这儿,可以看到,首先是从Set集合中删除当前请求,然后判断请求是否应该被缓存,这和add()方法里面是相一致的,add()方法里只有请求允许使用缓存,才会被加入到URL关联的等待队列中。然后就是将与URL关联的请求都加入到缓存队列中。下面就上面两个问题再次给出解释。
1. RequestQueue添加同一相同URL的两个Request对象:对于这种情况,Request1被执行,Request2被存在等待队列中,当Request1执行完成后,将会从URL关联的队列中得到Request2对象,然后将Request2加入到缓存队列,由于Request1之前已经有缓存结果了,所以执行Request2时只需要经过CacheDispatcher就可以得到结果然后finish了,可以发现这种情况下,同一个URL的只会进行一次网络请求,其余的都是走缓存请求;不过这是针对于可以缓存响应的情况,如果不能缓存响应,那么都会直接加入到网络请求中执行两次网络操作。
2. Request添加同一相同的Request两次:当执行了第一次之后,就从Set中成功移除了Request,然后再从等待队列中取出,加入到缓存队列,可以当发现这一次的Request依然会走CacheDispatcher中一趟。

至此,我们将一个请求提交给了RequestQueue,那么RequestQueue是如何执行请求,又是如何将响应交付给UI线程处理呢?这一部分,我们下一篇再讲。下一篇文章Volley源码解析(二)——CacheDispatcher将会介绍CacheDispatcher是如何进行缓存分发的。

0 3
原创粉丝点击