Android Volley框架使用源码分享
过去在android上网络通信都是使用的xutils 因为用它可以顺道处理了图片和网络这两个方面,后来发觉xutils里面使用的是httpclient 而google在6.0的版本上已经把httpclient废除了,所以开始寻找新的网络框架,okhttp也用过,但是它是在作用在ui线程,使用起来还需要用handler 所以就先用着volley框架了。 这里我先分析下volley框架的简单网络请求的源码。
使用volley请求网络数据的简单过程:
requestqueue queue = volley.newrequestqueue(this); //实例化一个请求队列 google推荐写一个单例类 获取唯一一个队列 stringrequest request = new stringrequest(request.method.post, url1, new response.listener<string>() { @override public void onresponse(string response) { toast.maketext(mainactivity.this, "success"+response, toast.length_short).show(); } }, new response.errorlistener() { @override public void onerrorresponse(volleyerror error) { toast.maketext(mainactivity.this, "失败了"+error.getmessage(), toast.length_short).show(); } }){ @override protected map<string, string> getparams() throws authfailureerror { //重写这个函数提交参数 也可以重写一个request实现这个方法 map<string,string> params = new hashmap<>(); params.put(aaa+"name","1233555"); //参数 return params; } }; queue.add(request);
请求的处理在newrequestqueue的时候就开始执行了 只不过那时候请求队列中还没有请求 所以阻塞了 当 add的方法执行时 才开始真正请求网络
所以我们先来看 queue.add(request) 方法
public <t> request<t> add(request<t> 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; } // insert request into stage if there's already a request with the same cache key in flight. synchronized (mwaitingrequests) { string cachekey = request.getcachekey(); if (mwaitingrequests.containskey(cachekey)) { //判断当前正在被处理并可以缓存的请求中是否包含该请求的key 如果包含说明已经有一个相同的请求 那么就加入到其中 // there is already a request in flight. queue up. queue<request<?>> stagedrequests = mwaitingrequests.get(cachekey); if (stagedrequests == null) { stagedrequests = new linkedlist<request<?>>(); } stagedrequests.add(request); mwaitingrequests.put(cachekey, stagedrequests); if (volleylog.debug) { volleylog.v("request for cachekey=%s is in flight, putting on hold.", cachekey); } } else { //如果不包含 加入一个空的请求到 暂存队列中 然后加入到缓存队列中 // insert 'null' queue for this cachekey, indicating there is now a request in // flight. mwaitingrequests.put(cachekey, null); mcachequeue.add(request); } return request; } }
分析add方法 首先加入到mcurrentrequests集合中 这个集合存放所有这个队列所处理的请求 然后判断这个请求是否需要缓存,如果不需要缓存,那么直接加入mnetworkqueue队列中等待处理即可,如果需要那么最终加入到mcachequeue队列中,因为requestqueue在处理请求时总会先处理缓存的任务,在处理缓存时如果第一次处理没有缓存还是会加入mnetworkqueue队列中处理,如果有缓存那么就直接获取缓存了,之后判断当前的请求中是否有相同的请求,如果有的话那么就把这个请求加入到暂存集合中,如果没有那么就加入一个空的到请求到暂存队列中,用来以后判断是否有和这个请求相同的请求,然后加入缓存队列中即可。
然后我们来看requstqueue的创建过程
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; //用户代理为app包名+版本号 } catch (namenotfoundexception e) { } if (stack == null) { //如果没传入httpstack 那么采用下述默认的 这里可以自行重写扩展httpstack 体现了该框架的高扩展性 if (build.version.sdk_int >= 9) { //如果sdk版本高于2.3 采用hurlstack 内部是httpurlconnection实现 stack = new hurlstack(); } else { //如果版本低于2.3 采用httpclientstack // 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; }
</pre><pre code_snippet_id="1680121" snippet_file_name="blog_20160512_5_2241745" name="code" class="java">public requestqueue(cache cache, network network, int threadpoolsize) { //构造函数 会创建默认的executordelivery 用于回调 this(cache, network, threadpoolsize, new executordelivery(new handler(looper.getmainlooper()))); }
requestqueue的创建过程也比较简单 根据sdk版本号判断使用httpurlconnection还是httpclient 因为在2.3之前 httpurlconnection有一个重大的bug 所以使用httpclient代替,而httpurlconnection体积小 支持gzip压缩和缓存,并且速度相对httpclient快 并逐渐优化 所以选择httpurlconnection 之后根据创建的network 创建requestqueue队列 然后开启即可
之后我们查看 queue的start方法
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); //创建一个缓存调度器 是一个线程 start后执行run方法 mcachedispatcher.start(); // create network dispatchers (and corresponding threads) up to the pool size. for (int i = 0; i < mdispatchers.length; i++) { //默认会有4个networkdispatcher 为了提高效率 执行networkqueue里的request networkdispatcher networkdispatcher = new networkdispatcher(mnetworkqueue, mnetwork, mcache, mdelivery); mdispatchers[i] = networkdispatcher; networkdispatcher.start(); } }
这个方法 先执行缓存调度器线程然后执行4个网络工作调度器线程,因为在缓存调度器中 会判断是否缓存过,如果缓存过并且没过期,就直接复用缓存的,不把任务加入networdqueue中 所以下面的network调度器线程就会取不到请求而阻塞,不会执行,而如果没有缓存,缓存调度器线程中就会把请求加入network队列中,下面的network调度器就会取到该请求并执行了
我们仔细看一下cachedispatcher线程的源码:
run方法的代码比较长 我们分开来看 先看第一部分:
@override public void run() { if (debug) volleylog.v("start new dispatcher"); process.setthreadpriority(process.thread_priority_background); //设置线程的优先级 值为10 // make a blocking call to initialize the cache. mcache.initialize(); //初始化一下缓存 while (true) { try { // get a request from the cache triage queue, blocking until // at least one is available. final request<?> request = mcachequeue.take(); //从缓存队列取出一个请求 如果没有则会阻塞 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"); //添加缓存miss标记 // cache miss; send off to the network dispatcher. mnetworkqueue.put(request); //换区缓存失败 添加到network中等待请求 continue; } // if it is completely expired, just send it to the network. if (entry.isexpired()) { //判断缓存是否过期了 如果过期了 那么就添加到network中等待请求 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( //把读取到的缓存内容解析成response对象 new networkresponse(entry.data, entry.responseheaders)); request.addmarker("cache-hit-parsed"); //添加标记 if (!entry.refreshneeded()) { //如果缓存不需要刷新 直接调用 mdelivery.postresponse方法 在其中会回调request的listener接口 // completely unexpired cache hit. just deliver the response. mdelivery.postresponse(request, response); } else { //如果需要刷新 把请求加入mnetworkqueue中 等待请求 // 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. mdelivery.postresponse(request, response, new runnable() { @override public void run() { try { mnetworkqueue.put(request); } catch (interruptedexception e) { // not much we can do about this. } } }); } } catch (interruptedexception e) { // we may have been interrupted because it was time to quit. if (mquit) { return; } continue; } } }
上面代码的具体过程也很简单 首先从缓存请求队列取出一个请求,在缓存中看看有没有该请求的缓存,如果没有 那么 请求放入network调度器中 等待调用 如果有 也分几种情况 如果获取到的是空,放入network 如果过期 放入 network 如果不需要刷新 就直接从缓存获取响应信息并解析 然后用mdelivery回调接口即可 如果需要刷新 放入netword队列等待调用。。。
我们再来看看networkdispatcher 线程的代码就可以了 类似于cachedispatcher的代码:
@override public void run() { process.setthreadpriority(process.thread_priority_background); //设置优先级 10 while (true) { long starttimems = systemclock.elapsedrealtime(); //获取请求执行开始时间 request<?> request; 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 { request.addmarker("network-queue-take"); // if the request was cancelled already, do not perform the // network request. if (request.iscanceled()) { request.finish("network-discard-cancelled"); continue; } addtrafficstatstag(request); // perform the network request. networkresponse networkresponse = mnetwork.performrequest(request); //真正执行请求的函数 并返回响应 request.addmarker("network-http-complete"); // if the server returned 304 and we delivered a response already, // we're done -- don't deliver a second identical response. if (networkresponse.notmodified && request.hashadresponsedelivered()) { request.finish("not-modified"); continue; } // parse the response here on the worker thread. response<?> response = request.parsenetworkresponse(networkresponse); //解析响应 request.addmarker("network-parse-complete"); // write to cache if applicable. // todo: only update cache metadata instead of entire record for 304s. if (request.shouldcache() && response.cacheentry != null) { //如果需要缓存 那么把响应的信息存入缓存中 mcache.put(request.getcachekey(), response.cacheentry); request.addmarker("network-cache-written"); } // post the response back. 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); //回调错误接口 } } }
networkdispatcher 线程的执行过程 先从 networkdispatch中获取一个请求 然后判断 是否取消了 如果没有 那么就执行network的performrequest方法 执行http请求,这个函数内部才是真正的请求数据 ,请求后 根据设置的shouldcache标志 判断是否放入缓存中 之后回调一些接口方法 即可 这样就完成了一个请求
最后我们看一看network类mnetwork.performrequest(request)方法是如何提交请求的吧 代码比较长 但是不难:
@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()); //根据缓存添加请求头 httpresponse = mhttpstack.performrequest(request, headers); //调用httpstack的方法请求网络 statusline statusline = httpresponse.getstatusline(); int statuscode = statusline.getstatuscode(); responseheaders = convertheaders(httpresponse.getallheaders()); //获取响应头 // handle cache validation. if (statuscode == httpstatus.sc_not_modified) { //如果为304 读取的缓存 entry entry = request.getcacheentry(); //查看以前是否缓存过 if (entry == null) { //如果以前缓存的为空 那么 说明上次缓存的请求也为空 直接返回response 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); } // some responses such as 204s do not have content. we must check. if (httpresponse.getentity() != null) { //不是304的情况
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 (sockettimeoutexception e) { attemptretryonexception("socket", request, new timeouterror()); } catch (connecttimeoutexception e) { attemptretryonexception("connection", request, new timeouterror()); } catch (malformedurlexception e) { throw new runtimeexception("bad url " + request.geturl(), e); } catch (ioexception e) { int statuscode = 0; networkresponse networkresponse = null; if (httpresponse != null) { statuscode = httpresponse.getstatusline().getstatuscode(); } else { throw new noconnectionerror(e); } volleylog.e("unexpected response code %d for %s", statuscode, request.geturl()); if (responsecontents != null) { networkresponse = new networkresponse(statuscode, responsecontents, responseheaders, false, systemclock.elapsedrealtime() - requeststart); if (statuscode == httpstatus.sc_unauthorized || statuscode == httpstatus.sc_forbidden) { attemptretryonexception("auth", request, new authfailureerror(networkresponse)); } else { // todo: only throw servererror for 5xx status codes. throw new servererror(networkresponse); } } else { throw new networkerror(networkresponse); } } }
然后看 httpstack的 请求代码:
@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; }
这个函数中主要是httpurlconnection的使用 添加头在 connection.addrequestproperty方法中 添加参数需要获取流 然后写入参数 下面这个函数中有介绍 假设是post方式:
case method.post: connection.setrequestmethod("post"); addbodyifexists(connection, request); break;
private static void addbodyifexists(httpurlconnection connection, request<?> request) throws ioexception, authfailureerror { byte[] body = request.getbody(); if (body != null) { connection.setdooutput(true); connection.addrequestproperty(header_content_type, request.getbodycontenttype()); dataoutputstream out = new dataoutputstream(connection.getoutputstream()); out.write(body); out.close(); } }
将body写入到流中 就可以 参数的封装在 body中
public byte[] getbody() throws authfailureerror { map<string, string> params = getparams(); if (params != null && params.size() > 0) { return encodeparameters(params, getparamsencoding()); } return null; } getparams方法 是request需要重写的一个方法 返回值就是参数的map集合 [java] view plain copy 在code上查看代码片派生到我的代码片 private byte[] encodeparameters(map<string, string> params, string paramsencoding) { stringbuilder encodedparams = new stringbuilder(); 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('&'); } return encodedparams.tostring().getbytes(paramsencoding); } catch (unsupportedencodingexception uee) { throw new runtimeexception("encoding not supported: " + paramsencoding, uee); } }
这个函数就是按照一定规则拼接字符串参数即可 然后 就可以提交参数了
最后介绍下这个框架主要的几个类、成员及他们作用:
requestqueue 用来处理请求的队列,请求都放在这个类中 调用start方法 开始处理请求
mcache 请求的缓存,当提交了一个请求 并且此请求需要缓存时,会放入这个缓存中
mnetwork 单纯用于提交网络请求的接口 只有一个提交请求的方法 需要传入一个httpstack来完成请求的提交
mdelivery 用于请求响应后的 接口回调等功能
mdispatchers network调度器线程数组 包含4个对象处理请求 目的是为了提高效率 当没有缓存可以获取或者已经过期 需要刷新时 会调用这个线程的run方法 如果没有 则阻塞
mcachedispatcher 缓存调度器线程 处理已经缓存了的请求 如果没有缓存 则将请求放入 networkqueue 等待调用
以上就是本文的全部内容,希望对大家学习android volley框架有所帮助。
上一篇: JVM进阶教程之字段访问优化浅析