Volley支持网络图片、文本获取,内部会自动在内存,文件中缓存图片。
Volley还可以使用第三方的网络请求类库,只需在创建请求队列时实现相应接口即可。
Volley基本用法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// 创建 RequestQueue
RequestQueue mQueue = Volley.newRequestQueue(this);
//构造请求,并加入到RequestQueue中。只需加入,即可自动执行网络请求
mQueue.add(new StringRequest("http://www.baidu.com", new Listener<String>() {
@Override
public void onResponse(String response) {
String txt = new String(response.getBytes());
((TextView) findViewById(R.id.tv)).setText(txt);
System.out.println(txt);
}
}, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}));
//一个请求队列可加入多个请求,默认会选择4个线程中的一个去执行网络请求
mQueue.add(
new ImageRequest("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
((ImageView) findViewById(R.id.iv1)).setImageBitmap(response);
}
}, 0, 0, Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}));
//第2种图片加载方式(推荐使用)
ImageView imageView = (ImageView) findViewById(R.id.iv2);
ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate,
android.R.drawable.ic_delete);
ImageLoader mImageLoader = new ImageLoader(mQueue, new BitmapCache());
mImageLoader.get("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", listener);
}
源码分析
使用Volley的第一步是创建请求队列,即:
RequestQueue mQueue = Volley.newRequestQueue(this);
或
Volley.newRequestQueue(getApplicationContext(), new HttpStack() {
@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
/*
* 这里可以使用第三方的网络请求类库,网络请求所需的参数都已经封装在了request中,
* additionalHeaders中是部分请求头信息。
*
* 执行完网络请求后只需构造一个org.apache.http.HttpResponse,
* 并把返回的数据(相应头,状态码,body等)封装在里面并返回即可
*/
return null;
}
});
第二种方法中使用了自定义的网络请求类库,当该mQueue中add请求时,就会使用自定义的网络请求类库去执行网络请求。接下来看一下请求队列的创建过程
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;
}
可以看到当stack不是null时就使用自定义的网络请求类库,否则就使用默认的,当Build.VERSION.SDK_INT >= 9时就使用Volley中的HurlStack,打开HurlStack可以看到用的是HttpURLConnection ,当<9时用的是Volley中的HttpClientStack,打开HttpClientStack可以看到用的是HttpUriRequest
。
HurlStack中重写的public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError
@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);
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;
}
HttpClientStack中重写的 public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError
@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);
}
ps:所以如果要使用第三方的网络请求类库时也可以仿照上面的写法
创建请求队列时有这么一句话
Network network = new BasicNetwork(stack);
BasicNetwork中最重要的一个方法就是重写的Network中的
public NetworkResponse performRequest(Request<?> request) throws VolleyError
该方法内部只是调用了 stack.performRequest(request, headers);
从而获取到网络数据,然后封装成一个Volley专用的NetworkResponse并返回,NetworkResponse中只是记录了http响应的状态码,响应头,返回的byte数组以及是否缓存。
现在只剩下创建请求队列的最后三步了
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
RequestQueue构造函数中最终执行了如下构造函数
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
NetworkDispatcher是Thread的直接子类,即该请求队列创建了几个线程(默认是4个),(当然还有一个CacheDispatcher线程)
run方法中mQueue是一个jdk中的线程安全的阻塞队列PriorityBlockingQueue。
由此可知四个线程都是死循环,不断从mQueue中获取request,没有就阻塞,有就执行网络请求,获取网络数据
@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 {
省略部分代码。。。
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
RequestQueue中的add方法就是往线程安全的阻塞队列PriorityBlockingQueue中添加请求的,所以你可以同时调用请求队列的add方法添加各种请求
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;
}
执行完网络请求后需要对返回的数据进行处理(parseNetworkResponse方法是Request中需要重写的方法。networkResponse 中记录的不是字符串也不是图片,而是原始的byte数组, 如果你想获取图片,那你就需要使用ImageRequest,想获取字符串就使用StringRequest,所以到底该怎么处理数据由你的请求来决定,所以数据处理工作就放在了Request中而不是Response中)
Response<?> response = request.parseNetworkResponse(networkResponse);
处理完后就该回调监听了。NetworkDispatcher中run方法最后又这么一句
mDelivery.postResponse(request, response);
通过查找代码我们发现mDelivery是在构造请求队列时创建的,同时使用UI线程的Looper创建了一个Handler,这样其他线程使用该handler发送的消息就会跑到UI线程中了。
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
在ExecutorDelivery中我们看到了最终调用了
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
request中最终又调用了
mListener.onResponse(response);(以StringRequest为例)
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
Request中:
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
到这为止Volley的主要流程就分析完毕了。总结一下主要流程:
RequestQueue mQueue = Volley.newRequestQueue(this); ->
mQueue.add(Request)到PriorityBlockingQueue中 ->
多个NetworkDispatcher线程循环从PriorityBlockingQueue 读取Requset ->
得到Requset然后执行网络请求 NetworkResponse networkResponse = mNetwork.performRequest(request); ->
得到networkResponse ,对networkResponse 中的原始数据进行处理(如 转换成图片或字符串)得到Response->
通过ExecutorDelivery转移到主线程去回调监听器 ->
得到结果,请求结束
图片加载源码解析
图片加载源码只不过是对封装了一下,底层实现类似。
先看一下如何使用Volley加载图片
ImageView imageView = (ImageView) findViewById(R.id.iv2);
ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate,
android.R.drawable.ic_delete);
ImageLoader mImageLoader = new ImageLoader(mQueue, new BitmapCache());
mImageLoader.get("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", listener);
ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate,
android.R.drawable.ic_delete);
主要看这个函数
mImageLoader.get("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", listener);
详见如下注释
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
// only fulfill requests that were initiated from the main thread.
// 是否在主线程中执行,不是就抛异常
throwIfNotOnMainThread();
/*
* 把图片路径以及大小拼接成一个字符串,作为map中的键,图片作为值,从
* 而可以根据键来快速从内存中获取图片(即 缓存到内存中,同时使用LRU算法对bitmap进行替换)
*/
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
//把图片,url等封装一下作为参数传递给监听器
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}
// The bitmap did not exist in the cache, fetch it!
//如果内存中之前没有这张图片
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// The request is not already in flight. Send the new request to the network and
// track it.
/*
* 创建一个ImageRequest,加入到请求队列中下载图片,下载成功后会把bitmap和cacheKey保
* 存到map中进行缓存。
* ImageRequest中的parseNetworkResponse会对图片进行压缩处理
*/
Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}