首先看怎么使用
private void testStringRequest() { String url = "http://api.k780.com/?app=weather.history&weaid=1&date=2015-07-20&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json"; RequestQueue queue = Volley.newRequestQueue(getApplicationContext()); StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener() { @Override public void onResponse(String response) { Log.i("response:", response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("error", error.getMessage()); } } ); queue.add(stringRequest);}
首先我们第一步是根据上下文对象创建一个RequestQueue
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
对应的源码是:
public class Volley { /** Default on-disk cache directory. */ private static final String DEFAULT_CACHE_DIR = "volley"; /** * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. * * @param context A {@link Context} to use for creating the cache dir. * @param stack An {@link HttpStack} to use for the network, or null for default. * @return A started {@link RequestQueue} instance. */ 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) { 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 queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue; } /** * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. * * @param context A {@link Context} to use for creating the cache dir. * @return A started {@link RequestQueue} instance. */ public static RequestQueue newRequestQueue(Context context) { return newRequestQueue(context, null); }}
Volley类的源码非常简单,只有80行代码
我们调用的是
public static RequestQueue newRequestQueue(Context context)
然后它又调用了其重载方法
public static RequestQueue newRequestQueue(Context context, HttpStack stack)
所以只需要看这个重载方法就行了
首先是创建一个默认的缓存目录,然后根据上下文对象拿到包名,然后根据包名拿到包管理器,通过包管理器拿到对应的versionCode,最后拼接成userAgent字符串备用,因为下面在使用AndroidHttpClient.newInstance(userAgent)作为参数创建HttpClientStack的时候要用到,其实这里我认为代码还能够优化一下,这个获取userAgent的过程可以往后推移一下,在判断完成经行。因为下面会根据Build.VERSION.SDK_INT >= 9来判断是使用HttpURLConnection还是使用HttpClient,最终都是为了创建一个网络进行数据请求,同时传入了一个缓存对象。
Network network = new BasicNetwork(stack);RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);queue.start();
接下来我们看RequestQueue的源码,只看重要部分
public class RequestQueue { ... ...省略 /** Number of network request dispatcher threads to start. */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; /** Cache interface for retrieving and storing responses. */ 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; private ListmFinishedListeners = new ArrayList (); /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create * @param delivery A ResponseDelivery interface for posting responses and errors */ public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create */ public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests */ public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } /** * Starts the dispatchers in this queue. */ public void start() { stop(); // Make sure any currently running dispatchers are stopped. // Create the cache dispatcher and start it. mCacheDispatcher.start(); mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); // 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(); } } /** * Stops the cache and network dispatchers. */ public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (int i = 0; i < mDispatchers.length; i++) { if (mDispatchers[i] != null) { mDispatchers[i].quit(); } } } ... ...省略 /** * Adds a Request to the dispatch queue. * @param request The request to service * @return The passed-in 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); 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; } // 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)) { // There is already a request in flight. Queue up. Queue > stagedRequests = mWaitingRequests.get(cacheKey); if (stagedRequests == null) { stagedRequests = new LinkedList >(); } 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); mCacheQueue.add(request); } return request; } } ... ...省略}
add方法
add方法中其实只是根据根据判断条件往NetworkDispatcher 或者 CacheDispatcher中添加请求(StringRequest,JsonRequest,ImageRequest),并不负责请求,网络的请求实际上是NetworkDispatcher和CacheDispatcher他们自己完成。
start方法
从上面源码我们可以看到,start方法做了什么,CacheDispatcher和NetworkDispatcher分别是两个调度线程,他们都是Thread的子类,CacheDispatcher是缓存调度线程,NetworkDispatcher是网络调度线程,在RequestQueue的
构造函数中能够清楚的看到NetworkDispatcher作为一个的线程组被初始化长度是4,也就是说网络调度线程在Volley中有4条。start方法中同时开启了这两种调度线程,也就是说后Volley在后台同时开起来了5条线程等待处理请求。
RequestQueue中的start方法是被调用是在Volley通过newRequestQueue创建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) { 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 queue = new RequestQueue(new DiskBasedCache(cacheDir), network); queue.start(); return queue;}
Thread,Thread的start方法和run方法有什么区别?
Thread 实现了Runnable接口,Runnable接口中的代码很简单,就只有一个抽象方法run。
因此创建线程有两张方式:
1)继承Thread类
2)实现Runnable接口,重写run方法,然后构造一个Thread对象,将Runnable的这个子类通过Thread的构造函数传递给Thread,Thread在构造函数中会调用其init方法,把这个子类继续往下传递,赋值给Thread的自变量target,然后Thread的run函数被调用的时候,实际上就是调用target.run()
start函数做了什么呢?
根据方法描述翻译过来是:
[调用start方法会]导致此线程开始执行; Java虚拟机调用此线程的 run 方法。
结果是两个线程同时运行:当前线程(从调用start方法返回)和另一个线程(执行其run方法)。(也可以说是主线程和子线程)
多次启动同一个线程是不允许的,特别是,线程完成它执行的任务后,可能就不会被重新启动了。
从方法描述上我们知道了start方法只是创建一个线程并开始执行,但是run方法是java虚拟机调用的,所以我们在源码中不会看见run方法被调用的痕迹。
因此我们这里可以解答一个问题:
start和run有什么区别?
start被调用的时候java虚拟机会创建一个线程,然后这个线程处于等待状态,一旦cpu给该线程分配时间了,这个时候该线程才会开始执行然后run方法会被虚拟机调用。两者的区别便是,start会创建一个线程,run不会创建线程,run方法只是在等待被调用,然后处理赋予该线程需要处理的事情。
那又有一个问题,可能有人会问run方法是怎么被java虚拟机调用的呢?在start方法中nativeCreate(this, stackSize, daemon);将this传给了native方法,由于更底层的代码这里看不到,但是我们可以推断,既然this作为指引被传过去,那么run方法自然就能够被拿到。
new Thread(new Runnable() { @Override public void run() { }}).start();
############Thread部分源码#############
/** * Allocates a new {@code Thread} object. This constructor has the same * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} * {@code (null, target, gname)}, where {@code gname} is a newly generated * name. Automatically generated names are of the form * {@code "Thread-"+}n, where n is an integer. * * @param target * the object whose {@code run} method is invoked when this thread * is started. If {@code null}, this classes {@code run} method does * nothing. */public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0);}
/** * Initializes a Thread. * * @param g the Thread group * @param target the object whose run() method gets called * @param name the name of the new Thread * @param stackSize the desired stack size for the new thread, or * zero to indicate that this parameter is to be ignored. */private void init(ThreadGroup g, Runnable target, String name, long stackSize) { Thread parent = currentThread(); if (g == null) { g = parent.getThreadGroup(); } g.addUnstarted(); this.group = g; this.target = target; this.priority = parent.getPriority(); this.daemon = parent.isDaemon(); setName(name); init2(parent); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; tid = nextThreadID();}
/** * If this thread was constructed using a separate *Runnable
run object, then that *Runnable
object'srun
method is called; * otherwise, this method does nothing and returns. ** Subclasses of
Thread
should override this method. * * @see #start() * @see #stop() * @see #Thread(ThreadGroup, Runnable, String) */@Overridepublic void run() { if (target != null) { target.run(); }}
/** * Causes this thread to begin execution; the Java Virtual Machine * calls therun
method of this thread. ** The result is that two threads are running concurrently: the * current thread (which returns from the call to the *
start
method) and the other thread (which executes its *run
method). ** It is never legal to start a thread more than once. * In particular, a thread may not be restarted once it has completed * execution. * * @exception IllegalThreadStateException if the thread was already * started. * @see #run() * @see #stop() */public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ // Android-changed: throw if 'started' is true if (threadStatus != 0 || started) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); started = false; try { nativeCreate(this, stackSize, daemon); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } }}
CacheDispatcher
经过上面的分析我们可以清楚的知道调用链,Volley通过newRequestQueue创建RequestQueue,并调用了其start方法,然后宰割方法中分别调用了CacheDispatcher和NetworkDispatcher的start方法,开启了5条线程,当cpu给线程分配时间后,开始执行,虚拟机将会调用个线程的run方法,所以这里我们将关注点放在CacheDispatcher的run方法中,NetworkDispatcher也是一样。下面我们看看CacheDispatcher的run方法到底做了什么?
源码:
@Overridepublic void run() { if (DEBUG) VolleyLog.v("start new dispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//设置线程优先级,后台线程 // Make a blocking call to initialize the cache.(进行阻塞调用以初始化缓存) mCache.initialize(); while (true) { try { // Get a request from the cache triage queue, blocking until // at least one is available. final Request request = mCacheQueue.take(); request.addMarker("cache-queue-take"); // If the request has been canceled, don't bother dispatching it. 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); mNetworkQueue.put(request); continue; } // We have a cache hit; parse its data for delivery back to the request. request.addMarker("cache-hit"); Response response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed"); if (!entry.refreshNeeded()) { // Completely unexpired cache hit. Just deliver the response. mDelivery.postResponse(request, response); } else { // Soft-expired cache hit. We can deliver the cached response, // but we need to also send the request to the network for // refreshing. request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); // Mark the response as intermediate. response.intermediate = true; // Post the intermediate response back to the user and have // the delivery then forward the request along to the network. 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) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } }}
源码分析:
我们可以看到首先会设置线程的优先级别为后台线程,然后进行阻塞调用以初始化缓存,最后通过while开启一个无限循环处理事情,我们主要也是看这个while中主要做了什么事情?
1)首先从mCacheQueue列队中获取一个request,注意如果列队中没有这里会阻塞。
2)判断这个request是否被取消了,如果被cancle了则进行下一次循环,去获取和另外的request。
3)尝试从缓存中检索是否存在request所请求的项目,从代码中我们可以看到mCache.get(request.getCacheKey()),然后看看getCacheKey返回的是什么,就是我们请求的url地址,所以说Volley缓存资源的时候使用的就是请求地址。
/** * Returns the cache key for this request. By default, this is the URL. */public String getCacheKey() { return getUrl();}
4)在获取到资源后,紧接着会判断其是否已经过期了,如果过期了就将这个请求转给NetworkDispatcher从网络上重新获取新资源。
5)当我们获取的资源没有过期,说明我们缓存的资源被request的uil匹配到了,注释中说的是命中,也就是这个意思。
6)将命中的资源返回,这里是做了判断的,如果原始数据源不需要刷新,就会把命中的数据直接返回给请求,如果原始数据源需要被刷新,那么这个请求将会重新交给网络分发线程处理,重新去网络上请求数据。
7)过期时间判断,通过的是时间比较,那这个sfotTtl在哪里赋值的呢?有个com.android.volley.toolbox.HttpHeaderParser类,在他的parseCacheHeaders方法中,看下面源码,可以得出结论这个缓存时间是有服务器返回的资源头部说明决定的,可以没有缓存,有缓存的就通过max-age作为key取出缓存时间来。
public boolean refreshNeeded() { return this.softTtl < System.currentTimeMillis();}
public static Cache.Entry parseCacheHeaders(NetworkResponse response) { long now = System.currentTimeMillis(); Mapheaders = 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); } 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;}
NetworkDispatcher
NetworkDispatcher的run方法是什么时候开始运行的,上面已经讲了,下面直接看源码分析,这里同样会通过while开启一个无限循环,不断重请求列队中获取request
1)从列队中获取request
2)入股欧能获取到,检查当前request是否被cancle了
3)当SDK > 14 会根据当前request给线程标记tag,源码中的解释是:设置活动标记,以便在计算来自当前线程的{
Socket}流量时使用。每个线程只支持一个活动标记。4)网络通过request请求资源
5)看是否当前请求被重复了,如果重复了就不请求了
6)解析服务器返回的response
7)检查是否允许缓存,如果允许则缓存本地
8)将根据当前request获取到资源返回
源码
@Overridepublic void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); while (true) { long startTimeMs = SystemClock.elapsedRealtime(); Request request; 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; } addTrafficStatsTag(request); // 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()) { 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) { 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); } }}
到此Volley源码的大流程就分析完了