Android开发学习之路-Volley源码解析

时间:2022-09-24 23:50:21

从简单的StringRequest入手看看Volley的工作机制。

先简单说下Volley的用法:

① 获取一个RequestQueue

mRequestQueue = Volley.newRequestQueue(this);

② 构造一个StringRequest对象

mStringRequest = new StringRequest(url, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
mTextView.setText(response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.d(TAG, "onErrorResponse: " + error.getMessage());
}
});

③ 将StringRequest对象add进RequestQueue

mRequestQueue.add(mStringRequest);

下面通过源码跟踪一下Volley处理请求的过程:

用过Volley都知道,请求一般是继承自Request这个抽象类,那么StringRequest自然也是。在构造方法中需要几个参数,具体的构造方法如下

public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}

可以看到我们需要给它指定请求的方法、url、成功返回的回调类和错误的回调类。

接着我们在需要请求的时候,把这个对象传递给RequestQueue的add方法。这个add方法是做什么的我们其实都能猜到,就是把这个请求放到队列中。

 public <T> Request<T> add(Request<T> 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<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);
mCacheQueue.add(request);
}
return request;
}
}

代码可能有点长,但是核心的地方也比较简单,就是进行线程同步之后将请求放入请求的集合mNetworkQueue(在不需要缓存的情况下)。如果需要缓存请求,则同步等待序列,判断请求是否已经被发出过,根据情况返回请求。

因为这里会根据是否需要缓存进行区别处理,下面按照不需要缓存来讲解源码。而StringRequest是会进行缓存的。

接着我们看看RequestQueue是怎么工作的。这里其实我们也是可以知道,既然命名为Queue,肯定是会通过一个线程来不停的遍历队列中的等待者然后进行处理,跟Handler中的MessageQueue是很类似的。

我们先看RequestQueue的构造,我们一般通过下面这个方法来获得一个RequestQueue

mRequestQueue = Volley.newRequestQueue(this);

而实际上这个构造的方法内容是下面这样的

 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;
}

从注释可以看到,在api等级大于9的时候,使用HttpUrlConnection来进行主要的网络请求工作,到这里已经很明显了,Volley底层是使用HttpUrlConnection进行的。我们看到这里是使用了一个HttpClientStack来包装根据userAgent得到的HttpClient(这个类在最新的源码中已经被移除了),而HttpClient实际上就是使用HttpUrlConnection来实现的。最后被包装为一个BasicNetwork对象。

接着根据得到的BasicNetwork对象和一个DiskBasedCache对象(磁盘缓存)来构造一个RequestQueue,并且调用了它的start方法来启动这个线程。

再看看RequestQueue的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(); // 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();
}
}

这里我们先不顾这个mCacheDispatcher,直接看到下面的for循环,这个for循环遍历了mDispatchers,这个mDispatcher其实相当于一个线程池,这个线程池的大小默认是4。然后分别让这里面的线程运行起来(调用了它们的start方法)。这里为什么要有多个线程来处理呢?原因很简单,因为我们每一个请求都不一定会马上处理完毕,多个线程进行同时处理的话效率会提高。

我们进入NetworkDispatcher看看它的run方法:

 @Override
public 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);
}
}
}

第三行设置了这些线程的优先级,这个优先级比较低,目的是为了尽量减少对UI线程的影响保证流畅度。

接着第9行,调用mQueue的take方法取出队列头的一个请求进行处理,这个mQueue是什么?其实它就是我们在上面add方法中添加进去的一个请求。

我们不看这些设置状态标记的地方,直接看到第31行,如果请求没有被取消,也就是正常的情况下,我们会调用mNetwork的performRequest方法进行请求的处理。不知道你还记的这个mNetwork不,它其实就是我们上面提到的那个由HttpUrlConnection层层包装的网络请求对象。

如果请求得到了结果,我们会看到54行调用了mDelivery的postResponose方法来回传我们的请求结果。

这里还有两个重要的地方需要再了解一下,一个是究竟postResponse是怎么传回我们的请求结果的,另一个就是performRequest是怎么去进行网络请求的。

先看第一个,结果的回传。我们先了解下这个mDelivery是怎么定义的。它其实是在RequestQueue中创建的,可以看到RequestQueue的其中一个构造方法:

 public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}

这里直接就new了一个ExecutorDelivery对象,并传入了一个不断从MainLooper中获取Message的Handler。再看看postResponse方法的内容:

 @Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

这里看到第5行调用了mResponsePoster的execute方法并传入了一个ResponseDeliveryRunnable对象,再看mResponsePoster的定义:

 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);
}
};
}

也就是我们在这里把ResponseDeliveryRunnable对象通过Handler的post方法发送出去了。这里为什么要发送到MainLooper中?因为RequestQueue是在子线程中执行的,回调到的代码也是在子线程中的,如果在回调中修改UI,就会报错。再者,为什么要使用post方法?原因也很简单,因为我们在消息发出之后再进行回调,post方法允许我们传入一个Runnable的实现了,post成功会自动执行它的run方法,这个时候在run方法中进行结果的判断并且进行回调:

 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;
} @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();
}
}
}

可以看到,23行是调用Request的deleverResponse方法将结果回调给StringRequest。接着看看StringRequest中该方法是实现:

 @Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}

直接通过我们构造StringRequest时传进来的Listener的回调方法onResponse来将结果回调给Activity。deleverError也是同样的做法。

再来看看performRequest是怎么进行网络请求的。

mNetwork是Network接口的对象,而这个接口只有一个实现类,就是BasicNetwork,我们看看这个BasicNetwork中的performRequest的代码:

 @Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError {
long requestStart = SystemClock.elapsedRealtime();
while (true) {
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try {
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode(); responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.
if (statusCode == HttpStatus.SC_NOT_MODIFIED) { Entry entry = request.getCacheEntry();
if (entry == null) {
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
} // A HTTP 304 response does not have all header fields. We
// have to use the header fields from the cache entry plus
// the new ones from the response.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
entry.responseHeaders.putAll(responseHeaders);
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
} // Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null) {
responseContents = entityToBytes(httpResponse.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 < 200 || statusCode > 299) {
throw new IOException();
}
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
} 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);
} catch (IOException e) {
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, SystemClock.elapsedRealtime() - requestStart);
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);
}
}
}

这段代码中,先10和11行代码将cache的属性设置给header,接着第9行调用mHttpStack对象的performRequest方法并传入请求对象和头部来进行请求,得到一个HttpResponse对象。

接着将HttpResponse对象中的状态吗取出,如果值为HttpStatus.SC_NOT_MODIFIED(也就是304),则表示请求得到的Response没有变化,直接显示缓存内容。

第39行表示请求成功并且获取到请求内容,将内容取出并作为一个NetworkResponse对象的属性并返回给NetworkDispatcher,接着将其转,接着就是上面介绍的回调给主线程了。

用过HttpClient的都知道,其实这里得到的HttpResponse就是由HttpClient返回的,我们直接看第12行调用的performRequest的源码:

 @Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
addHeaders(httpRequest, additionalHeaders);
addHeaders(httpRequest, request.getHeaders());
onPrepareRequest(httpRequest);
HttpParams httpParams = httpRequest.getParams();
int timeoutMs = request.getTimeoutMs();
// TODO: Reevaluate this connection timeout based on more wide-scale
// data collection and possibly different for wifi vs. 3G.
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
return mClient.execute(httpRequest);
}

这里14行的mClient其实就是一个HttpClient对象。

说到这里,是不是感觉很混乱呢?这里我做了一个图,可以参考看看

Android开发学习之路-Volley源码解析

我们这里简单再解析下:

① 在RequestQueue创建的时候,会生成多个NetwrokDispatcher,接着这些NetwrokDispatcher会不断的从请求队列中读取请求,如果有就使用包装好的请求类来执行performRequest,接着将结果通过postResponse方法传包装好并通过post方法发送到MainLooper。

② 在MainLooper中,判断Response是否有内容,通过deliverResponse将结果回调给RequestQueue,RequestQueue通过我们构造时传入的Listener中的回调方法对结果进行回调。

③ 在我们需要请求的时候,构建一个StringRequest(设置好对应的回调接口和实现回调方法)并将其add到MessageQueue中即可自动完成请求。

我们应该掌握什么?

① 子线程中应该如何将结果回调给主线程

② 如果自己要设计一个类似的框架,知道如何进行设计保证低耦合和便于维护

③ 通过学习其他的Request子类定义我们自己的请求类

④ 阅读源代码的技巧,比如查看直接子类,方法和参数定义查看等等

Android开发学习之路-Volley源码解析的更多相关文章

  1. Android开发学习之路-SimpleAdapter源码分析学习

    今天在课堂上,老师用到了SimpleAdapter,然后女神在边上问我为什么这个SimpleAdapter不能做到我app那种带有进度条的效果,言语说不清,然后就开始看源代码,发现这个Adapter的 ...

  2. Android开发——AsyncTask的使用以及源码解析

    .AsyncTask使用介绍  转载请标明出处:http://blog.csdn.net/seu_calvin/article/details/52172248 AsyncTask封装了Thread和 ...

  3. Android开发学习之路-二维码学习

    这个月装逼有点少了,为什么呢,因为去考软件射鸡师了,快到儿童节了,赶紧写篇博纪念一下逝去的青春,唔,请忽略这句话. 二维码其实有很多种,但是我们常见的微信使用的是一种叫做QRCode的二维码,像下面这 ...

  4. Android开发学习之路-RecyclerView滑动删除和拖动排序

    Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...

  5. Android开发学习之路--基于vitamio的视频播放器(二)

      终于把该忙的事情都忙得差不多了,接下来又可以开始good good study,day day up了.在Android开发学习之路–基于vitamio的视频播放器(一)中,主要讲了播放器的界面的 ...

  6. Android开发学习之路--Android Studio cmake编译ffmpeg

      最新的android studio2.2引入了cmake可以很好地实现ndk的编写.这里使用最新的方式,对于以前的android下的ndk编译什么的可以参考之前的文章:Android开发学习之路– ...

  7. Android开发学习之路--Activity之初体验

    环境也搭建好了,android系统也基本了解了,那么接下来就可以开始学习android开发了,相信这么学下去肯定可以把android开发学习好的,再加上时而再温故下linux下的知识,看看androi ...

  8. Android开发学习之路--Android系统架构初探

    环境搭建好了,最简单的app也运行过了,那么app到底是怎么运行在手机上的,手机又到底怎么能运行这些应用,一堆的电子元器件最后可以运行这么美妙的界面,在此还是需要好好研究研究.这里从芯片及硬件模块-& ...

  9. 50个Android开发人员必备UI效果源码&lbrack;转载&rsqb;

    50个Android开发人员必备UI效果源码[转载] http://blog.csdn.net/qq1059458376/article/details/8145497 Android 仿微信之主页面 ...

随机推荐

  1. React 入门教程

    React 起源于Facebook内部项目,是一个用来构建用户界面的 javascript 库,相当于MVC架构中的V层框架,与市面上其他框架不同的是,React 把每一个组件当成了一个状态机,组件内 ...

  2. &bsol;&bsol;ip 映射 指定的网络名不再可用

    问题:\\ip 映射  指定的网络名不再可用 解决方法:服务器端打开服务列表  services.msc 启动两个进程 1.Computer Browser 2. Workstation 就正常了~~ ...

  3. 56个PHP开发常用代码

    2016/02/14 6203 4    在编写代码的时候有个神奇的工具总是好的!下面这里收集了 50+ PHP 代码片段,可以帮助你开发 PHP 项目. 这些 PHP 片段对于 PHP 初学者也非常 ...

  4. python语言

    python语言 因为我比较熟悉python语言,所以月刊中python语言的项目居多,个人能力有限,其他语言涉及甚少,欢迎各路人士加入,丰富月刊的内容. 当然,如果您有更好的建议或者意见,欢迎发邮件 ...

  5. 很详细、很移动的Linux makefile教程:介绍,总述,书写规则,书写命令,使用变量,使用条件推断,使用函数,Make 的运行,隐含规则 使用make更新函数库文件 后序

    很详细.很移动的Linux makefile 教程 内容如下: Makefile 介绍 Makefile 总述 书写规则 书写命令 使用变量 使用条件推断 使用函数 make 的运行 隐含规则 使用m ...

  6. curl post请求总是返回417错误

    在进行post请求的时候, curl总是返回417错误 在使用curl做POST的时候, 当要POST的数据大于1024字节的时候, curl并不会直接就发起POST请求, 而是会分为俩步. 发送一个 ...

  7. &lbrack;LeetCode&rsqb; Score of Parentheses 括号的分数

    Given a balanced parentheses string S, compute the score of the string based on the following rule: ...

  8. 解析 ViewTreeObserver 源码(上)

    主要内容:ViewTreeObserver 是被用来注册监听视图树的观察者,在视图树发生全局改变时将收到通知.本文从 ViewTreeObserver 源码出发,带你剖析 ViewTreeObserv ...

  9. vue项目常见需求(项目实战笔记)

    一.起步 1.引入reset.css解决手机之间不同分辨率的问题(reset.css为别人封装的css文件) import './assets/styles/reset.css' 使用方式 1rem= ...

  10. hive操作

    1.创建hive分区表: create table invites (id int, name string) partitioned by (ds string) row format delimi ...