android面试(11)-Volley
忙活了半天,终于把异步消息处理机制差不多都介绍完了,赶紧趁热打铁,从今天开始,就开始复习一些市面上比较有名的开源框架,每个开源框架大致都分为两步来说道说道,先谈一下怎么使用,会有代码实例,其次在讲一下关于源码的分析,好了,话不多说,现在就开始吧。
今天先讲Volley,volley是google推出的异步加载的一个网络框架,功能很强大,它适合那些数据量小的但是需要频繁去获取的情况。
1.简单使用:
(1)创建高并发的请求队列
(2)创建新的请求
(3)将请求添加到请求队列中
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Volley第一步:获取RequestQueue对象 queue = Volley.newRequestQueue(this); } private void VolleyStringRequest(){ //volley第二步,创建新的Request StringRequest request=new StringRequest("http://www.baidu.com", new Response.Listener<String>() { @Override public void onResponse(String response) { Log.e("返回成功的数据",response); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e("返回失败的原因",error.getMessage(),error); } }); //volley第三步,将request添加到RequestQueue中 queue.add(request); }
2.源码分析:
一步一步来看,先从第一步来看,看看他的newRequestQueue方法:
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) { 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; 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(); return queue; }
从这里我们可以看到,当stack==null时,手机版本如果大于9的话,他会创建一个HurlStack对象,这个HurlStack对象是什么呢,我截取它的一段代码就可以明白了
URL parsedUrl = new URL(url); HttpURLConnection connection = openConnection(parsedUrl, request); for (String headerName : map.keySet()) { connection.addRequestProperty(headerName, map.get(headerName)); }
这段代码在他的performRequest方法中,看起来非常熟悉,没错,这个HurlStack内部其实就是使用android原生的HTTPURLConnection来进行网络请求的。
继续往下看,可以看到,它会根据创建好的stack对象来创建NetWork对象,然后根据NetWork对象来创建RequestQueue对象,并调用其start方法,最后再将这个RequestQueue对象返回;
我们再来看看这个start方法到底做了些什么,
/** * 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 = 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(); } }
可以看到,start方法里面new了两种对象,一个是CacheDispatcher,从名字我们就知道他是一个缓存分发器,而实质上,它其实就是一个线程,内部其实是继承Thread的,NetWorkDispatcher,顾名思义,就是网络请求的分发器,看到这两个对象,我们就可以大致的想象出Volley的请求过程了;
当Volley进行请求时,他会开启两中请求线程,缓存分发器和网络分发器,当请求发起时,会先在缓存中找,如果缓存有,那么直接调用缓存中额数据,如果缓存中没有,则会请求网络去获取数据,获取完数据之后,还会向缓存中写一份。
还没完呢,这才是第一步,我们接下来看第三步的queue.add()方法,这里面代码比较繁琐,我截取其中比较重要的一段来看看
// If the request is uncacheable, skip the cache queue and go straight to the network. if (!request.shouldCache()) { mNetworkQueue.add(request); return request; }
这段代码表示,add之前,会判断当前的请求是否可以进行缓存,如果不可以,那么直接添加到网络请求队列中,否则,就添加到缓存请求队列中,默认的,所有请求都是可以进行缓存的;
既然我们一直在提什么缓存队列,网络请求队列,那我们就来看看这个CacheDispatcher到底是个什么
之前说过CacheDispatcher是一个线程,那么我们就来看看它的run方法:
public 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(); Request<?> request; while (true) { // release previous request object to avoid leaking request object when mQueue is drained. request = null; try { // Take a request from the queue. request = mCacheQueue.take(); } catch (InterruptedException e) { // We may have been interrupted because it was time to quit. if (mQuit) { return; } continue; } try { 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. final Request<?> finalRequest = request; mDelivery.postResponse(request, response, new Runnable() { @Override public void run() { try { mNetworkQueue.put(finalRequest); } catch (InterruptedException e) { // Not much we can do about this. } } }); } } catch (Exception e) { VolleyLog.e(e, "Unhandled exception %s", e.toString()); } } }代码比较长,我们挑重点的来看,可以看到,他里面其实是开启的一个while的死循环,说明,这个缓存分发器是一直在运行的,在
// 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; }
这段代码中,它会尝试从缓存中读取数据,如果这个数据是空的话,就把请求添加到网络请求队列中;如果不是空的话,再判断缓存是否过期,如果没过期,那么就尝试解析数据,然后把解析后的数据回调,过期的话和空的处理方法是一样的;
再来看看NetWorkDiapatcher:
它的run方法和cache的run方法差不多,内部也是开启了一个while死循环,不同的是,它在里面会调用performRequest(request)方法去请求数据,当得到响应response时,它会先写一份进入缓存,然后再根据不同的request进行不同的数据解析,StringRequest就解析成字符串类型的数据,JsonObjectRequest就解析出Json数据,解析完成后会调用postResponse方法将结果回调给主线程;
总结来说,就是上面这张图,好好理解把握对比源码,就可以明白了;