下面是一个实际的请求报文:
①是请求方法,GET和POST是最常见的HTTP方法,除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。不过,当前的大多数浏览器只支持GET和POST。
咱们的Volley差不多都覆盖了。。
②为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL
③是协议名称及版本号。
④是HTTP的报文头(请求头),报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
⑤是报文体(请求体),它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。
对照上面的请求报文,我们把它进一步分解,你可以看到一幅更详细的结构图:
|
来看张图吧,简单说下,有个印象
这个图很简单描述了Volley的请求流程。就是那么简单~ 理解达到一定高度就那么简单。。。
我们在使用Volley的时候,第一步就是创建一个RequestQueue。
然后就是创建Request 了。然后把Request往RequestQueue里面add~~
Volley里面提供了3中Request,StringRequest,JsonRequest,ImageRequest。
当然我们也可以自定义Request了,这里得提一下(
Volley 中大多是基于接口的设计,
扩展性强,
可配置性强。)
接着往下走~
我们把Request都add到RequestQueue后。。RequestQueue都干了啥?
我们在创建一个RequestQueue后,默认会开启一个缓存线程(CacheDispatcher)和四个网络线程(NetWorkDispatcher)
CacheDispatcher负责从缓存中取数据。NetWorkDispatcher负责请求网络请求数据。
我们知道一般网络请求最好先从缓存里面取,取不到再去请求网络咯。。Volley也是那么干的咯。。
Cache就是缓存啦,Cache从什么地方取缓存数据? 就是下面的Memory,File System。。如果Cache里面没有我们请求的数据,那就只能走网络了。。
网络请求是通过HttpClientStack或HurlStack来请求Server
这个图之后,再来张图梳理下流程。。
流程我们都清楚了,然下来啃下源码吧~
我们看下,Volley所涉及到的类的关系图:
红色圈内的部分,组成了 Volley 框架的核心,围绕 RequestQueue 类,将各个功能点以组合的方式结合在了一起。各个功能点也都是以接口或者抽象类的形式提供。
红色圈外面的部分,在 Volley 源码中放在了 toolbox 包中,作为 Volley 为各个功能点提供的默认的具体实现。
通过类图我们看出, Volley 有着非常好的拓展性。通过各个功能点的接口,我们可以给出自定义的,更符合我们需求的具体实现。
|
Volley 的调用比较简单,通过 newRequestQueue(…) 函数新建并启动一个请求队列
RequestQueue 后,只需要往这个
RequestQueue 不断 add Request 即可。
Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列 Request:表示一个请求的抽象类。 RequestQueue:表示请求队列,里面包含一个 CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给 NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给 ResponseDelivery:返回结果分发接口,目前只有基于 HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的 Network:调用
Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的
DiskBasedCache 。
NetworkDispatcher 得到请求结果后判断是否需要存储在 Cache,
CacheDispatcher 会从 Cache 中取缓存结果。
|
<!DOCTYPE
html
> <html> <body> <?php header( 'Cache-Control: max-age=120, public, must-revalidate' ) ; //max-age 修改为 2 分钟 , 使用 must-revalidate //header('Cache-Control: max-age=120, public, stale-while-revalidate=60');// 使用 stale-while-revalidate //header('Expires: '.gmdate('D, d M Y H:i:s', time() + 604800).' GMT'); // 过期时间修改为 7 天 header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' , filemtime( __FILE__ )) . ' GMT' ) ; // 新增此行,获取文件最后修改时间 $eTag = md5_file( __FILE__ ) ; $last_modified_time = filemtime( __FILE__ ) ; header( "Etag: $eTag " ) ; if (( isset ( $_SERVER [ 'HTTP_IF_NONE_MATCH' ])) && (stripslashes( $_SERVER [ 'HTTP_IF_NONE_MATCH' ]) == $eTag ) ||( isset ( $_SERVER [ 'HTTP_IF_MODIFIED_SINCE' ])) && (strtotime( $_SERVER [ 'HTTP_IF_MODIFIED_SINCE' ]) == $last_modified_time )) { header( "HTTP/1.1 304 Not Modified" , TRUE , 304 ) ; exit () ; } echo " 我的第一段 PHP 脚本! " ; ?> </body> </html> |
private void
requestServer2() {
String url = "http://192.168.0.30/volley_server/index.php" ; /** * 这里使用局部变量 request * @see NetworkDispatcher#run() 的 119 * 如果服务器返回的是 304 ,第二个条件就为 false 了。。 * 304 就是服务器不返回数据给我们,我们还是用缓存中的数据更新啦。 */ CustomStringRequest request = new CustomStringRequest(url , new Response.Listener<String>() { @Override public void onResponse(String response) { mResponseEdt.setText( new String(response.getBytes())) ; } } , new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { mResponseEdt.setText( "request error") ; } }) ; VolleyPlayApp. getApplication().getReqQueue().add(request) ;
}
|
CacheDispatcher 缓存线程优先
然后是网络请求线程NetworkDispatcher
|
void
finish(
final String tag) {
if ( mRequestQueue != null) { mRequestQueue.finish( this) ; onFinish() ; } 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()) ; } } |
<
T>
void
finish(Request<
T> request) {
// Remove from the set of requests currently being processed. synchronized ( mCurrentRequests) { mCurrentRequests.remove(request) ; } synchronized ( mFinishedListeners) { for (RequestFinishedListener< T> listener : mFinishedListeners) { listener.onRequestFinished(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) ; } } } } |
import com.android.volley.Cache;
import com.android.volley.NetworkResponse;
import com.android.volley.toolbox.HttpHeaderParser;
import org.apache.http.impl.cookie.DateUtils;
import java.util.Date;
import java.util.Map;
/**
* Created by congwiny on 2015/12/3.
*/
public class CustomHttpHeaderParser extends HttpHeaderParser {
public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
long now = System.currentTimeMillis();
Map<String, String> headers = response.headers;
long serverDate = 0;
long lastModified = 0;
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
long maxAge = 0;
long staleWhileRevalidate = 0;
boolean hasCacheControl = false;
boolean mustRevalidate = false;
String serverEtag = null;
String headerValue;
headerValue = headers.get("Date");
if (headerValue != null) {
serverDate = parseDateAsEpoch(headerValue);
}
//造假头信息
headers.put("Cache-Control", "max-age=86400");//缓存24小时
long time = serverDate + 1000 * 60 * 60 * 24;
Date expiresTime = new Date(time);
headers.put("Expires", DateUtils.formatDate(expiresTime, DateUtils.PATTERN_RFC1123));
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.startsWith("stale-while-revalidate=")) {
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e) {
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
mustRevalidate = true;
}
}
}
headerValue = headers.get("Expires");
if (headerValue != null) {
serverExpires = parseDateAsEpoch(headerValue);
}
headerValue = headers.get("Last-Modified");
if (headerValue != null) {
lastModified = parseDateAsEpoch(headerValue);
}
serverEtag = headers.get("ETag");
// Cache-Control takes precedence over an Expires header, even if both exist and Expires
// is more restrictive.
if (hasCacheControl) {
softExpire = now + maxAge * 1000;
finalExpire = mustRevalidate
? softExpire
: softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate) {
// Default semantic for Expire header in HTTP specification is softExpire.
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.etag = serverEtag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
}