Volley源码解析
2016-01-22 16:36
405 查看
Volley支持网络图片、文本获取,内部会自动在内存,文件中缓存图片。
Volley还可以使用第三方的网络请求类库,只需在创建请求队列时实现相应接口即可。
Volley基本用法:
源码分析
使用Volley的第一步是创建请求队列,即:
。
HurlStack中重写的public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError
HttpClientStack中重写的public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError
ps:所以如果要使用第三方的网络请求类库时也可以仿照上面的写法
创建请求队列时有这么一句话
该方法内部只是调用了stack.performRequest(request, headers);
从而获取到网络数据,然后封装成一个Volley专用的NetworkResponse并返回,NetworkResponse中只是记录了http响应的状态码,响应头,返回的byte数组以及是否缓存。
现在只剩下创建请求队列的最后三步了
RequestQueue构造函数中最终执行了如下构造函数
NetworkDispatcher是Thread的直接子类,即该请求队列创建了几个线程(默认是4个),(当然还有一个CacheDispatcher线程)
run方法中mQueue是一个jdk中的线程安全的阻塞队列PriorityBlockingQueue。
由此可知四个线程都是死循环,不断从mQueue中获取request,没有就阻塞,有就执行网络请求,获取网络数据
RequestQueue中的add方法就是往线程安全的阻塞队列PriorityBlockingQueue中添加请求的,所以你可以同时调用请求队列的add方法添加各种请求
执行完网络请求后需要对返回的数据进行处理(parseNetworkResponse方法是Request中需要重写的方法。networkResponse 中记录的不是字符串也不是图片,而是原始的byte数组, 如果你想获取图片,那你就需要使用ImageRequest,想获取字符串就使用StringRequest,所以到底该怎么处理数据由你的请求来决定,所以数据处理工作就放在了Request中而不是Response中)
处理完后就该回调监听了。NetworkDispatcher中run方法最后又这么一句
通过查找代码我们发现mDelivery是在构造请求队列时创建的,同时使用UI线程的Looper创建了一个Handler,这样其他线程使用该handler发送的消息就会跑到UI线程中了。
在ExecutorDelivery中我们看到了最终调用了
到这为止Volley的主要流程就分析完毕了。总结一下主要流程:
图片加载源码解析
图片加载源码只不过是对封装了一下,底层实现类似。
先看一下如何使用Volley加载图片
下面这句话没啥特殊的,就是new了一个Listener并返回,并在重写的方法中设置图片下载失败、成功时的图片
主要看这个函数
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);
下面这句话没啥特殊的,就是new了一个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; }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件