学习内容:
1.Http请求的过程...
2.Volley的简单介绍...
1.Http请求...
这里只是简单的说一下Http请求的过程...非常的简单...首先是发送Request..然后服务器在获取到Request请求后会对其进行相应的处理,然后以Response的形式进行返回,然后分配Response,即谁发送的请求,那么响应就分配给谁...
2.Volley简单介绍...
这里只是先简单的说一下Volley,Volley框架是由Google发布的一款开源框架...这个框架主要是针对网络请求而包装生成的开源框架...主要功能是异步的网络请求和图片的加载,适用于Android这种请求频繁而每次请求数据量并不是很大的这一类网络请求...通过源码能够发现Volley有非常好的扩展性,更多的地方采用接口的设计...所以我们都可以自己重写内部的一些方法...总体的设计思路也是非常的明确的...
客户端如果想通过网络连接来连接服务器,那么首先需要发送相关请求...每一个请求都需要被建立,那么建立请求的类就靠Request.java来实现...
Request.java(源码解析)
简单的说说Request.java,这是Volley的最核心的类,Request.java不仅仅封装了Request请求,还包括对服务器返回的Response的数据信息进行相应的处理..总之Request.java是一个最大的父类,内部封装了非常多的方法...其他的几个子类都是通过继承Request.java从而实现自己的功能...Request只是对外提供了一个接口,任何方式的请求只需要实现接口就能够实例化自己的请求对象,创建自己内部的方法..从而形成一种良好的扩展...
凡是继承了Request.java必须要实现的一个方法...
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
这个方法是对网络服务器响应的一个解析过程..也是最后由这个函数通过postResponse方法,将Response传递给ResponseDelivery从而对响应进行分发,不难理解,在每一个Request中实现这个方法,那么也就能够知道是谁发出的请求,这样在分发响应的时候也就不会产生错误...
2.1 public void addMarker(String tag){}函数...
public void addMarker(String tag) { if (MarkerLog.ENABLED) { mEventLog.add(tag, Thread.currentThread().getId()); } else if (mRequestBirthTime == 0) { mRequestBirthTime = SystemClock.elapsedRealtime(); } }
addMarker函数,其实是就是一个标志,这个函数可以为每一个request添加相应事件标识符,这样我们就可以通过捕获标识符的方式从而对每一个request做进一步的操作.
比如说一个网络请求从请求队列取出的标识:request.addMarker("network-queue-take");
一个请求被取消的标识:request.addMarker("network-discard-cancelled");
在Volley中很容易看到这些标识,他们的存在就是为了捕获每一个请求的发生状态,通过捕获这些状态,比如说一个请求已经完成,那么我们捕获到了请求完成之后,我们就需要将这次请求从请求队列当中移除,那么这个操作的执行就需要首先捕获到请求的状态,我们才能够采取下一步的操作...总之就是通过标识才能够清楚的了解请求到底执行到了何种状态...
2.2 以下几个函数完成实体部分(Body)中验证参数的传递以及编码过程...
2.2.1 public byte[] getPostBody() throws AuthFailureError{}
public byte[] getBody() throws AuthFailureError{}
public byte[] getPostBody() throws AuthFailureError { // Note: For compatibility with legacy clients of volley, this implementation must remain // here instead of simply calling the getBody() function because this function must // call getPostParams() and getPostParamsEncoding() since legacy clients would have // overridden these two member functions for POST requests. Map<String, String> postParams = getPostParams(); if (postParams != null && postParams.size() > 0) { return encodeParameters(postParams, getPostParamsEncoding()); } return null; } public byte[] getBody() throws AuthFailureError { Map<String, String> params = getParams(); if (params != null && params.size() > 0) { return encodeParameters(params, getParamsEncoding()); } return null; }
这两个函数想必大家看函数名称估计都能明白到底是怎么回事了,只不过这里有个小小的区别...第一个方法仅仅是对Post请求方式中Body实体部分的获取,而第二个是对Post或Put请求方式中Body实体部分的获取...
我们知道网络请求时,信息是以数据报的形式进行传递的,数据报有头部(Headers)和实体(Body)部分...实体一般都是封装着想要发送的数据以及验证信息...因此想要获取Body中的实体数据就必须要通过某种方法来获取原生态的实体数据(Body)...获取了实体数据之后,就可以提交验证以及发送数据...那么上面两个函数就是用来解决这个问题的...
我们可以从源码看到,二者分别调用getParams()和getPostParams()函数...
2.2.2 protected Map<String,String> getPostParams() throws AuthFailureError{}
protected Map<String,String> getParams() throws AuthFailureError{}
protected Map<String, String> getPostParams() throws AuthFailureError { return getParams(); } protected Map<String, String> getParams() throws AuthFailureError { return null; }
这两个方法就是获取数据报中Body用于验证或授权时传递的参数,通过源码我们可以看到,方法的返回值是空值,这也不难理解,如果客户端想要完成验证和授权,必须要由客户端发送验证数据,通过对客户端验证数据信息的抓取,接着应用程序重写上面的两个方法,客户端的验证数据信息被封装到这两个方法内,这样服务器就可以真正的获取到客户端提交的信息了..来张图片方便大家理解...
这里还涉及到了一个参数的编码...通过调用encodeParamters()方法...
2.2.3 private byte[] encodeParameters(Map<String,String>params,String paramsEncoding){}
这个方法需要对传递过来的所有参数进行遍历...将参数转化成 postid=4868291&update=1 这样的形式...我们在访问网站的时候经常会在url看到这样类似的字串,那也就是传递的参数想必也就不难理解了...
private byte[] encodeParameters(Map<String, String> params, String paramsEncoding) { StringBuilder encodedParams = new StringBuilder(); try { for (Map.Entry<String, String> entry : params.entrySet()) { encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding)); encodedParams.append('='); encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding)); encodedParams.append('&'); } return encodedParams.toString().getBytes(paramsEncoding); } catch (UnsupportedEncodingException uee) { throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee); } }
2.3 请求完成后需要执行的函数...
2.3.1 void finish(final String tag){}
当一个请求完成之后需要对做出一些操作,首先需要做的事情就是将请求队列中的这次请求进行移除操作,因为请求已经完成...这也不难理解,也就是下面源码第一个if执行的过程,他会再次调用RequestQueue中finish()函数,来移除这次请求,这个源码就先不介绍...等到后面介绍RequestQueue源码的时候再细细说一下...我们只需要知道现在它的功能就行了...也是这个函数的主要部分...而下面第二个if()函数,其实是为了转储这次请求中所有的日志文件,为了以后的调试...
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()); } } }
2.4 Request的构造函数...
2.4.1 public Request(String url,Response.ErrorListener listener){}
public Request(int method,String url,Response.ErrorListener listener){}
构造函数分为两种,一个是直接通过url来执行请求,一个是通过指定提交方式,然后通过url来执行请求...
public Request(String url, Response.ErrorListener listener) { this(Method.DEPRECATED_GET_OR_POST, url, listener); } public Request(int method, String url, Response.ErrorListener listener) { mMethod = method; mUrl = url; mErrorListener = listener; setRetryPolicy(new DefaultRetryPolicy()); mDefaultTrafficStatsTag = TextUtils.isEmpty(url) ? 0: Uri.parse(url).getHost().hashCode(); }
Request只是一个抽象接口,为外部暴露接口,从而让其他的类去实现,其中还有许多的方法,包括请求失败时的重试策略,以及请求缓存的一些定义...还有许多变量和方法,只是在Request中进行了封装,而真正去调用和实现这些方法则需要其他的类进行扩展...因此Request的源码只是做一些简单的介绍..介绍的也是其中非常重要的方法...其他的内容则在后续进行介绍...
学习内容:
1.Request的完整封装...
2.RetryPolicy,DefaultRetryPolicy(请求重试策略源码解析)
3.RequestQueue(请求队列源码解析)
RequestQueue(源码解析)...
RequestQueue顾名思义,请求队列,Volley中采用请求队列来管理所有正在提交的Request,将所有封装好的Request保存在其内部,方便管理...而一个Request需要被完整的封装,才能够真正的加入到RequetQueue当中的,每一个Request都有一个重试策略,用于当一个请求提交失败的时候重试的操作,总不能一个请求失败了一次就直接把请求past掉吧...因此一个请求失败之后是否需要重试是需要经过设置的...
1.RetryPolicy.java
RetryPolicy是一个接口,前面说到Volley大多是使用接口的方式,以形成良好扩展,这个也不例外...
接口内部只有三个方法,分别表示获取超时时间,以及重试的次数,还有是否还有必要进行重试...
需要注意:这个类只是用于判断一个请求在失败之后是否需要进行重试,而没有对如何重试进行定义..
package com.android.volley; /** * Retry policy for a request. */ public interface RetryPolicy { /** * Returns the current timeout (used for logging). */ public int getCurrentTimeout(); /** * Returns the current retry count (used for logging). */ public int getCurrentRetryCount(); /** * Prepares for the next retry by applying a backoff to the timeout. * @param error The error code of the last attempt. * @throws VolleyError In the event that the retry could not be performed (for example if we * ran out of attempts), the passed in error is thrown. */ public void retry(VolleyError error) throws VolleyError; }
2.DefaultRetryPolicy.java
这个类则是用于实现上面接口的类,是对重试方案的一个实现过程,这个类只是简单的获取重试的次数,判断其是否超过的允许的最大重试次数,以及重试超时的时间...并没有如何重试的具体实现过程...只是对于一个请求是否进行重试的一个判断过程...通过这个类也不难发现,请求失败之后是按照默认的方式来完成请求的重试的...
2.1 变量的定义...
/** The current timeout in milliseconds. */ private int mCurrentTimeoutMs;//超时时间.. /** The current retry count. */ private int mCurrentRetryCount; //重试次数 /** The maximum number of attempts. */ private final int mMaxNumRetries; //允许重试的最大次数 /** The backoff multiplier for for the policy. */ private final float mBackoffMultiplier; //一个乘数因子,每次超时时间的获取都需要乘上这个乘数因子... /** The default socket timeout in milliseconds */ public static final int DEFAULT_TIMEOUT_MS = 2500;//定义默认的超时时间... /** The default number of retries */ public static final int DEFAULT_MAX_RETRIES = 1;//定义默认的最大重试次数... /** The default backoff multiplier */ public static final float DEFAULT_BACKOFF_MULT = 1f;//默认的乘数因子的定义..
2.2 DefaultRetryPolicy的构造函数..
public DefaultRetryPolicy(){}
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier){}
这两个构造函数,一个是使用系统默认构造重试方案,超时的时间(表示每一次重试耗费掉的时间的总和),重试最大次数,以及乘数因子都是使用系统默认分配的变量..而另一个则是我们人为进行指定,指定超时的时间,允许的最大次数,以及相关的乘数因子,因为客户端发出的Request是各种各样的...不可能都指定的去用一种重试方案,因此人为指定是非常重要的...
public DefaultRetryPolicy() { this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); } public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { mCurrentTimeoutMs = initialTimeoutMs; mMaxNumRetries = maxNumRetries; mBackoffMultiplier = backoffMultiplier; }
2.3 public int getCurrentTimeout(){}
对获取超时时间总和方法的实现...返回重试耗费掉的时间总和..非常的简单...
@Override public int getCurrentTimeout() { return mCurrentTimeoutMs; }
2.4 public int getCurrentRetryCount(){}
对重试次数获取的方法的实现...返回重试次数...
@Override public int getCurrentRetryCount() { return mCurrentRetryCount; }
2.5 public void retry(VolleyError error) throws VolleyError{}
protected Boolean hasAttemptRemaining(){}
每一次请求超时的时间,以及重试的次数都需要进行相应的记录,如果重试的次数已经超过了允许的次数,则需要抛出异常..这些过程由这两个方法来实现...方法中涉及到的东西都非常的少...就不去解释每一句代码的含义了...
@Override public void retry(VolleyError error) throws VolleyError { mCurrentRetryCount++; mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); if (!hasAttemptRemaining()) { throw error; } } /** * Returns true if this policy has attempts remaining, false otherwise. */ protected boolean hasAttemptRemaining() { return mCurrentRetryCount <= mMaxNumRetries; }
这样通过这些设定,我们才能够指定当一个请求失败时,是否需要进行重试,需要设定重试的次数,以及重试过程中,所耗费掉的时间...这也符合设计的思想...不能一个请求失败时直接就past掉,同时也不能一个请求如果一直失败那就一直提交...这些都是不合理的...所以需要对每一个请求去设定一个重试方案才能使每一次请求都能够更加合理...
3.RequestQueue.java
RequestQueue请求队列...首先得先说一下,ReuqestQueue是如何对请求进行管理的...RequestQueue是对所有的请求进行保存...然后通过自身的start()方法开启一个CacheDispatcher线程用于缓存调度,开启若干个NetWorkDispatcher线程用于网络调度...那么为什么要这样设计呢?
因为一个请求如果已经提交了一次,那么就只需要处理这一次结果就可以了,对于多次重复的请求,我们完全可以使用一个缓存来进行保存..从而减少一些不必要的网络请求,减小服务器的负担...如果一个请求在缓存中没存在过,那么我们再执行网络请求就可以了...
总而言之,设计理念就是从RequestQueue取出请求,先判断缓存是否命中,如果缓存命中,则从缓存中取出数据,如果缓存没有命中,则提交网络请求,从服务器端获取数据...
3.1 变量的定义..
/** Used for generating monotonically-increasing sequence numbers for requests. */ private AtomicInteger mSequenceGenerator = new AtomicInteger();//自动生成序列号... private final Map<String, Queue<Request>> mWaitingRequests = new HashMap<String, Queue<Request>>(); //如果一个请求可以被缓存并且正在被执行,那么后续与之相同的url请求进入此队列... private final Set<Request> mCurrentRequests = new HashSet<Request>(); //用于保存尚未完成请求的集合... private final PriorityBlockingQueue<Request> mCacheQueue = new PriorityBlockingQueue<Request>(); //用于保存缓存请求,从缓存中取出数据... private final PriorityBlockingQueue<Request> mNetworkQueue = new PriorityBlockingQueue<Request>(); //用于保存网络请求,从服务器上获取数据... /** Number of network request dispatcher threads to start. */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; //默认的网络线程数量... /** Cache interface for retrieving and storing respones. */ private final Cache mCache; //缓存机制...用于保存缓存数据... /** Network interface for performing requests. */ private final Network mNetwork; //用于执行网络请求... /** Response delivery mechanism. */ private final ResponseDelivery mDelivery; //用于分发相应.. /** The network dispatchers. */ private NetworkDispatcher[] mDispatchers; //网络调度线程... /** The cache dispatcher. */ private CacheDispatcher mCacheDispatcher; //缓存调度线程...
3.2 RequestQueue的构造函数
public RequestQueue(Cache cache, Network network, int threadPoolSize,ResponseDelivery delivery) {}
public RequestQueue(Cache cache, Network network, int threadPoolSize){}
public RequestQueue(Cache cache, Network network){}
三个构造函数,第一个是最后被调用的,第二个和第三个只是对数据进行一下封装..最后在第一个构造函数中进行赋值...这里需要注意的是,RequestQueue的构造函数不是用来生成RequestQueue对象的...创建一个RequestQueue并不是通过使用构造函数来实现的...它的存在是为RequestQueue添加其他属性...通过这个构造函数可以实例化缓存线程调度的缓存对象同时还定义了按照指定数量的网络请求调度线程的网络请求对象,以及请求结束后的分发响应对象...
public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; } public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); }
那么上面既然说到创建RequestQueue对象不是通过构造函数来实现的..它创建的方式采用:
RequestQueue queue=Volley.newRequestQueue(MainActivity.this);
简单的介绍一下...后续对这个还会有解析...目的要明确RequestQueue的构造函数到底是做什么的...不能混淆了它的作用...
3.3 public void start(){}
这个方法的调用决定开启缓存请求线程调度和网络请求线程调度...使用两个异步线程来对每一个请求进行处理...
public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); //缓存请求线程调度对象的实例化... mCacheDispatcher.start(); //调用start()方法开启线程.. // Create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mDispatchers.length; i++) { NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery); //根据指定大小,实例化网络请求对象... mDispatchers[i] = networkDispatcher; //赋值 networkDispatcher.start(); //开启网络线程.. } }
从上面的方法发现,缓存调度线程对象的实例化需要传递mNetworkQueue和mCacheQueue...两个参数...这里可能有点费解...其实理解起来也是非常的简单的...由于缓存数据不可能一辈子都是这个缓存数据,也不可能一直停留在内存当中,必定会有过期的时间,新鲜度,以及缓存丢失等情况的发生都会导致缓存失效...那么当缓存失效的时候,我们必须要有相关的对策发出响应传递给客户端,那么就只能通过网络请求的方式从服务器上获取请求数据了...在缓存请求线程调度的时候是需要传递mNetWorkQueue的...
3.4 public Request add(Request request){}
加入请求的操作...
public Request add(Request request) { // Tag the request as belonging to this queue and add it to the set of current requests. request.setRequestQueue(this); //为Request设置请求队列... synchronized (mCurrentRequests) { mCurrentRequests.add(request); //将请求放入到正在执行的请求队列中... } // Process requests in the order they are added. request.setSequence(getSequenceNumber()); //为请求设置一个唯一的序列号... request.addMarker("add-to-queue"); //添加标识 // If the request is uncacheable, skip the cache queue and go straight to the network. 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 { //否则的话,新建一个与这个请求相同的等待队列,并且将这次请求放入到缓存队列当中... // Insert 'null' queue for this cacheKey, indicating there is now a request in // flight. mWaitingRequests.put(cacheKey, null);//这里传递空值,当再次回调这个函数的时候,就会调用if中的函数,从而完成队列的创建... mCacheQueue.add(request); } return request; } }
函数根据条件加入请求...我们来过一下思路...首先当一个请求到来的时候,将请求加入到正在执行的队列当中(mCurrentQueue),接着为请求设置唯一的序列号,使得不同的请求之间有区别...然后判断请求是否允许被缓存,如果请求不允许被缓存,那么我们就可以直接发送网络请求了,缓存中是一定没有数据的...但是如果允许缓存的话,那么还需要进行后续的判断.判断是否有与之相同的正在被处理的请求,如果存在,那么将这次请求放入(mWaittingQueue)等待请求队列中,这个队列用来解决发送同一种请求多次的处理...而如果不存在相同的请求被执行,那么我们需要建立一个与这个请求相同的等待队列...然后将这次请求放入到缓存队列当中...再来一张图片...方便理解...图解过程...
3.5 void finish(Request request){}
请求结束后的过程,这个函数由Request.finish()调用...还记得上一章当一个Request结束后,需要请求移出请求队列当中,那么这个函数就是将请求移出请求队列的...当一个请求执行完毕之后,不仅将这次请求移出请求队列当中,还需要判断mWaitingQueue中是否存在与之相同的请求...如果存在,那么等待队列中的所有请求全部移出等待队列...加入到缓存队列当中,直接通过缓存数据来完成这些相同请求的响应...能够使用缓存队列处理的请求前提是允许被缓存...
void finish(Request request) { // Remove from the set of requests currently being processed. synchronized (mCurrentRequests) { mCurrentRequests.remove(request); //从当前请求队列中移出.. } if (request.shouldCache()) { //判断是否允许缓存... synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); Queue<Request> waitingRequests = mWaitingRequests.remove(cacheKey); //获取所有相同的请求... if (waitingRequests != null) { if (VolleyLog.DEBUG) { VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", waitingRequests.size(), cacheKey); } // Process all queued up requests. They won't be considered as in flight, but // that's not a problem as the cache has been primed by 'request'. mCacheQueue.addAll(waitingRequests); //将所有的请求放入到缓存队列当中,由缓存队列完成请求响应... } } } }
3.6 public void stop()...
停止函数,将网络请求调度的线程和缓存请求调度的线程停止...没什么可说的,非常的简单...
public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (int i = 0; i < mDispatchers.length; i++) { if (mDispatchers[i] != null) { mDispatchers[i].quit(); } } }
3.7 public void cancleAll(final Object tag){}
public void cancleAll(RequestFilter filter){}
public interface RequestFilter {
public boolean apply(Request<?> request);
}
请求撤销函数...前面两个函数都是撤销请求的方式,只不过一个是通过设置过滤器来过滤掉请求,这个过滤方式需要我们人为进行重写...重写第三个函数...根据指定的过滤取消掉这一类的请求,而第二种则是通过设置标识符来过滤掉与标识符相同的一类请求...一般是设置撤销某一线程内的请求..比如说一个Activity中的所有请求...那么我们就给Activity设置上tag就行了...那么通过tag的获取,就能够撤销掉Activity的所有请求...
3.8 public int getSequenceNumber(){}
获取序列号的函数...为请求设置相应的序列号...非常的简单..
public int getSequenceNumber() { return mSequenceGenerator.incrementAndGet(); }
3.9 public Cache getCache(){}
获取缓存对象...用于保存缓存的数据...
public Cache getCache() { return mCache; }
RequestQueue类中所有的东西都进行了一下解释...说了一下其中涉及的东西,以及这个类的内部原理...缓存数据的存储,缓存请求线程的调度,以及网络请求的调度也只是开了个头,是我们能有一个初步的印象...其他的东西将在后续进行记录和解析..
学习内容:
1.CacheDispatcher缓存请求调度...
2.Cache缓存数据的保存...
3.DiskBasedCache基于磁盘的缓存类实现方式...
前面说到使用Volley发送网络请求的时候,每一个请求都会保存在请求队列RequestQueue中,RequestQueue会开启一个缓存请求调度线程,若干网络请求调度线程来对这些请求进行相关的处理...缓存调度线程通过调用缓存数据来完成这次请求的提交...而网络请求调度线程通过发送网络请求来完成请求的提交...最后通过把数据发送给客户端就完成了这次请求和响应的整个过程...
1.CacheDispathcer.java(缓存请求线程)...
缓存请求调度线程,用于处理缓存过的请求..当此类开启的时候会不断的从缓存队列当中取出请求,如果缓存存在,那么从缓存获取的数据通过deliverResponse方法去分发响应,如果没有进行缓存,那么通过发送网络请求从服务器端获取数据,因此在实例化对象时还需要传递网络请求队列...
1.1 变量的定义..
一些相关变量的定义,非常的简单...
private static final boolean DEBUG = VolleyLog.DEBUG; /** The queue of requests coming in for triage. */ private final BlockingQueue<Request> mCacheQueue; //缓存请求队列.. /** The queue of requests going out to the network. */ private final BlockingQueue<Request> mNetworkQueue; //网络请求队列... /** The cache to read from. */ private final Cache mCache; //缓存,用于保存缓存数据... /** For posting responses. */ private final ResponseDelivery mDelivery; //用于分发请求... /** Used for telling us to die. */ private volatile boolean mQuit = false; //布尔值,用于判断线程是否结束...
1.2 public CacheDispatcher(){}
CacheDispatcher的构造函数,用于实例化缓存请求线程调度的对象...
public CacheDispatcher( BlockingQueue<Request> cacheQueue, BlockingQueue<Request> networkQueue, Cache cache, ResponseDelivery delivery) { mCacheQueue = cacheQueue; mNetworkQueue = networkQueue; mCache = cache; mDelivery = delivery; }
1.3 public void quit(){}
退出函数...当主线程注销的同时...这些线程也就被注销掉了...非常的简单...
public void quit() { mQuit = true; interrupt(); }
1.4 public void run(){}
前面已经说到,CacheDispatcher是一个线程,那么自然需要继承Thread,其中run()是线程内部中最重要的方法...Volley为什么能够异步实现数据信息的加载,就是因为缓存请求调度和网络网络请求调度是通过继承Thread来实现的,这两个类是异步线程的实现类...也正是因为是异步线程,才能够完成异步操作..从而使得请求能够更加的高效,有质量,同时也减少了服务器上的负担...
public void run() { if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//设置线程的优先级... mCache.initialize(); //缓存的初始化... while (true) { try { final Request request = mCacheQueue.take(); //从缓存队列中取出请求... request.addMarker("cache-queue-take"); //添加标识符...方便以后调试.. if (request.isCanceled()) { //如果中途撤消了请求... /* * 那么结束这次请求,将请求从当前请求队列中移除..并且如果相同请求队列中还有这一类的请求,那么将所有请求移出相同请求队列..将请求交给缓存队列处理.. */ request.finish("cache-discard-canceled"); continue; } // Attempt to retrieve this item from cache. Cache.Entry entry = mCache.get(request.getCacheKey()); if (entry == null) { //判断缓存中是否保存了这次请求.. request.addMarker("cache-miss"); //缓存丢失... // Cache miss; send off to the network dispatcher. mNetworkQueue.put(request); //交给网络请求队列执行网络请求... continue; } // If it is completely expired, just send it to the network. if (entry.isExpired()) { //判断缓存的新鲜度... request.addMarker("cache-hit-expired"); //已经不新鲜,说白了就是失效... request.setCacheEntry(entry); //保存entry mNetworkQueue.put(request); //提交网络请求... continue; } request.addMarker("cache-hit"); //添加标识缓存命中... Response<?> response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); //建立一个response对象解析响应返回的数据... request.addMarker("cache-hit-parsed"); //添加标识缓存命中,并且已被解析.. if (!entry.refreshNeeded()) { //判断缓存是需要刷新.. mDelivery.postResponse(request, response);//不需要刷新就直接发送... } else { request.addMarker("cache-hit-refresh-needed");//添加标识标识缓存命中后需要刷新... request.setCacheEntry(entry); //保存entry... 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; } } }
这里我解释一下缓存刷新的概念...缓存刷新的概念表示,客户端虽然提交过这次请求,并且请求获取的数据报中的数据也已经被保存在了缓存当中,但是服务器端发生了改变..也就是说服务器数据发生了变化,那么就会导致这次请求对应的数据已经有所变化了,但是客户端缓存保存的仍然是没有改变的数据,因此即使缓存命中也无法获取到正确的数据信息,因此需要重新提交新的网络请求...因此这就是缓存刷新的概念...
同时我们还可以看到,一直都有entry的出现...上面只是说保存了entry对象,但是却没有说它到底是做什么的...下面说一下Entry对象...说Entry就不得不说Cache了...
2.Cache.java
保存缓存数据的类...内部定义了一些抽象方法和一个Entry类...
2.1 抽象方法的定义...
几种抽象方法的操作都是针对于Map的操作...也正是Map<String,CacheHeader>以键值对的形式保存在一个集合当中...并且CacheHeader中封装了Entry对象...
public Entry get(String key); //通过键获取Entry对象... public void put(String key, Entry entry); //在缓存中放入键值... public void initialize(); //缓存的初始化... public void invalidate(String key, boolean fullExpire); //使Entry对象在缓存中无效... public void remove(String key); //移除函数... public void clear(); //清空函数...
2.2 Entry类...
Entry是定义在Cache类中的一个静态类...用于保存缓存的一些属性,比如说过期时间,失效时间...何时需要刷新等等...以及缓存数据...
public static class Entry { public byte[] data; //保存Body实体中的数据... public String etag; //用于缓存的新鲜度验证... public long serverDate; //整个请求-响应的过程花费的时间... public long ttl; //缓存过期的时间... public long softTtl; //缓存新鲜时间.. public Map<String, String> responseHeaders = Collections.emptyMap(); //Map集合..用于保存请求的url和数据... public boolean isExpired() { return this.ttl < System.currentTimeMillis();//判断是否新鲜(失效)... } public boolean refreshNeeded() { //判断缓存是否需要刷新... return this.softTtl < System.currentTimeMillis(); } }
Cache类中主要的东西还是封装的抽象方法,有了这些方法,缓存才能够真正的起到作用,只有类而没有实现方法,那么显然缓存是没意义的,因此定义了这些具体如何实现缓存的方法,之所以定义为抽象,目的是非常清晰的,为了形成良好的扩展,我们可以使用Volley的缓存机制,那么同时我们自己也可以自己重写缓存机制...至于如何重写那就取决于需求了...我们来说一下系统提供了缓存机制的实现...
3.DiskBasedCache.java
缓存类的具体实现类,是基于磁盘的一种缓存机制...
首先是变量的定义和构造函数...
3.1 变量的定义和构造函数...
private final Map<String, CacheHeader> mEntries = new LinkedHashMap<String, CacheHeader>(16, .75f, true); //map集合,以键值对的形式保存缓存... private long mTotalSize = 0; //额外增加的大小...用于缓存大小发生变化时需要记录增加的数值... /** The root directory to use for the cache. */ private final File mRootDirectory; //缓存文件的根目录.. /** The maximum size of the cache in bytes. */ private final int mMaxCacheSizeInBytes; //缓存分配的最大内存... /** Default maximum disk usage in bytes. */ private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024; //默认分配的最大内存.. /** High water mark percentage for the cache */ private static final float HYSTERESIS_FACTOR = 0.9f; //浮点数..用于缓存优化.. /** Magic number for current version of cache file format. */ private static final int CACHE_MAGIC = 0x20120504; //缓存的内存分区.. //构造函数...这个是通过人为指定缓存的最大大小来实例化一个缓存对象... public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { mRootDirectory = rootDirectory; mMaxCacheSizeInBytes = maxCacheSizeInBytes; } //使用了系统默认分配的缓存大小来实例化一个缓存对象... public DiskBasedCache(File rootDirectory) { this(rootDirectory, DEFAULT_DISK_USAGE_BYTES); }
3.2 public synchronized void initialize(){}
缓存初始化函数,用于初始化缓存,初始化的过程是对缓存文件的扫描,在源码中我们可以看到文件的遍历过程,把所有的缓存数据进行保存,然后写入到内存当中...其中调用了其他函数ReadHeader()..这个函数通过对缓存文件数据的读取,将数据保存在Entry当中...最后通过键值对的形式把封装后的Entry保存起来...
@Override public synchronized void initialize() { if (!mRootDirectory.exists()) { //如果缓存文件不存在,那么需要报错... if (!mRootDirectory.mkdirs()) { VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); } return; } File[] files = mRootDirectory.listFiles();//获取所有的缓存文件..保存在数组中... if (files == null) { return; } for (File file : files) { //通过遍历所有文件,将数据进行保存... FileInputStream fis = null; try { fis = new FileInputStream(file); //获取文件的I/O流... CacheHeader entry = CacheHeader.readHeader(fis); //将读取的数据保存在Entry当中... entry.size = file.length(); putEntry(entry.key, entry); //将封装好的数据保存在Map当中... } catch (IOException e) { if (file != null) { file.delete(); } } finally { try { if (fis != null) { fis.close(); } } catch (IOException ignored) { } } } }
3.3 private void putEntry(String key,CacheHeader entry){}
这个函数是对缓存数据放入Map中的一个调用过程,首先需要判断缓存是否已经存在,如果内部不存在,那么直接设置缓存的数据长度大小,如果内部存在,那么说明当前的缓存数据大小已经发生了变化,也就是同一个键值对应的数据已经发生了变化,那么我们需要重新设置缓存数据增加的大小...
private void putEntry(String key, CacheHeader entry) { //首先对缓存命中进行判断... if (!mEntries.containsKey(key)) { mTotalSize += entry.size; //如果缓存中没有保存过当前数据...那么定义缓存数据的长度... } else { CacheHeader oldEntry = mEntries.get(key);//如果缓存命中,那么说明缓存的数据大小已经发生了改变.. mTotalSize += (entry.size - oldEntry.size);//赋上新的数据长度值... } mEntries.put(key, entry); //调用放入函数... }
3.4 public synchronized void put(String key, Entry entry) {}
通过键值对的形式将Entry数据保存在HashMap当中,保存的值是以CacheHeader的形式进行保存的,CacheHeader算是对Entry的数据的一个封装,将Entry中保存的数据封装,最后以Map的形式将数据保存起来...
public synchronized void put(String key, Entry entry) { pruneIfNeeded(entry.data.length); //判断缓存是否需要经过优化... File file = getFileForKey(key); //获取缓存文件的key值.. try { FileOutputStream fos = new FileOutputStream(file); //获取文件的I/O流.. CacheHeader e = new CacheHeader(key, entry);//创建一个新的CacheHeader对象... e.writeHeader(fos); //按照指定方式写头部信息,包括缓存过期时间,新鲜度等等... fos.write(entry.data); //写数据信息... fos.close(); putEntry(key, e); //以键值对的形式将数据保存... return; } catch (IOException e) { } boolean deleted = file.delete(); if (!deleted) { VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); } }
我们可以看到这里调用了一个pruneIfNeeded()函数...这个函数是在数据被放入之前需要进行调用的...下面就来说一下...
3.5 private void pruneIfNeeded(int neededSpace) {}
这个函数的调用目的是对当前缓存数据大小的一个判断过程,如果缓存的数据大小小于指定的规格大小,那么直接就return就可以了,说明指定的大小可以满足缓存的存储大小,但是如果缓存的数据大小超出了我们预先指定的规格,那么我们需要对缓存数据进行优化,优化成可以满足预先指定的规格...具体优化的大小为0.9倍的指定的大小...
private void pruneIfNeeded(int neededSpace) { if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { return; //如果缓存数据的大小小于预先指定的大小..直接return... } if (VolleyLog.DEBUG) { VolleyLog.v("Pruning old cache entries."); } long before = mTotalSize; //表示文件数据减小的长度... int prunedFiles = 0; //优化的文件数量... long startTime = SystemClock.elapsedRealtime();//获取时间..用于调试过程... Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator(); //对Map保存的数据进行遍历... while (iterator.hasNext()) { //迭代过程... Map.Entry<String, CacheHeader> entry = iterator.next(); CacheHeader e = entry.getValue(); //获取entry对象... boolean deleted = getFileForKey(e.key).delete(); //删除原本的文件名...对文件名进行优化... if (deleted) { mTotalSize -= e.size; //设置数据减小的长度... } else { VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", e.key, getFilenameForKey(e.key)); } iterator.remove(); //移除迭代... prunedFiles++; //表示优化的文件数量... if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { //如果优化后的大小小于预先设定的大小...那么就结束所有操作... break; } } if (VolleyLog.DEBUG) { //调试时需要显示的数据... VolleyLog.v("pruned %d files, %d bytes, %d ms", prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime); } }
这里涉及了优化过程,其实优化的也仅仅是文件名字的长度,为了满足预先设置的大小需求,同时还要作为键值保存在Map当中,因此文件名字还需要具有唯一性,因此这个优化是需要考虑到一些事情的...优化将调用 getFilenameForKey()函数...简答说一下这个函数...
3.6 private String getFilenameForKey(String key) {}
这个函数其实就是优化缓存大小的过程,被优化的东西不是数据,而是键的优化...由于原本保存的键值对其中的键值是保存的文件的名称,由于文件的名称唯一,所以保存的时候也就不会出现由于相同键值二次插入而导致的错误发生...但是由于其长度不一定能顾满足预先设置的缓存大小,因此我们需要对键值进行优化...优化的过程是折半截取文件名并获取Hash码的过程从而能够获取唯一的键值...
private String getFilenameForKey(String key) { int firstHalfLength = key.length() / 2; //获取名字长度的一般... String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode()); //对文件名字符串进行截取... localFilename += String.valueOf(key.substring(firstHalfLength).hashCode()); //获取其Hash码.. return localFilename; }
3.7 public synchronized Entry get(String key) {}
通过给定键值来获取缓存数据中的内容...非常的简单,涉及的东西到不是很多,思想就是通过键值来获取缓存文件,通过获取缓存文件的I/O流,然后将数据写出来..最后进行返回就可以了...
public synchronized Entry get(String key) { CacheHeader entry = mEntries.get(key); //通过key获取Entry对象.. // if the entry does not exist, return. if (entry == null) { return null; //如果不存在,直接return掉... } File file = getFileForKey(key); //返回键值对应的缓存文件... CountingInputStream cis = null; try { cis = new CountingInputStream(new FileInputStream(file)); //封装成流... CacheHeader.readHeader(cis); // eat header byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead)); //读取数据... return entry.toCacheEntry(data); //返回entry中保存的数据... } catch (IOException e) { VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); remove(key); return null; } finally { if (cis != null) { try { cis.close(); } catch (IOException ioe) { return null; } } } }
这里将文件封装成流使用了CountInputStream()...写入数据的方法则是采用了streamToBytes()方法...
3.8 CountInputStream()...
这个类继承与FilterInputStream(),FilterInputStream()继承与InputStream()...类与类之间采用了装饰者模式...
private static class CountingInputStream extends FilterInputStream { private int bytesRead = 0; private CountingInputStream(InputStream in) { super(in); } @Override public int read() throws IOException { int result = super.read(); if (result != -1) { bytesRead++; } return result; } @Override public int read(byte[] buffer, int offset, int count) throws IOException { int result = super.read(buffer, offset, count); if (result != -1) { bytesRead += result; } return result; } }
3.9 private static byte[] streamToBytes(InputStream in, int length) throws IOException {}
通过指定的输入流,对数据进行写入的一个过程...非常的简单,没什么可以进行解释的...
private static byte[] streamToBytes(InputStream in, int length) throws IOException { byte[] bytes = new byte[length]; int count; int pos = 0; while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) { pos += count; } if (pos != length) { throw new IOException("Expected " + length + " bytes, read " + pos + " bytes"); } return bytes; }
3.10 public synchronized void clear() {}
清空所有的文件缓存,其实就是delete,释放内存...
public synchronized void clear() { File[] files = mRootDirectory.listFiles(); if (files != null) { for (File file : files) { file.delete(); } } mEntries.clear(); mTotalSize = 0; VolleyLog.d("Cache cleared."); }
3.11 public void removeEntry(String key){}
public synchronized void remove(String key) {}
移除函数,如果缓存内部已经存在了当前键值的缓存数据,那么将这个键值进行删除...比较简单...函数调用由函数1调用函数2...
private void removeEntry(String key) { CacheHeader entry = mEntries.get(key); if (entry != null) { mTotalSize -= entry.size; mEntries.remove(key); } }
public synchronized void remove(String key) { boolean deleted = getFileForKey(key).delete(); removeEntry(key); if (!deleted) { VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", key, getFilenameForKey(key)); } }
这样与以键值对形式保存的缓存数据集合的操作就全部完成了..剩下的源码过程涉及的就是缓存数据的封装...CacheHeader类,由于CacheHeader还是涉及了一些其他的东西,因此就单独抽出来...
4.CacheHeader.java
CacheHeader,对缓存数据的一个打包过程...
4.1 变量的定义...
public long size; /** The key that identifies the cache entry. */ public String key; //缓存的键值 /** ETag for cache coherence. */ public String etag; //新鲜度验证... /** Date of this response as reported by the server. */ public long serverDate; //响应过程中花费的时间... /** TTL for this record. */ public long ttl; //缓存过期时间... /** Soft TTL for this record. */ public long softTtl; //缓存的新鲜时间... /** Headers from the response resulting in this cache entry. */ public Map<String, String> responseHeaders; //保存响应头部信息的map
4.2 CacheHeader的构造函数...
构造函数也是很简单的,将Entry中所有保存的数据进行了一下封装...包含着缓存的一些基本属性,以及数据报的头部信息...
public CacheHeader(String key, Entry entry) { this.key = key; this.size = entry.data.length; this.etag = entry.etag; this.serverDate = entry.serverDate; this.ttl = entry.ttl; this.softTtl = entry.softTtl; this.responseHeaders = entry.responseHeaders; }
4.3 public static CacheHeader readHeader(InputStream is) throws IOException {}
用于获取响应数据报的头部属性,也就是Header,其中包括了一些Http版本的信息等一些基本条件...这个函数由3.7上面的get()函数进行调用,将缓存数据保存的头部数据进行了相关的获取...这里调用了非常多的读取函数,都是按照指定的格式对数据获取的方式...
public static CacheHeader readHeader(InputStream is) throws IOException { CacheHeader entry = new CacheHeader(); int magic = readInt(is); if (magic != CACHE_MAGIC) { // don't bother deleting, it'll get pruned eventually throw new IOException(); } entry.key = readString(is); entry.etag = readString(is); if (entry.etag.equals("")) { entry.etag = null; } entry.serverDate = readLong(is); entry.ttl = readLong(is); entry.softTtl = readLong(is); entry.responseHeaders = readStringStringMap(is); return entry; }
4.4 public Entry toCacheEntry(byte[] data) {}
将缓存中的数据封装给CacheHeader...
public Entry toCacheEntry(byte[] data) { Entry e = new Entry(); e.data = data; e.etag = etag; e.serverDate = serverDate; e.ttl = ttl; e.softTtl = softTtl; e.responseHeaders = responseHeaders; return e; }
4.5 public boolean writeHeader(OutputStream os){}
在向缓存中放入数据的时候,也就是调用put()函数的时候,需要将缓存将缓存的内容写入到文件当中,需要写入缓存包含的一些头部信息Header以及Body实体的数据部分...那么写入头部信息就需要调用上述函数...这个函数只是一个调用的模块...调用了许多的write()函数...其实都是一些基本函数的调用,writeInt()一类的函数是通过使用位运算的方式来完成数据的读取,而writeString()方法,则是采用字节流的形式来完成数据的读取...涉及到了很多的从内存读取的read()函数,以及写入内存的write()函数...在这里就不进行一一粘贴了...也没什么好解释的...
public boolean writeHeader(OutputStream os) { try { writeInt(os, CACHE_MAGIC); writeString(os, key); writeString(os, etag == null ? "" : etag); writeLong(os, serverDate); writeLong(os, ttl); writeLong(os, softTtl); writeStringStringMap(responseHeaders, os); os.flush(); return true; } catch (IOException e) { VolleyLog.d("%s", e.toString()); return false; } }
static void writeInt(OutputStream os, int n) throws IOException { os.write((n >> 0) & 0xff); os.write((n >> 8) & 0xff); os.write((n >> 16) & 0xff); os.write((n >> 24) & 0xff); }
也就这么多了,缓存请求的线程调度类,缓存类,以及缓存的实现也都进行了详细的介绍,Volley采用接口的方式形成了一种良好的扩展,在这里也是很容易体现出来的,就拿Cache来说吧,对外提供接口,方便去自己实现缓存,也可以去使用系统给设置的缓存类,DiskBasedCahce,形成了良好的扩展性...
学习内容:
1.NetWorkDispatcher网络请求线程调度...
2.NetWork网络请求抽象类...
3.BasicNetWork网络请求抽象类的具体实现...
4.NetWorkResponse接收网络请求返回的响应...
5.ResponseDelivery请求分配抽象类...
6.ExecutorDelivery请求分配的具体实现类...
上一章说完了缓存请求线程调度,那么现在就说一下网络请求线程调度是如何来完成的,网络请求线程调度也是有一个核心过程的,从网络上获取数据信息的过程, 首先从网络请求队列取出请求,如果请求存在,那么对请求进行相关的处理,如果没有请求,那么线程进入等待状态,取出请求之后需要先对请求是否已经被中途取 消进行相关的判断,如果已经请求已经被中途中断,那么结束这次的处理过程,如果没有取消,那么执行请求,获取服务器的返回数据,然后对返回的响应是够是 304响应进行相关的判断,如果是304响应,那么直接也结束对请求的处理。
304请求表示的是相同请求已经被服务器响应,并且返回了已经返回了相关的数据,由于服务器的状态是高并发的执行状态,有可能在同一时间段对两种或几种相同的请求进行相关的处理,那么对于这样多种相同的请求,服务器只需要响应一次数据就可以了,剩下的由ResponseDelivery去分发给所有与之相同的请求就可以了...也就是说服务器对于同一时间段的多个相同的请求只需要响应一次...
如果不是304响应,那么表示这次请求是一个新的请求,那么我们需要向服务器发送请求来获取服务器的响应,最后通过是否进行缓存进行判断之后,一个请求就可以被分发出去了...
1.NetWorkDispatcher.java
1.1 变量的定义
private final BlockingQueue<Request> mQueue; //请求队列...
/** The network interface for processing requests. */ private final Network mNetwork; //网络请求对象,用于执行网络请求工作 /** The cache to write to. */ private final Cache mCache; //缓存对象,用于缓存数据 /** For posting responses and errors. */ private final ResponseDelivery mDelivery; //用于分发请求... /** Used for telling us to die. */ private volatile boolean mQuit = false; // 用于关闭线程...
1.2 NetWorkDispatcher构造函数...
网络请求线程调度对象的初始化过程...构造函数比较简单,没什么说的...
public NetworkDispatcher(BlockingQueue<Request> queue,
Network network, Cache cache,
ResponseDelivery delivery) {
mQueue = queue;
mNetwork = network;
mCache = cache;
mDelivery = delivery; }
1.3 public void run(){}
run()方法,线程中最重要的方法,必须要继承的...整个方式就如同刚开始介绍的一样,从请求队列中取出请求,然后判断请求是否被中断,中断就结束处理,没有中断就发送请求,然后对服务器是否已经对这次请求返回了响应数据,如果服务器已经响应了与之相同的请求,那么就停止对这次请求的处理,由分发响应函数去处理,如果是一个新的请求,那么需要定义一个请求对象,然后发送...最后获取到服务器响应后,对响应进行分发...
@Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //设置线程优先级... Request request; while (true) { try { // Take a request from the queue. request = mQueue.take(); //取出请求... } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } try { request.addMarker("network-queue-take");//添加标识符.. // If the request was cancelled already, do not perform the // network request. if (request.isCanceled()) { //如果请求被中途取消... request.finish("network-discard-cancelled"); 添加标识符... continue; } // Tag the request (if API >= 14) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { //如果API>=10,那么为请求设置标签... TrafficStats.setThreadStatsTag(request.getTrafficStatsTag()); } // Perform the network request. NetworkResponse networkResponse = mNetwork.performRequest(request); //发送请求获取服务器的响应... request.addMarker("network-http-complete"); // If the server returned 304 AND we delivered a response already, // we're done -- don't deliver a second identical response. if (networkResponse.notModified && request.hasHadResponseDelivered()) { //如果这个请求是没有改变..说白了就是请求如果是相同的...那么服务器就返回一次响应...其他的通过Delivery去分发就行了... request.finish("not-modified"); continue; } // Parse the response here on the worker thread. Response<?> response = request.parseNetworkResponse(networkResponse);//如果是一个新的请求,那么就需要新建一个请求对象... request.addMarker("network-parse-complete"); // Write to cache if applicable. // TODO: Only update cache metadata instead of entire record for 304s. //如果请求允许被缓存,并且响应的数据不为空...那么将这次请求放入到缓存中.. if (request.shouldCache() && response.cacheEntry != null) { mCache.put(request.getCacheKey(), response.cacheEntry); //放入的操作... request.addMarker("network-cache-written"); } // Post the response back. request.markDelivered(); //确认请求要被分发... mDelivery.postResponse(request, response); 发送请求... } catch (VolleyError volleyError) { parseAndDeliverNetworkError(request, volleyError); } catch (Exception e) { //如果出现了错误,那么把错误发送... VolleyLog.e(e, "Unhandled exception %s", e.toString()); mDelivery.postError(request, new VolleyError(e)); } } } //一个错误处理函数,当发生解析请求或者是分发请求时出现错误时进行调用... private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) { error = request.parseNetworkError(error); mDelivery.postError(request, error); }
上面涉及到了执行请求...以及分发服务器响应,但是是如何实现的呢?我们先看如何去执行一个请求...
2.NetWork.java
NetWork.java是一个抽象的接口...对外提供一个执行请求方法的接口,方便其他类去实现...
package com.android.volley; /** * An interface for performing requests. */ public interface Network { /** * Performs the specified request. * @param request Request to process * @return A {@link NetworkResponse} with data and caching metadata; will never be null * @throws VolleyError on errors */ public NetworkResponse performRequest(Request<?> request) throws VolleyError; }
3.BasicNetWork.java
BasicNetWork是实现NetWork的具体抽象类,是如何执行请求的一个具体过程,其中内部也封装了一些其他方法...
3.1 变量的定义
这几个变量的定义相对比较抽象,但是会在下面细说...
protected static final boolean DEBUG = VolleyLog.DEBUG; //用于Volley内部调试.. private static int SLOW_REQUEST_THRESHOLD_MS = 3000;//对于缓慢的请求定义了一个请求时间... private static int DEFAULT_POOL_SIZE = 4096; //Int值,用于以后的获取网络数据... protected final HttpStack mHttpStack; //Http请求栈... protected final ByteArrayPool mPool; //ByteArrayPool对象...
3.2 public BasicNetWork(){}
构造函数...构造函数构造了一个保存Http请求栈,以及一个获取网络数据对象...
public BasicNetwork(HttpStack httpStack) { // If a pool isn't passed in, then build a small default pool that will give us a lot of // benefit and not use too much memory. this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); //调用下面函数.. } /** * @param httpStack HTTP stack to be used * @param pool a buffer pool that improves GC performance in copy operations */ //建立一个BasicNetWork对象,这个对象保存了一个请求栈区,另一个参数用于获取数据而建立的对象... public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { mHttpStack = httpStack; mPool = pool; }
3.3 public NetworkResponse performRequest(Request<?> request) throws VolleyError {}
至关重要的方法,用于执行请求,这里我们也可以看到,请求传递的参数并没有写死,而是使用了泛型的方式形成了良好的扩展,也即是说传递过来的请求是什么类型,那么就执行什么类型的请求...
@Override public NetworkResponse performRequest(Request<?> request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime(); //获取请求开始的时间,用于调试... while (true) { HttpResponse httpResponse = null; //请求响应对象... byte[] responseContents = null; //请求内容对象... Map<String, String> responseHeaders = new HashMap<String, String>(); //map集合,用于保存数据报的Header中的数据.. try { // Gather headers. Map<String, String> headers = new HashMap<String, String>(); //用于保存缓存下来的Header...缓存的Header一般包含服务响应的整体时间,缓存新鲜度验证等属性值... addCacheHeaders(headers, request.getCacheEntry()); //添加请求头部的过程... httpResponse = mHttpStack.performRequest(request, headers);//执行请求,获取响应.. StatusLine statusLine = httpResponse.getStatusLine();//获取响应状态... int statusCode = statusLine.getStatusCode();//获取状态码.. responseHeaders = convertHeaders(httpResponse.getAllHeaders());//获取响应后的Header中的所有数据... // Handle cache validation. //对304响应的一个判断过程,如果是304响应,那么直接走缓存,从缓存获取数据... if (statusCode == HttpStatus.SC_NOT_MODIFIED) { return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, request.getCacheEntry().data, responseHeaders, true); } // Some responses such as 204s do not have content. We must check. if (httpResponse.getEntity() != null) {//判断响应是否是204判断,由于204响应时不返回数据信息的...因此需要判断... responseContents = entityToBytes(httpResponse.getEntity()); //如果存在内容,那么通过getEntity()方法获取数据... } else { // Add 0 byte response as a way of honestly representing a // no-content request. responseContents = new byte[0]; //返回空数据... } // if the request is slow, log it. long requestLifetime = SystemClock.elapsedRealtime() - requestStart; logSlowRequests(requestLifetime, request, responseContents, statusLine);//如果一个请求的时间超过了指定的缓慢请求时间,那么需要显示这个时间... if (statusCode != HttpStatus.SC_OK && statusCode != HttpStatus.SC_NO_CONTENT) { throw new IOException(); //如果请求状态出现错误,那么抛出异常... } return new NetworkResponse(statusCode, responseContents, responseHeaders, false); //如果请求不是304请求,并且上面异常情况也没有发生,那么返回新的请求中的内容+头部以及状态码... } catch (SocketTimeoutException e) { attemptRetryOnException("socket", request, new TimeoutError()); //套接字异常.. } catch (ConnectTimeoutException e) { attemptRetryOnException("connection", request, new TimeoutError());//连接超时异常... } catch (MalformedURLException e) { throw new RuntimeException("Bad URL " + request.getUrl(), e);//url异常... } catch (IOException e) {//IO异常的处理... int statusCode = 0; NetworkResponse networkResponse = null; if (httpResponse != null) { //如果响应存在,但是出现异常.. statusCode = httpResponse.getStatusLine().getStatusCode(); //返回异常状态码..可以使客户端知道异常情况... } else { throw new NoConnectionError(e); } VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); if (responseContents != null) {//如果响应内容不是空... networkResponse = new NetworkResponse(statusCode, responseContents, responseHeaders, false);//获取响应... //请求需要进行验证,或者是需要授权异常处理... if (statusCode == HttpStatus.SC_UNAUTHORIZED || statusCode == HttpStatus.SC_FORBIDDEN) { attemptRetryOnException("auth", request, new AuthFailureError(networkResponse)); } else { // TODO: Only throw ServerError for 5xx status codes. throw new ServerError(networkResponse); } } else { throw new NetworkError(networkResponse); } } } }
这个函数涉及了其他几个函数...
3.4 private void logSlowRequests(long requestLifetime, Request<?> request,byte[] responseContents, StatusLine statusLine) {}
记录一个请求——响应的时间超出了预先设置的缓慢请求时间,那么需要进行记录...记录响应码,响应内容等等函数比较的简单...
private void logSlowRequests(long requestLifetime, Request<?> request, byte[] responseContents, StatusLine statusLine) { if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " + "[rc=%d], [retryCount=%s]", request, requestLifetime, responseContents != null ? responseContents.length : "null", statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount()); }
3.5 private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {}
添加请求的请求头部,从缓存当中获取头部信息添加到这次请求...
private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) { // If there's no cache entry, we're done. if (entry == null) { //缓存数据为空...直接return return; } if (entry.etag != null) { headers.put("If-None-Match", entry.etag); //返回新鲜度验证标志.. } if (entry.serverDate > 0) { Date refTime = new Date(entry.serverDate); //返回请求——响应的时间... headers.put("If-Modified-Since", DateUtils.formatDate(refTime)); } }
3.6 private static void attemptRetryOnException(String logPrefix, Request<?> request,VolleyError exception) throws VolleyError {}
尝试重试策略方法...如果请求发生了异常...
private static void attemptRetryOnException(String logPrefix, Request<?> request, VolleyError exception) throws VolleyError { RetryPolicy retryPolicy = request.getRetryPolicy(); //获取请求的重试策略... int oldTimeout = request.getTimeoutMs(); try { retryPolicy.retry(exception); //重试方式执行... } catch (VolleyError e) { request.addMarker( String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); throw e; } request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); }
3.7 private static Map<String, String> convertHeaders(Header[] headers) {}
上面涉及到了当响应返回的时候需要获取响应数据报的Header,将所有的Header数据获取并保存...以键值对的形式保存在map集合当中...最后传递给responseHeaders集合...
private static Map<String, String> convertHeaders(Header[] headers) { Map<String, String> result = new HashMap<String, String>(); for (int i = 0; i < headers.length; i++) { result.put(headers[i].getName(), headers[i].getValue()); } return result; }
3.8 private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError {}
当Http请求响应成功时,我们需要从HttpEntity中获取返回的内容数据...数据的获取调用此函数..这个函数采用了PoolingArrayByteOutputStream()流..这个流采用了字节回收机制,可以减少内存的分配和回收...我们先知道就行...后面还会具体说明...
private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError { PoolingByteArrayOutputStream bytes = new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength()); //新建一个流对象.. byte[] buffer = null; //缓冲字节.. try { InputStream in = entity.getContent();//将Entity中保存的内容封装成流... if (in == null) { throw new ServerError(); } buffer = mPool.getBuf(1024); //缓冲流分配...首先new一个缓冲字节数组... int count; while ((count = in.read(buffer)) != -1) { bytes.write(buffer, 0, count); //写入数据... } return bytes.toByteArray();//返回数据.. } finally { try { // Close the InputStream and release the resources by "consuming the content". entity.consumeContent(); } catch (IOException e) { // This can happen if there was an exception above that left the entity in // an invalid state. VolleyLog.v("Error occured when calling consumingContent"); } mPool.returnBuf(buffer); bytes.close(); } }
这样总体就完成了请求之后响应数据的获取,也就是数据报的Header+Body的数据被保存了起来...那么完成了数据的获取,就需要响应数据的分发了...分发到请求才是请求——响应的一个最终完成过程...
4.NetWorkResponse.java
那么响应的传递就需要通过NetWorkResponse来进行传递,无论是从网络上获取的请求数据,还是从缓存当中获取的请求数据,都会被封装成NetWorkResponse,然后传递给相应请求中的parseNetWorkResponse方法,在进行下一步的处理...
package com.android.volley; import org.apache.http.HttpStatus; import java.util.Collections; import java.util.Map; /** * Data and headers returned from {@link Network#performRequest(Request)}. */ public class NetworkResponse { //对服务器的响应进行一个彻底封装... public NetworkResponse(int statusCode, byte[] data, Map<String, String> headers, boolean notModified) { this.statusCode = statusCode; this.data = data; this.headers = headers; this.notModified = notModified; } //构造函数..用来调用第一个构造函数,传递过来的响应只包含数据部分... public NetworkResponse(byte[] data) { this(HttpStatus.SC_OK, data, Collections.<String, String>emptyMap(), false); } //第二个构造函数,通过响应的数据和相关头部来调用第一个构造函数... public NetworkResponse(byte[] data, Map<String, String> headers) { this(HttpStatus.SC_OK, data, headers, false); } /** The HTTP status code. */ public final int statusCode; //响应状态码... /** Raw data from this response. */ public final byte[] data; //响应数据... /** Response headers. */ public final Map<String, String> headers; //以键值对的形式保存首部.. /** True if the server returned a 304 (Not Modified). */ public final boolean notModified; //304响应的判断... }
NetWorkResponse只是对服务器的响应的一个进一步封装,以参数的形式传递到Request.parseNetWorkResponse()方法...如果中间并么有出现什么异常情况,那么最后相应实现了Request.parseNetWorkResponse类会调用Response.success方法,将这次请求进行最后的封装,封装成Response<T>的形式,请求是什么类型,T就是什么类型...
5.Response.java
package com.android.volley; public class Response<T> { /** Callback interface for delivering parsed responses. */ public interface Listener<T> { /** Called when a response is received. */ public void onResponse(T response); //当一个请求——相应成功后的监听... } /** Callback interface for delivering error responses. */ public interface ErrorListener { /** * Callback method that an error has been occurred with the * provided error code and optional user-readable message. */ public void onErrorResponse(VolleyError error); //当请求出现了错误时,需要监听错误.. } /** Returns a successful response containing the parsed result. */ public static <T> Response<T> success(T result, Cache.Entry cacheEntry) { return new Response<T>(result, cacheEntry); //success方法,当请求被解析成功时调用的方法... } /** * Returns a failed response containing the given error code and an optional * localized message displayed to the user. */ public static <T> Response<T> error(VolleyError error) { return new Response<T>(error); //如果解析请求时失败需要封装错误... } /** Parsed response, or null in the case of error. */ public final T result; //响应返回的结果.. /** Cache metadata for this response, or null in the case of error. */ public final Cache.Entry cacheEntry; //缓存... /** Detailed error information if <code>errorCode != OK</code>. */ public final VolleyError error; //一个错误对象...记录错误的生成... /** True if this response was a soft-expired one and a second one MAY be coming. */ public boolean intermediate = false; //判断一个请求是否失效... /** * Returns whether this response is considered successful. */ public boolean isSuccess() { return error == null; //如果成功就没有错误传递... } private Response(T result, Cache.Entry cacheEntry) { //对成功时封装的构造函数... this.result = result; this.cacheEntry = cacheEntry; this.error = null; } private Response(VolleyError error) {/失败时封装的构造函数... this.result = null; this.cacheEntry = null; this.error = error; } }
当响应被封装成Response之后,就需要向客户端发送响应了,通过postResponse方法进行发送,如果一个响应成功,我们需要发送响应,但是如果中途出现了失败,那么我们需要把错误发送,需要让客户端清楚到底是发生了什么错误,这样在错误发生时提示用户到底应该怎样进行操作...
6.ResponseDelivery.java
解析响应之后的发送类,只是一个抽象的接口,我们可以人为去重写如何发送响应...而系统默认则采用线程池的方式对响应进行发送...
package com.android.volley; public interface ResponseDelivery { /** * Parses a response from the network or cache and delivers it. */ public void postResponse(Request<?> request, Response<?> response); //对响应进行发送... /** * Parses a response from the network or cache and delivers it. The provided * Runnable will be executed after delivery. */ public void postResponse(Request<?> request, Response<?> response, Runnable runnable); //对响应发送,同时开启一个线程去执行其他事情...线程在这个方法结束后执行... /** * Posts an error for the given request. */ public void postError(Request<?> request, VolleyError error); //发送错误信息... }
7.ExecutorDelivery.java
发送请求或者是错误的具体实现类,采用线程池的方式对响应进行发送...无论是服务器响应正确还是错误,我们都需要对其进行封装发送给客户端,让客户端去清楚服务器到底返回了什么东西...
package com.android.volley; import android.os.Handler; import java.util.concurrent.Executor; public class ExecutorDelivery implements ResponseDelivery { /** Used for posting responses, typically to the main thread. */ private final Executor mResponsePoster; //定义一个线程池... //定义了一个Response的传输接口... public ExecutorDelivery(final Handler handler) { // Make an Executor that just wraps the handler. mResponsePoster = new Executor() { @Override public void execute(Runnable command) { handler.post(command); } }; } //构造函数,定义了一个线程池... public ExecutorDelivery(Executor executor) { mResponsePoster = executor; } //发送请求的抽象方法的实现... @Override public void postResponse(Request<?> request, Response<?> response) { postResponse(request, response, null); } /*最后都是通过调用此方法来发送请求,因为poseResponse的方法有两种 * 一种是public void postResponse(Request<?> request, Response<?> response); * 另一种是 public void postResponse(Request<?> request, Response<?> response, Runnable runnable); *这两个方法上面已经说过,就是一个带有一个附加线程,一个没有而已... *但最终都需要调用这个方法... */ @Override public void postResponse(Request<?> request, Response<?> response, Runnable runnable) { request.markDelivered(); //表示可以发送请求... request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); //用线程封装好Response..通过线程池的方式去管理这些线程...从这一步开始run()方法已经被调用了... } //如果出现了错误,那么将错误封装,同时也要发送给请求的客户端... @Override public void postError(Request<?> request, VolleyError error) { request.addMarker("post-error"); Response<?> response = Response.error(error); //封装错误.. mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); //发送错误... } @SuppressWarnings("rawtypes") private class ResponseDeliveryRunnable implements Runnable { private final Request mRequest; //请求 private final Response mResponse; //响应 private final Runnable mRunnable; //其他线程... //构造函数非常的简单..对三者的一个封装过程... public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { mRequest = request; mResponse = response; mRunnable = runnable; } //run方法... @SuppressWarnings("unchecked") @Override public void run() { // If this request has canceled, finish it and don't deliver. if (mRequest.isCanceled()) { //如果请求被中断,那么就不需要发送响应了... mRequest.finish("canceled-at-delivery"); return; } // Deliver a normal response or error, depending. if (mResponse.isSuccess()) { //如果服务器响应成功,中途没有错误的发生,,, mRequest.deliverResponse(mResponse.result);//将服务器返回的结果发送给客户端...这是最后的关键地方... } else { mRequest.deliverError(mResponse.error);//如果其中出现了失败,需要把错误发送... } // If this is an intermediate response, add a marker, otherwise we're done // and the request can be finished. //这里不知道该如何理解...翻译成中间响应... if (mResponse.intermediate) { mRequest.addMarker("intermediate-response"); //如果是需要进行调试过程... } else { mRequest.finish("done");//如果不是表示这次请求结束... } // If we have been provided a post-delivery runnable, run it. if (mRunnable != null) { //如果附加线程不是空,那么就启动附加线程.. mRunnable.run(); } } } }
我们可以看到,最后服务器的响应被封装之后,通过mRequest.deliveryResponse或者是mRequest.deliveryerror进行发送...而这两个方法就会在相应类型的请其中得到重写...因为所有的其他请求都是继承Request类的...Request类中只是一个抽象的方法,具体的实现在那些实际实现了Request的类中...而每一个实现类中都会去调用mListener.onResponse(response)方法,这里只表示请求成功时调用的方法...
abstract protected void deliverResponse(T response);
public interface Listener<T> { /** Called when a response is received. */ public void onResponse(T response); }
这样在客户端重写OnResponse方法之后,就彻底的完成了请求——响应的结束过程...数据也就成功的从服务器通过网络成功的发送给了客户端...
学习内容:
1.PoolingByteArrayOutputStream
2.ByteArrayPool
3.HttpStack
4.HurlStack
5.HttpHeaderParser
前面整体的解释了网络请求——响应的整个过程,只是其中还是涉及到了一些其他的类,因此在这里都说一说,最后几篇会是Volley正式做一些请求,这篇仍然是进行源码解析...
1.PoolingByteArrayOutputStream.java
PoolingByteArrayOutputStream继承与ByteArrayOutputStream...当ByteArrayOutputStream进行写入数据操作时,需要通过缓冲buf来作为缓冲机制,如果缓存的空间不足,那么需要new一个更大数量级的buf来作为缓冲机制,这样会增加内存的分配个释放的过程,而PoolingByteArrayOutputStream的优势在于它内部实现了回收机制,可以对Byte进行回收和再次利用,减少了频繁分配内存和释放的操作..
Byte回收池机制通过使用ArrayList,一共有两个ArrayList决定了,一个用于从小到大的顺序保存Byte[]...另一个按照时间顺序,用于缓存一旦满了时,按照时间顺序进行清除..
package com.android.volley.toolbox; import java.io.ByteArrayOutputStream; import java.io.IOException; public class PoolingByteArrayOutputStream extends ByteArrayOutputStream { private static final int DEFAULT_SIZE = 256; //定义缓冲池的默认字节大小.. private final ByteArrayPool mPool; //定义比特回收池对象... //按照默认的方式去构造一个缓冲池对象... public PoolingByteArrayOutputStream(ByteArrayPool pool) { this(pool, DEFAULT_SIZE); } //按照人为指定的大小去构造一个缓冲池对象... public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) { mPool = pool; buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE)); } //释放内存...关闭流的操作... @Override public void close() throws IOException { mPool.returnBuf(buf); buf = null; super.close(); } //返回一个缓冲池... @Override public void finalize() { mPool.returnBuf(buf); } //写入数据前需要调用的函数,确保在缓存有足够大的空间来满足给定的字节大小... private void expand(int i) { /* Can the buffer handle @i more bytes, if not expand it */ if (count + i <= buf.length) { return; } byte[] newbuf = mPool.getBuf((count + i) * 2); System.arraycopy(buf, 0, newbuf, 0, count); mPool.returnBuf(buf); buf = newbuf; } //写入数据函数... @Override public synchronized void write(byte[] buffer, int offset, int len) { expand(len); super.write(buffer, offset, len); } @Override public synchronized void write(int oneByte) { expand(1); super.write(oneByte); } }
PoolingByteArrayOutputStream只是对流的一个封装,内部实现了写操作的实现方法...它基于缓冲回收机制,那么缓冲回收机制的实现类也必然是需要我们去清楚的...需要明确是如何实现的缓冲回收...
2.ByteArrayPool.java
实现回收机制的实现类...通过使用两个ArrayList实现了缓冲池回收机制,一个是按照大小顺序来保存使用过的Byte[]缓冲,另一个则是按照时间顺序对Byte[]进行保存...
package com.android.volley.toolbox; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; import java.util.List; public class ByteArrayPool { private List<byte[]> mBuffersByLastUse = new LinkedList<byte[]>();//以时间的先后顺序保存Byte[] private List<byte[]> mBuffersBySize = new ArrayList<byte[]>(64);//以字节大小的顺序保存Byte[] /** The total size of the buffers in the pool */ private int mCurrentSize = 0; //记录Bytep[]的数量... private final int mSizeLimit; //大小限制.. /** Compares buffers by size */ //对所有的Byte[]进行大小比较... protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() { @Override public int compare(byte[] lhs, byte[] rhs) { return lhs.length - rhs.length; } }; /** * @param sizeLimit the maximum size of the pool, in bytes */ //设置允许的最大Byte[]... public ByteArrayPool(int sizeLimit) { mSizeLimit = sizeLimit; } //获取缓冲的过程,如果缓冲池当中存在一个合适的缓冲区,那么就return这个缓冲区,如果没有合适的,那么就需要新建立一个缓冲区... public synchronized byte[] getBuf(int len) { for (int i = 0; i < mBuffersBySize.size(); i++) { //遍历过程... byte[] buf = mBuffersBySize.get(i); //获取每一个缓冲.. if (buf.length >= len) { //如果满足规格... mCurrentSize -= buf.length; //表示缓冲被占用,那么ArrayList就将其移除并返回... mBuffersBySize.remove(i); mBuffersByLastUse.remove(buf); return buf; //返回合适的buf... } } return new byte[len]; //如果没有,新建立一个缓冲... } //对使用过的Byte[]进行保存...根据大小插入到合适的ArrayList中... public synchronized void returnBuf(byte[] buf) { if (buf == null || buf.length > mSizeLimit) { return; } mBuffersByLastUse.add(buf); //加入这次使用过的缓冲... //比较完大小进行插入.. int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR); if (pos < 0) { pos = -pos - 1; } mBuffersBySize.add(pos, buf);//插入过程... mCurrentSize += buf.length; //记录当前大小... trim(); //trim()函数调用... } //函数的目的是判断,如果使用过的缓冲大小超出了预先设定的大小,那么按照先进先出的原则,缓冲会移除每次都移除第一个Byte,当Byte[]满足了指定大小,就不用再删除字节了... private synchronized void trim() { while (mCurrentSize > mSizeLimit) { byte[] buf = mBuffersByLastUse.remove(0); mBuffersBySize.remove(buf); mCurrentSize -= buf.length; } } }
3.HttpStack.java
前面涉及到的网络请求都是通过Http协议来完成请求的,在new Request的时候需要建立一个栈区来保存所有请求,那么这个栈区则是通过HttpStack来实现的,而HttpStack只是一个抽象类的接口...
package com.android.volley.toolbox; import com.android.volley.AuthFailureError; import com.android.volley.Request; import org.apache.http.HttpResponse; import java.io.IOException; import java.util.Map; public interface HttpStack { public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError; }
我们知道Http请求一种是通过HttpURLConnection通过url来建立一个连接的,而另一种方式则是通过HttpClient,基于Apache的一种请求方式,而Android从2.3版本以后就推荐使用第一种连接方式来创建一个连接...因此我们就说一下HttpUrlConnectionStack,在Volley中实现类以HurlStack,创建一个url连接的栈区,来保存所有通过url来建立的网络连接...
4.HurlStack.java
package com.android.volley.toolbox; import com.android.volley.AuthFailureError; import com.android.volley.Request; import com.android.volley.Request.Method; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; public class HurlStack implements HttpStack { private static final String HEADER_CONTENT_TYPE = "Content-Type"; //Header内容的类型... public interface UrlRewriter { public String rewriteUrl(String originalUrl); //重写一个url... } private final UrlRewriter mUrlRewriter; //url重写对象... private final SSLSocketFactory mSslSocketFactory; //用于Https请求... public HurlStack() { this(null); } public HurlStack(UrlRewriter urlRewriter) { this(urlRewriter, null); } //创建一个栈区,保存url连接... public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) { mUrlRewriter = urlRewriter; mSslSocketFactory = sslSocketFactory; } @Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) //执行请求的过程... throws IOException, AuthFailureError { //获取url... String url = request.getUrl(); HashMap<String, String> map = new HashMap<String, String>(); //为请求加上头部,以及我们传递的额外参数.. map.putAll(request.getHeaders()); map.putAll(additionalHeaders); //对url进行重写,重写的好处使url更加的保密,安全... if (mUrlRewriter != null) { String rewritten = mUrlRewriter.rewriteUrl(url); if (rewritten == null) { throw new IOException("URL blocked by rewriter: " + url); } url = rewritten; } //创建url对象... URL parsedUrl = new URL(url); HttpURLConnection connection = openConnection(parsedUrl, request); //建立连接... for (String headerName : map.keySet()) { connection.addRequestProperty(headerName, map.get(headerName)); //设置请求的相关属性... } setConnectionParametersForRequest(connection, request);//根据请求的方式去执行相关的方法... // Initialize HttpResponse with data from the HttpURLConnection. ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); //获取协议版本... int responseCode = connection.getResponseCode(); //响应码获取... if (responseCode == -1) { //如果为-1,抛出异常... throw new IOException("Could not retrieve response code from HttpUrlConnection."); } StatusLine responseStatus = new BasicStatusLine(protocolVersion, connection.getResponseCode(), connection.getResponseMessage()); //获取响应信息... BasicHttpResponse response = new BasicHttpResponse(responseStatus); //封装响应状态... response.setEntity(entityFromConnection(connection)); //获取响应中的实体... //对Header进行遍历...为响应添加相关的Header... for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { if (header.getKey() != null) { Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); response.addHeader(h); } } return response;//返回响应... } private static HttpEntity entityFromConnection(HttpURLConnection connection) { //获取实体的方法.. BasicHttpEntity entity = new BasicHttpEntity(); //实体封装对象的创建... InputStream inputStream; try { inputStream = connection.getInputStream(); //获取连接的I/O流.. } catch (IOException ioe) { inputStream = connection.getErrorStream(); } entity.setContent(inputStream); //实体的内容.. entity.setContentLength(connection.getContentLength());//实体的长度.. entity.setContentEncoding(connection.getContentEncoding()); //实体的编码... entity.setContentType(connection.getContentType());//实体内容的类型... return entity; } //创建连接... protected HttpURLConnection createConnection(URL url) throws IOException { return (HttpURLConnection) url.openConnection(); } //打开连接方法,,, private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException { HttpURLConnection connection = createConnection(url); int timeoutMs = request.getTimeoutMs(); //设置请求的超时时间.. connection.setConnectTimeout(timeoutMs); //连接超时时间的设置.. connection.setReadTimeout(timeoutMs); //读取时间超时... connection.setUseCaches(false); //是否设置缓存.. connection.setDoInput(true); //是否有相关的输入.. // use caller-provided custom SslSocketFactory, if any, for HTTPS if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); //创建一个Https连接... } return connection; } //为请求设置连接方式... @SuppressWarnings("deprecation") /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Method.DEPRECATED_GET_OR_POST: byte[] postBody = request.getPostBody(); if (postBody != null) { // Prepare output. There is no need to set Content-Length explicitly, // since this is handled by HttpURLConnection using the size of the prepared // output stream. connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(postBody); out.close(); } break; case Method.GET: // Not necessary to set the request method because connection defaults to GET but // being explicit here. connection.setRequestMethod("GET"); break; case Method.DELETE: connection.setRequestMethod("DELETE"); break; case Method.POST: connection.setRequestMethod("POST"); addBodyIfExists(connection, request); //调用最后一个函数... break; case Method.PUT: connection.setRequestMethod("PUT"); addBodyIfExists(connection, request); break; default: throw new IllegalStateException("Unknown method type."); } } //如果一个请求存在实体数据,那么需要为其加上请求数据... private static void addBodyIfExists(HttpURLConnection connection, Request<?> request) throws IOException, AuthFailureError { byte[] body = request.getBody(); if (body != null) { connection.setDoOutput(true); connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(body); out.close(); } } }
前面说到一个请求数据是否需要缓存是通过Header中封装的数据来判断一次请求后的数据是否需要进行缓存...那么是否需要缓存,以及新鲜度的验证等等头被封装在了Header中,那么我们是如何知道Header中的数据呢?前面已经可以获取到响应数据报中的实体部分(Body),那么Header还没有被获取..因此需要说一下HttpHeaderParser类...用来解析Header中的数据...
5.HttpHeaderParser.java
package com.android.volley.toolbox; import com.android.volley.Cache; import com.android.volley.NetworkResponse; import org.apache.http.impl.cookie.DateParseException; import org.apache.http.impl.cookie.DateUtils; import org.apache.http.protocol.HTTP; import java.util.Map; public class HttpHeaderParser { //解析缓存Header... public static Cache.Entry parseCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Map<String, String> headers = response.headers; //一些基本数据的定义... long serverDate = 0; long serverExpires = 0; long softExpire = 0; long maxAge = 0; boolean hasCacheControl = false; String serverEtag = null; String headerValue; //获取请求——服务的整个时间,将RFC1123的格式解析成epoch方式... headerValue = headers.get("Date"); if (headerValue != null) { serverDate = parseDateAsEpoch(headerValue); } headerValue = headers.get("Cache-Control"); if (headerValue != null) { hasCacheControl = true; String[] tokens = headerValue.split(","); for (int i = 0; i < tokens.length; i++) { String token = tokens[i].trim(); //判断是否有缓存... if (token.equals("no-cache") || token.equals("no-store")) { return null; } else if (token.startsWith("max-age=")) { try { maxAge = Long.parseLong(token.substring(8)); //设置缓存的有效时间... } catch (Exception e) { } } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { maxAge = 0; //如果不允许缓存,那么有效期为0... } } } headerValue = headers.get("Expires"); if (headerValue != null) { serverExpires = parseDateAsEpoch(headerValue); //设置缓存新鲜度时间... } serverEtag = headers.get("ETag"); //设置缓存的过期时间... if (hasCacheControl) { softExpire = now + maxAge * 1000; } else if (serverDate > 0 && serverExpires >= serverDate) { // Default semantic for Expire header in HTTP specification is softExpire. softExpire = now + (serverExpires - serverDate); } //将Header数据保存在Entry当中.. Cache.Entry entry = new Cache.Entry(); entry.data = response.data; entry.etag = serverEtag; entry.softTtl = softExpire; entry.ttl = entry.softTtl; entry.serverDate = serverDate; entry.responseHeaders = headers; return entry; } //将RFC1123的时间格式转换成epoch格式... public static long parseDateAsEpoch(String dateStr) { try { // Parse date in RFC1123 format if this header contains one return DateUtils.parseDate(dateStr).getTime(); } catch (DateParseException e) { // Date in invalid format, fallback to 0 return 0; } } //解析字符集... public static String parseCharset(Map<String, String> headers) { String contentType = headers.get(HTTP.CONTENT_TYPE); if (contentType != null) { String[] params = contentType.split(";"); for (int i = 1; i < params.length; i++) { String[] pair = params[i].trim().split("="); if (pair.length == 2) { if (pair[0].equals("charset")) { return pair[1]; } } } } return HTTP.DEFAULT_CONTENT_CHARSET; } }
Volley中还有一些其他类,不过基本都是一些简单的类,就不粘贴代码进行解析了,只是提一嘴就一笔带过就行了...
Volley.java:工具类,用于实现一个请求队列...
Authenticator.java:一个抽象接口,用于身份验证...用于基本认证和摘要认证...不过使用的不是非常的广泛..
AndroidAuthenticator.java:基于Android AccountManager的认证交互类...实现了验证接口的抽象方法...
VolleyLog.java:在Volley中用于显示Log信息...
VolleyError.java:Volley内部所有异常类的父类...对异常的处理方式的一个超类...继承了Expection...
TimeoutError.java
ServerError.java
NetWorkError.java
ParseError.java
NoConnection.java
AuthFailureError.java
都是异常发生如何处理的类,其中包括超时,服务端错误,网络错误,内容解析错误,无法连接错误,验证失败等异常处理...在这里就不一一介绍了..都比较简单...
Android 学习笔记之Volley(六)实现获取服务器的字符串响应...
学习内容:
1.使用StringRequest实现获取服务器的字符串响应...
前几篇一直都在对服务——响应过程的源码进行分析,解析了整个过程,那么Volley中到底实现了哪些请求才是我们在开发中需要进行使用的...Volley实现的东西其实并不是很多,它的主要功能是实现异步进行网络请求和图片加载,其实就是异步加载解析Json数据,异步获取服务器的字符串数据,异步实现网络图片的动态加载,还有一个请求就是清空缓存的请求,不过使用的地方不是很多,主要还是前面三个方面,因此Volley相对于AndBase来说,其实还算是一个轻量级的框架,而AndBase涉及到的东西就更加的广泛,全面,但是网络请求这一部分使用Volley基本算是够用了...
1.StringRequest.java
package com.android.volley.toolbox; import com.android.volley.NetworkResponse; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import java.io.UnsupportedEncodingException; public class StringRequest extends Request<String> { private final Listener<String> mListener; //请求成功的监听... //根据指定的请求方式和url创建一个StringRquest对象... public StringRequest(int method, String url, Listener<String> listener, ErrorListener errorListener) { super(method, url, errorListener); //设置请求方式,url,以及错误监听.. mListener = listener; //设置成功监听... } //根据指定的url来创建一个StringRequest对象,请求方式默认为GET.. public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } //这里涉及到发送响应的过程了...表示整个请求的响应已经返回... @Override protected void deliverResponse(String response) { mListener.onResponse(response); } //对响应的解析过程... @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));//返回请求成功... } }
上面只是StringRequest的源码实现,非常的简单...那么我们来具体的看看怎么用...
一般使用在简单的响应方式,返回一些基本的数据信息,比如说用户登录中,在发送Post请求发送用户的账号信息和密码的时候,需要服务器调取数据库进行相关查找...在完成这个响应之后需要为服务端返回响应信息,一般就是以字符串的形式进行发送的...
package com.example.oop; import com.android.volley.RequestQueue; import com.android.volley.toolbox.ImageLoader; import com.android.volley.toolbox.ImageLoader.ImageCache; import com.android.volley.toolbox.NetworkImageView; import com.android.volley.toolbox.Volley; import android.os.Bundle; import android.app.Activity; import android.graphics.Bitmap; import android.support.v4.util.LruCache; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; public class MainActivity extends Activity implements OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } public void init(){ RequestQueue queue=Volley.newRequestQueue(MainActivity.this); //首先创建一个请求队列... //然后需要向请求队列中添加相关请求... queue.add(new StringRequest("http://www.baidu.com/",new Listener <StringRequest>(){ //请求成功,接收请求方法的重写... @Override public void onResponse(String response){ System.out.println(response.toString()); } },new ErrorListener(){ //请求失败,对错误的获取... @Override public void onErrorResponse(VolleyError error){ System.out.println(error.toString()); } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
这里我们想百度页面发送了相关的请求,那么毫无疑问,请求成功是必然的,那么返回的东西就是百度页面的原生数据,其实就是Html页面代码...那么我们无法去解析这个页面,但是浏览器却是可以的,我们可以通过浏览器去加载这个页面...这只是一个简单的小例子而已,目的是我们需要清楚,服务器返回给我们的是什么数据...
第二个例子:
这是一个中间用于过程处理的JSP方法...用于处理账户和密码,只是一个简单的方式,我们当然也可以通过它去连接数据库,完善化这个函数...这里只是一个简单的小例子...
<% String name=request.getParameter("name"); String password=request.getParameter("password"); if("darker".equals(name)&& "49681888".equals(password)){ out.println("Receive name is:"+name); out.println("Receive password is:"+password);%> Your Message are right! <%}else{ out.println("Receive name is:"+name); out.println("Receive password is:"+password);%> Your Message are wrong! <%}%>
那么Activity中需要通过Post请求发送请求参数,才能够通过这个函数来进行下一步的判断...由于Post请求中没有传递参数的方法...但是我们可以通过重写getParam()方法...来指定相关参数,服务端会自动调用getParam()中的参数....
package com.example.oop; //有一部分包没有引用,在编写的时候会自动引用的... import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; public class MainActivity extends Activity { TextView tv; String url="192.168.19.172:8080/JSP/post.jsp" @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv=(TextView)findViewById(R.id.tv_1); init(); } public void init(){ RequestQueue queue=Volley.newRequestQueue(MainActivity.this); //首先创建一个请求队列... queue.add(new StringRequest(Method.POST, url, new Listener<String>() { @Override public void onResponse(String response) { // TODO Auto-generated method stub System.out.println(response.toString()); tv.setText(response.toString()); //对获取的数据进行显示... } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // TODO Auto-generated method stub System.out.println(error.toString()); } }){ //在这个方法里完成参数的相关传递.... @Override protected Map<String, String>getParams() throws AuthFailureError{ Map<String, String>map=new HashMap<String, String>(); map.put("name", "darker"); map.put("password", "49681888"); return map; } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
使用Post请求来完成验证,毫无疑问,这里由于我们传递的参数时正确的,因此客户端会获取到Receive name is:darker,Receive password is:49681888,Your Message are right!这段信息...
StringRequest请求非常的简单,涉及的东西也并不是很多,适合于发送网络请求来获取相应的字符串数据,呈递给客户端...