Android Volley框架使用源码分享
过去在Android上网络通信都是使用的Xutils因为用它可以顺道处理了图片和网络这两个方面,后来发觉Xutils里面使用的是HttpClient 而Google在6.0的版本上已经把HttpClient废除了,所以开始寻找新的网络框架,okhttp也用过,但是它是在作用在UI线程,使用起来还需要用handler所以就先用着Volley框架了。 这里我先分析下Volley框架的简单网络请求的源码。
使用Volley请求网络数据的简单过程:
RequestQueuequeue=Volley.newRequestQueue(this);//实例化一个请求队列Google推荐写一个单例类获取唯一一个队列 StringRequestrequest=newStringRequest(Request.Method.POST,url1,newResponse.Listener<String>(){ @Override publicvoidonResponse(Stringresponse){ Toast.makeText(MainActivity.this,"success"+response,Toast.LENGTH_SHORT).show(); } },newResponse.ErrorListener(){ @Override publicvoidonErrorResponse(VolleyErrorerror){ Toast.makeText(MainActivity.this,"失败了"+error.getMessage(),Toast.LENGTH_SHORT).show(); } }){ @Override protectedMap<String,String>getParams()throwsAuthFailureError{//重写这个函数提交参数也可以重写一个Request实现这个方法 Map<String,String>params=newHashMap<>(); params.put(aaa+"name","1233555");//参数 returnparams; } }; queue.add(request);
请求的处理在newRequestQueue的时候就开始执行了 只不过那时候请求队列中还没有请求 所以阻塞了当add的方法执行时 才开始真正请求网络
所以我们先来看 queue.add(request) 方法
public<T>Request<T>add(Request<T>request){ //Tagtherequestasbelongingtothisqueueandaddittothesetofcurrentrequests. request.setRequestQueue(this); synchronized(mCurrentRequests){ mCurrentRequests.add(request);//在当前队列中加入 } //Processrequestsintheordertheyareadded. request.setSequence(getSequenceNumber()); request.addMarker("add-to-queue");//设置标志 //Iftherequestisuncacheable,skipthecachequeueandgostraighttothenetwork. if(!request.shouldCache()){//根据是否需要缓存如果不需要缓存就直接加入网络任务队列中然后返回如果需要缓存那么在下面代码中加入缓存队列默认是需要缓存的 mNetworkQueue.add(request); returnrequest; } //Insertrequestintostageifthere'salreadyarequestwiththesamecachekeyinflight. synchronized(mWaitingRequests){ StringcacheKey=request.getCacheKey(); if(mWaitingRequests.containsKey(cacheKey)){//判断当前正在被处理并可以缓存的请求中是否包含该请求的key如果包含说明已经有一个相同的请求那么就加入到其中 //Thereisalreadyarequestinflight.Queueup. Queue<Request<?>>stagedRequests=mWaitingRequests.get(cacheKey); if(stagedRequests==null){ stagedRequests=newLinkedList<Request<?>>(); } stagedRequests.add(request); mWaitingRequests.put(cacheKey,stagedRequests); if(VolleyLog.DEBUG){ VolleyLog.v("RequestforcacheKey=%sisinflight,puttingonhold.",cacheKey); } }else{//如果不包含加入一个空的请求到暂存队列中然后加入到缓存队列中 //Insert'null'queueforthiscacheKey,indicatingthereisnowarequestin //flight. mWaitingRequests.put(cacheKey,null); mCacheQueue.add(request); } returnrequest; } }
分析add方法 首先加入到mCurrentRequests集合中 这个集合存放所有这个队列所处理的请求 然后判断这个请求是否需要缓存,如果不需要缓存,那么直接加入mNetworkQueue队列中等待处理即可,如果需要那么最终加入到mCacheQueue队列中,因为RequestQueue在处理请求时总会先处理缓存的任务,在处理缓存时如果第一次处理没有缓存还是会加入mNetworkQueue队列中处理,如果有缓存那么就直接获取缓存了,之后判断当前的请求中是否有相同的请求,如果有的话那么就把这个请求加入到暂存集合中,如果没有那么就加入一个空的到请求到暂存队列中,用来以后判断是否有和这个请求相同的请求,然后加入缓存队列中即可。
然后我们来看RequstQueue的创建过程
publicstaticRequestQueuenewRequestQueue(Contextcontext,HttpStackstack){ FilecacheDir=newFile(context.getCacheDir(),DEFAULT_CACHE_DIR);//创建一个文件用于缓存 StringuserAgent="volley/0";//用户代理初始化 try{ StringpackageName=context.getPackageName(); PackageInfoinfo=context.getPackageManager().getPackageInfo(packageName,0); userAgent=packageName+"/"+info.versionCode;//用户代理为app包名+版本号 }catch(NameNotFoundExceptione){ } if(stack==null){//如果没传入HttpStack那么采用下述默认的这里可以自行重写扩展HttpStack体现了该框架的高扩展性 if(Build.VERSION.SDK_INT>=9){//如果sdk版本高于2.3采用HurlStack内部是httpUrlConnection实现 stack=newHurlStack(); }else{//如果版本低于2.3采用httpClientStack //PriortoGingerbread,HttpUrlConnectionwasunreliable. //See:http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack=newHttpClientStack(AndroidHttpClient.newInstance(userAgent)); } } Networknetwork=newBasicNetwork(stack);//创建一个网络工作仅仅作用于请求网络 RequestQueuequeue=newRequestQueue(newDiskBasedCache(cacheDir),network);//实例化一个请求队列传入参数 queue.start(); returnqueue; }
</pre><precode_snippet_id="1680121"snippet_file_name="blog_20160512_5_2241745"name="code"class="java">publicRequestQueue(Cachecache,Networknetwork,intthreadPoolSize){//构造函数会创建默认的ExecutorDelivery用于回调 this(cache,network,threadPoolSize, newExecutorDelivery(newHandler(Looper.getMainLooper()))); }
RequestQueue的创建过程也比较简单 根据sdk版本号判断使用HttpURLConnection还是HttpClient 因为在2.3之前httpUrlConnection有一个重大的bug 所以使用HttpClient代替,而httpUrlConnection体积小 支持gzip压缩和缓存,并且速度相对httpClient快并逐渐优化所以选择httpUrlConnection 之后根据创建的NetWork创建RequestQueue队列然后开启即可
之后我们查看 queue的start方法
publicvoidstart(){ stop();//Makesureanycurrentlyrunningdispatchersarestopped. //Createthecachedispatcherandstartit. mCacheDispatcher=newCacheDispatcher(mCacheQueue,mNetworkQueue,mCache,mDelivery);//创建一个缓存调度器是一个线程start后执行run方法 mCacheDispatcher.start(); //Createnetworkdispatchers(andcorrespondingthreads)uptothepoolsize. for(inti=0;i<mDispatchers.length;i++){//默认会有4个NetworkDispatcher为了提高效率执行netWorkQueue里的request NetworkDispatchernetworkDispatcher=newNetworkDispatcher(mNetworkQueue,mNetwork, mCache,mDelivery); mDispatchers[i]=networkDispatcher; networkDispatcher.start(); } }
这个方法先执行缓存调度器线程然后执行4个网络工作调度器线程,因为在缓存调度器中会判断是否缓存过,如果缓存过并且没过期,就直接复用缓存的,不把任务加入netWordQueue中所以下面的NetWork调度器线程就会取不到请求而阻塞,不会执行,而如果没有缓存,缓存调度器线程中就会把请求加入NetWork队列中,下面的netWork调度器就会取到该请求并执行了
我们仔细看一下CacheDispatcher线程的源码:
run方法的代码比较长我们分开来看 先看第一部分:
@Override publicvoidrun(){ if(DEBUG)VolleyLog.v("startnewdispatcher"); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//设置线程的优先级值为10 //Makeablockingcalltoinitializethecache. mCache.initialize();//初始化一下缓存 while(true){ try{ //Getarequestfromthecachetriagequeue,blockinguntil //atleastoneisavailable. finalRequest<?>request=mCacheQueue.take();//从缓存队列取出一个请求如果没有则会阻塞 request.addMarker("cache-queue-take");//添加一个标记 //Iftherequesthasbeencanceled,don'tbotherdispatchingit. if(request.isCanceled()){ request.finish("cache-discard-canceled"); continue; } //Attempttoretrievethisitemfromcache. Cache.Entryentry=mCache.get(request.getCacheKey());//从缓存中读取缓存 if(entry==null){//如果没读取到缓存 request.addMarker("cache-miss");//添加缓存miss标记 //Cachemiss;sendofftothenetworkdispatcher. mNetworkQueue.put(request);//换区缓存失败添加到netWork中等待请求 continue; } //Ifitiscompletelyexpired,justsendittothenetwork. if(entry.isExpired()){//判断缓存是否过期了如果过期了那么就添加到netWork中等待请求 request.addMarker("cache-hit-expired"); request.setCacheEntry(entry); mNetworkQueue.put(request); continue; }
第二部分:
//Wehaveacachehit;parseitsdatafordeliverybacktotherequest. request.addMarker("cache-hit");//执行到了这里说明缓存没有过期并且可以使用 Response<?>response=request.parseNetworkResponse(//把读取到的缓存内容解析成Response对象 newNetworkResponse(entry.data,entry.responseHeaders)); request.addMarker("cache-hit-parsed");//添加标记 if(!entry.refreshNeeded()){//如果缓存不需要刷新直接调用mDelivery.postResponse方法在其中会回调request的listener接口 //Completelyunexpiredcachehit.Justdelivertheresponse. mDelivery.postResponse(request,response); }else{//如果需要刷新把请求加入mNetworkQueue中等待请求 //Soft-expiredcachehit.Wecandeliverthecachedresponse, //butweneedtoalsosendtherequesttothenetworkfor //refreshing. request.addMarker("cache-hit-refresh-needed"); request.setCacheEntry(entry); //Marktheresponseasintermediate. response.intermediate=true; //Posttheintermediateresponsebacktotheuserandhave //thedeliverythenforwardtherequestalongtothenetwork. mDelivery.postResponse(request,response,newRunnable(){ @Override publicvoidrun(){ try{ mNetworkQueue.put(request); }catch(InterruptedExceptione){ //Notmuchwecandoaboutthis. } } }); } }catch(InterruptedExceptione){ //Wemayhavebeeninterruptedbecauseitwastimetoquit. if(mQuit){ return; } continue; } } }
上面代码的具体过程也很简单 首先从缓存请求队列取出一个请求,在缓存中看看有没有该请求的缓存,如果没有那么请求放入NetWork调度器中 等待调用 如果有也分几种情况 如果获取到的是空,放入NetWOrk 如果过期放入NetWork 如果不需要刷新 就直接从缓存获取响应信息并解析 然后用mDelivery回调接口即可 如果需要刷新放入NetWOrd队列等待调用。。。
我们再来看看NetworkDispatcher线程的代码就可以了类似于CacheDispatcher的代码:
@Override publicvoidrun(){ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//设置优先级10 while(true){ longstartTimeMs=SystemClock.elapsedRealtime();//获取请求执行开始时间 Request<?>request; try{ //Takearequestfromthequeue. request=mQueue.take();//从队列获取一个请求没有则阻塞 }catch(InterruptedExceptione){ //Wemayhavebeeninterruptedbecauseitwastimetoquit. if(mQuit){ return; } continue; } try{ request.addMarker("network-queue-take"); //Iftherequestwascancelledalready,donotperformthe //networkrequest. if(request.isCanceled()){ request.finish("network-discard-cancelled"); continue; } addTrafficStatsTag(request); //Performthenetworkrequest. NetworkResponsenetworkResponse=mNetwork.performRequest(request);//真正执行请求的函数并返回响应 request.addMarker("network-http-complete"); //Iftheserverreturned304ANDwedeliveredaresponsealready, //we'redone--don'tdeliverasecondidenticalresponse. if(networkResponse.notModified&&request.hasHadResponseDelivered()){ request.finish("not-modified"); continue; } //Parsetheresponsehereontheworkerthread. Response<?>response=request.parseNetworkResponse(networkResponse);//解析响应 request.addMarker("network-parse-complete"); //Writetocacheifapplicable. //TODO:Onlyupdatecachemetadatainsteadofentirerecordfor304s. if(request.shouldCache()&&response.cacheEntry!=null){//如果需要缓存那么把响应的信息存入缓存中 mCache.put(request.getCacheKey(),response.cacheEntry); request.addMarker("network-cache-written"); } //Posttheresponseback. request.markDelivered(); mDelivery.postResponse(request,response);//之后回调一些方法 }catch(VolleyErrorvolleyError){ volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime()-startTimeMs); parseAndDeliverNetworkError(request,volleyError);//回调错误接口 }catch(Exceptione){ VolleyLog.e(e,"Unhandledexception%s",e.toString()); VolleyErrorvolleyError=newVolleyError(e); volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime()-startTimeMs); mDelivery.postError(request,volleyError);//回调错误接口 } } }
NetworkDispatcher线程的执行过程 先从networkDispatch中获取一个请求 然后判断是否取消了 如果没有那么就执行NetWOrk的performRequest方法 执行http请求,这个函数内部才是真正的请求数据 ,请求后根据设置的shouldCache标志判断是否放入缓存中 之后回调一些接口方法即可 这样就完成了一个请求
最后我们看一看NetWork类mNetwork.performRequest(request)方法是如何提交请求的吧 代码比较长但是不难:
@Override publicNetworkResponseperformRequest(Request<?>request)throwsVolleyError{ longrequestStart=SystemClock.elapsedRealtime();//记录开始时间 while(true){ HttpResponsehttpResponse=null; byte[]responseContents=null; Map<String,String>responseHeaders=Collections.emptyMap();//初始化响应头为空 try{ //Gatherheaders. Map<String,String>headers=newHashMap<String,String>();//请求头 addCacheHeaders(headers,request.getCacheEntry());//根据缓存添加请求头 httpResponse=mHttpStack.performRequest(request,headers);//调用HttpStack的方法请求网络 StatusLinestatusLine=httpResponse.getStatusLine(); intstatusCode=statusLine.getStatusCode(); responseHeaders=convertHeaders(httpResponse.getAllHeaders());//获取响应头 //Handlecachevalidation. if(statusCode==HttpStatus.SC_NOT_MODIFIED){//如果为304读取的缓存 Entryentry=request.getCacheEntry();//查看以前是否缓存过 if(entry==null){//如果以前缓存的为空那么说明上次缓存的请求也为空直接返回response returnnewNetworkResponse(HttpStatus.SC_NOT_MODIFIED,null, responseHeaders,true, SystemClock.elapsedRealtime()-requestStart); } //AHTTP304responsedoesnothaveallheaderfields.We //havetousetheheaderfieldsfromthecacheentryplus //thenewonesfromtheresponse. //http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 entry.responseHeaders.putAll(responseHeaders);//如果不空那么就添加头然后返回数据了 returnnewNetworkResponse(HttpStatus.SC_NOT_MODIFIED,entry.data, entry.responseHeaders,true, SystemClock.elapsedRealtime()-requestStart); } //Someresponsessuchas204sdonothavecontent.Wemustcheck. if(httpResponse.getEntity()!=null){//不是304的情况
responseContents=entityToBytes(httpResponse.getEntity());//获取响应的内容下面返回响应即可 }else{ //Add0byteresponseasawayofhonestlyrepresentinga //no-contentrequest. responseContents=newbyte[0]; } //iftherequestisslow,logit. longrequestLifetime=SystemClock.elapsedRealtime()-requestStart; logSlowRequests(requestLifetime,request,responseContents,statusLine); if(statusCode<200||statusCode>299){ thrownewIOException(); } returnnewNetworkResponse(statusCode,responseContents,responseHeaders,false, SystemClock.elapsedRealtime()-requestStart); }catch(SocketTimeoutExceptione){ attemptRetryOnException("socket",request,newTimeoutError()); }catch(ConnectTimeoutExceptione){ attemptRetryOnException("connection",request,newTimeoutError()); }catch(MalformedURLExceptione){ thrownewRuntimeException("BadURL"+request.getUrl(),e); }catch(IOExceptione){ intstatusCode=0; NetworkResponsenetworkResponse=null; if(httpResponse!=null){ statusCode=httpResponse.getStatusLine().getStatusCode(); }else{ thrownewNoConnectionError(e); } VolleyLog.e("Unexpectedresponsecode%dfor%s",statusCode,request.getUrl()); if(responseContents!=null){ networkResponse=newNetworkResponse(statusCode,responseContents, responseHeaders,false,SystemClock.elapsedRealtime()-requestStart); if(statusCode==HttpStatus.SC_UNAUTHORIZED|| statusCode==HttpStatus.SC_FORBIDDEN){ attemptRetryOnException("auth", request,newAuthFailureError(networkResponse)); }else{ //TODO:OnlythrowServerErrorfor5xxstatuscodes. thrownewServerError(networkResponse); } }else{ thrownewNetworkError(networkResponse); } } }
然后看HttpStack的请求代码:
@Override publicHttpResponseperformRequest(Request<?>request,Map<String,String>additionalHeaders) throwsIOException,AuthFailureError{ Stringurl=request.getUrl(); HashMap<String,String>map=newHashMap<String,String>(); map.putAll(request.getHeaders());//添加请求头 map.putAll(additionalHeaders); if(mUrlRewriter!=null){ Stringrewritten=mUrlRewriter.rewriteUrl(url); if(rewritten==null){ thrownewIOException("URLblockedbyrewriter:"+url); } url=rewritten; } URLparsedUrl=newURL(url); HttpURLConnectionconnection=openConnection(parsedUrl,request);//打开连接 for(StringheaderName:map.keySet()){//设置头 connection.addRequestProperty(headerName,map.get(headerName)); } setConnectionParametersForRequest(connection,request);//在这个函数里添加请求的参数和一些基本的信息配置 //InitializeHttpResponsewithdatafromtheHttpURLConnection. ProtocolVersionprotocolVersion=newProtocolVersion("HTTP",1,1); intresponseCode=connection.getResponseCode();//下面就是些获取响应信息后的处理了 if(responseCode==-1){ //-1isreturnedbygetResponseCode()iftheresponsecodecouldnotberetrieved. //Signaltothecallerthatsomethingwaswrongwiththeconnection. thrownewIOException("CouldnotretrieveresponsecodefromHttpUrlConnection."); } StatusLineresponseStatus=newBasicStatusLine(protocolVersion, connection.getResponseCode(),connection.getResponseMessage()); BasicHttpResponseresponse=newBasicHttpResponse(responseStatus); response.setEntity(entityFromConnection(connection)); for(Entry<String,List<String>>header:connection.getHeaderFields().entrySet()){ if(header.getKey()!=null){ Headerh=newBasicHeader(header.getKey(),header.getValue().get(0)); response.addHeader(h); } } returnresponse; }
这个函数中主要是HttpUrlConnection的使用添加头在connection.addRequestProperty方法中 添加参数需要获取流然后写入参数 下面这个函数中有介绍假设是post方式:
caseMethod.POST: connection.setRequestMethod("POST"); addBodyIfExists(connection,request); break;
privatestaticvoidaddBodyIfExists(HttpURLConnectionconnection,Request<?>request) throwsIOException,AuthFailureError{ byte[]body=request.getBody(); if(body!=null){ connection.setDoOutput(true); connection.addRequestProperty(HEADER_CONTENT_TYPE,request.getBodyContentType()); DataOutputStreamout=newDataOutputStream(connection.getOutputStream()); out.write(body); out.close(); } }
将body写入到流中就可以 参数的封装在body中
publicbyte[]getBody()throwsAuthFailureError{ Map<String,String>params=getParams(); if(params!=null&¶ms.size()>0){ returnencodeParameters(params,getParamsEncoding()); } returnnull; } getParams方法是Request需要重写的一个方法返回值就是参数的Map集合 [java]viewplaincopy在CODE上查看代码片派生到我的代码片 privatebyte[]encodeParameters(Map<String,String>params,StringparamsEncoding){ StringBuilderencodedParams=newStringBuilder(); try{ for(Map.Entry<String,String>entry:params.entrySet()){ encodedParams.append(URLEncoder.encode(entry.getKey(),paramsEncoding)); encodedParams.append('='); encodedParams.append(URLEncoder.encode(entry.getValue(),paramsEncoding)); encodedParams.append('&'); } returnencodedParams.toString().getBytes(paramsEncoding); }catch(UnsupportedEncodingExceptionuee){ thrownewRuntimeException("Encodingnotsupported:"+paramsEncoding,uee); } }
这个函数就是按照一定规则拼接字符串参数即可 然后就可以提交参数了
最后介绍下这个框架主要的几个类、成员及他们作用:
RequestQueue 用来处理请求的队列,请求都放在这个类中 调用start方法开始处理请求
mCache 请求的缓存,当提交了一个请求并且此请求需要缓存时,会放入这个缓存中
mNetwork 单纯用于提交网络请求的接口 只有一个提交请求的方法 需要传入一个HttpStack来完成请求的提交
mDelivery 用于请求响应后的接口回调等功能
mDispatchers NetWork调度器线程数组 包含4个对象处理请求 目的是为了提高效率 当没有缓存可以获取或者已经过期 需要刷新时 会调用这个线程的run方法 如果没有则阻塞
mCacheDispatcher 缓存调度器线程 处理已经缓存了的请求 如果没有缓存则将请求放入NetWorkQueue等待调用
以上就是本文的全部内容,希望对大家学习AndroidVolley框架有所帮助。