下面介绍一个基于Volley来写的一个网络请求模块,对于Volley的介绍,可以看这篇文章:
Android 网络通信框架Volley简介(Google IO 2013)
好啦,直接进入主题咯。
那么我们的网络模块到底该怎么写比较好看一点呢?
下面是一个可以参考的模板:
Activity
在我们的Activity里面的请求是这样的:
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loadData();
}
private void loadData() {
StringRequest stringRequest = new StringRequest(
Constant.YOUR_URL, new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.i("onResponse","data= "+response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("str","onErrorResponse "+error);
}
});
stringRequest.setTag(getClass().getSimpleName());
MyApplication.getRequestQueue().add(stringRequest);
}
}
上面的代码看起来还不是很多,还可以看懂。建立一个请求,然后请求的连接就是Constant.YOUR_URL
,这个是你想请求的地址。然后把我们的请求传给我们的队列,由他去执行。
Application
很简单的就这样
public class MyApplication extends Application {
private static MyApplication myApplication;
private static RequestQueue requestQueue;
public static MyApplication getInstance() {
return myApplication;
}
public static RequestQueue getRequestQueue() {
return requestQueue;
}
@Override
public void onCreate() {
super.onCreate();
myApplication = this;
requestQueue = Volley.newRequestQueue(this);
}
}
到现在应该都很好理解,那么,该进入主题了!
看我们怎么写StringRequest咯?
StringRequest
不好意思哦,这个是Volley自带的一个类,不用我们写啦,
是不是觉得很容易好用!用Volley框架让你省去很多功夫?是不是觉有点没过瘾?
那我们看下源码:
/**
* A canned request for retrieving the response body at a given URL as a String.
*/
public class StringRequest extends Request<String> {
private final Listener<String> mListener;
/**
* Creates a new request with the given method.
*
* @param method the request {@link Method} to use
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
public StringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
}
/**
* Creates a new GET request.
*
* @param url URL to fetch the string at
* @param listener Listener to receive the String response
* @param errorListener Error listener, or null to ignore errors
*/
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));
}
}
这代码看起来也很简单,继承了Request,然后实现连接它的抽线接口做回调。
因为这个Request类太大,就不贴在这,我们先从头开始看起下吧。
源头
经过我们的查看,我们在Application申请的队列requestQueue = Volley.newRequestQueue(this);
最后在源代码是下面这么句,申请一个队列,然后就start(),开始干活咯。
RequestQueue queue;
if (maxDiskCacheBytes <= -1)
{
// No maximum size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
}
else
{
// Disk cache size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
}
queue.start();
好了,那么问题来了。这个背后是个什么故事?
RequestQueue
RequestQueue,查看他的构造器,是下面这样,看起来很可能是一个什么线程池的样子,因为有一个threadPoolSize单词在这里,而且认识似乎都和networkDispatcher.start()这个紧密结合在一起,因为他背后是让这个开始去干活。
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
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();
}
}
嗯,是需要看下这个NetworkDispatcher是什么东西了。
NetworkDispatcher
我们看到源代码,他的声明是一个继承Thread的东西,看来似乎验证了我们的什么猜想。
public class NetworkDispatcher extends Thread
既然是继承与Thread,那我们得来看下他的Run里面写了什么东东:
现在,我们再来看下他里面具体的,为便于说明,有删减部分代码。
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
Request<?> request;
// Take a request from the queue.
request = mQueue.take();
// 这句重要,我们的Request最后就是跑到这里被执行了的。
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
//把请求的网络结果进行解析。
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//接着就是用 ResponseDelivery mDelivery这个类回调结果
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);
}
//我们的出错回调来自这两个异常。
}
}
好啦,基本到这里快结束了,但我们需要看下这个回调的Delivery是怎么回事;
层层返回后,我们看到这Delivery是在构造RequestQueue里面生成的,
new ExecutorDelivery(new Handler(Looper.getMainLooper()))
就是这句。
ResponseDelivery
本身只是一个接口,具体的实现是这个ExecutorDelivery
不知道你们注意到他的参数没有,
这个Delivery构造函数是一个Handler
,
而且这个Handler拿的是运行在UI线程的Looper
哦,因此他的回调结果能运行在主线程的哦!下面是代码,真的就是用ExecutorDelivery.
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
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) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
OK。知道了具体干活的ExecutorDelivery,我们来看下
mDelivery.postResponse(request, response);
这个函数里面到底干了什么
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
层层转包,发现是ResponseDeliveryRunnable
这么个线程被执行了,那我们来看下他的Run方法里面写了什么内容
public void run() {
// 如果这请求被取消就finish掉,不传递回调
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
//好啦,终于看到重要的一句啦,就是这个回调啦
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
......
}
到现在为止,我们建立了StringRequest里面的回调和这个的联系,从而让我们收到结果。
== ,不对啊,好像落了什么
好像还有些没说完的,那么,好像没看到和我们前面说的MainLooper,Handler什么关系啊?
因为还没说mResponsePoster.execute()
这个函数,我们看下构造函数。
private final Executor 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);
}
};
}
到此,一切都清楚了
看到这里,我想到了以前刚开始学安卓的时候,对于网络请求,就是在Activity里面直接开一个线程,
然后用Handler来显示数据。
这个Volley应该就算是高度封装版本吧,哈哈,所以万变不离其中,核心的还是一样的。
就像AnsyncTask底层其实也是封装Handler和Looper一样。
基础方法就那样,没别的。
====
好像还是漏了很重要的内容,
那个,他的网络求情底层到底用什么实现的?httpClient?HttpUrlConnection?还是什么鬼?
前面好像没提及到啊,为了先讲清楚流程,前面跳过了一句话,没对他进行深入,那就是
NetworkResponse networkResponse = mNetwork.performRequest(request);
就是这句啦,通过mNetWork去执行我们的请求。
NetWork本身也是一个接口,里面只有一个performRequest(),
他的接口实现是BasicNetWork类,在Volley.newRequestQueue()
里面初始化
代码如下:
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);
我们来看下这个BasicNetWork实现的接口里面写了什么
@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());
//------------------看这里--------------------
//------什么,背后居然还有一个HttpStack.performRequest()---
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);
}
// Handle moved resources
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
String newUrl = responseHeaders.get("Location");
request.setRedirectUrl(newUrl);
}
// 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(){
.....
}
}
}
好吧,那么这个HttpStack到底是什么鬼呢?
其实刚才在初始化这个NetWork时候就有写了
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));
}
}
对于版本为9以上的用HttpUrlConnection,9以下的版本用HttpClient来.
HttpClientStack里面的Perform如下
protected final HttpClient mClient;
@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);
}
接着来看下UrlConenction的perform
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
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 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
response.setEntity(entityFromConnection(connection));
}
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;
}
写了很长了,不过关于这个库的还有一些没有说的,再说这文章就更长了,下次再补充吧
补充的就是他的CacheDispatcher
,缓存请求命中的直接返回部分.
好了,到这里,是时候发一张图上来
Volley的架构设计
Volley
使用了线程池来作为基础结构,主要分为主线程,cache线程和network线程。
主线程和cache线程都只有一个,而NetworkDispatcher线程可以有多个,这样能解决比并行问题。
相信通过上面的层层挖掘,你对下面这张图就直接看得更明白了吧
看了这么,这里有一篇高逼格的,叫别人家的分析的文章推荐,相当不错Volley 源码解析
// 2015/12/16 更新下面内容
赏析
上面的图很好了解释整体,但并不是很好的能够欣赏
到整个设计的良好的地方
上面这张图就可以,我们可以看到,很多关节的点是接口。
这是关于依赖倒置
的一个很好的解释案例图,
另外一点补充的是,图中的各种XxxRequest
都是继承于Request<T>
的,
这个设计很好,我看到不少项目,对于返回的数据,是没有设置一个通用的ResponeBody
类来做的