概述
之前在讨论组里听到许多讨论okhttp的话题,可见okhttp是一个相对成熟的解决方案,看到android4.4后网络访问的源码中HttpURLConnection已经替换成okhttp实现了,所以当时考虑来支持下,最近项目中新版应用效果良好,所以抽空来谈谈,顺便记录下.Volley源码解析,Okhttp介绍,Rxjava处理异步调度(主要是针对一些网络响应后,需要对数据进行一些复杂操作,耗时在子线程,更新在主线程,所以补了一个异步机制).源码感兴趣的可以关注我github开源项目:https://github.com/gongjr/ScanData
/** * RequestQueue类作为volley的核心类,可以说是连接请求与响应的桥梁 * 一个拥有线程池的请求队列 * 调用add()分发,将添加一个用于分发的请求 * worker线程从缓存或网络获取响应,然后将该响应提供给主线程 * * A request dispatch queue with a thread pool of dispatchers. * Calling {@link #add(Request)} will enqueue the given Request for dispatch, * resolving from either cache or network on a worker thread, and then delivering * a parsed response on the main thread. */ public class RequestQueue { /** * 任务完成的回调接口 * Callback interface for completed requests. */ public static interface RequestFinishedListener<T> { /** Called when a request has finished processing. */ public void onRequestFinished(Request<T> request); } /** * 原子操作的Integer的类,线程安全的加减操作接口,记录队列中当前的请求数目 * Used for generating monotonically-increasing sequence numbers for requests. */ private AtomicInteger mSequenceGenerator = new AtomicInteger(); /** * 等候缓存队列,重复请求集结map,每个queue里面都是相同的请求。 * 为什么需要这个map呢?map的key其实是request的url,如果我们有多个请求的url都是相同的,也就是说请求的资源是相同的, * volley就把这些请求放入一个队列,在用url做key将队列放入map中。 * 因为这些请求都是相同的,可以说结果也是相同的。那么我们只要获得一个请求的结果,其他相同的请求,从缓存中取就可以了。 * 所以等候缓存队列的作用就是,当其中的一个request获得响应,我们就将这个队列放入缓存队列mCacheQueue中,让这些request去缓存获取结果就好了。 * 这是volley处理重复请求的思路。 * Staging area for requests that already have a duplicate request in flight. * <ul> * <li>containsKey(cacheKey) indicates that there is a request in flight for the given cache * key.</li> * <li>get(cacheKey) returns waiting requests for the given cache key. The in flight request * is <em>not</em> contained in that list. Is null if no requests are staged.</li> * </ul> */ private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>(); /** * The set of all requests currently being processed by this RequestQueue. A Request * will be in this set if it is waiting in any queue or currently being processed by * any dispatcher. * 队列当前拥有的所有请求的集合 * 请求在队列中或者正被调度中,都会在这个集合里 * 也就是下面mCacheQueue缓存队列与mNetworkQueue网络队列的总和 */ private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>(); /** * 缓存队列 * The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>(); /** * 网络队列,PriorityBlockingQueue-阻塞优先级队列 * 线程安全,有阻塞功能,也就是说当队列里面没有东西的时候,线程试图从队列取请求,这个线程就会阻塞 * 根据Request实现compareTo接口可知:请求优先级高,则在队列中优先级高, * 如果优先级相同,则根据mSequence序列号,来判断,先进先出 * The queue of requests that are actually going out to the network. */ private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>(); /** * 默认用于调度的线程池数目 * Number of network request dispatcher threads to start. */ private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 3; /** * 缓存接口,面向对象的思想,把缓存看成一个实体,此处只声明缓存实体的实现规则接口, * 具体实现的时候,可以是用默认实现了Cache接口的DiskBasedCache类实体,也可以自定义扩展 * Cache interface for retrieving and storing responses. */ private final Cache mCache; /** * 网络接口,面向对象的思想,把网络看成一个实体,此处只声明网络实体的实现规则接口, * 具体实现的时候,可以是默认实现了Network接口的BasicNetwork类实体,也可以自定义扩展 * Network interface for performing requests. */ private final Network mNetwork; /** * 响应分发器接口,负责把响应发给对应的请求,分发器存在的意义主要是为了耦合更加低并且能在主线程中操作UI * 将网络响应的分发操作看成一个实体,此处声明实体的规则接口 * 具体实现时:可以是默认实现ResponseDelivery接口的ExecutorDelivery实体 * Response delivery mechanism. */ private final ResponseDelivery mDelivery; /** * 网络调度器数组,NetworkDispatcher继承了Thread类,其本质是多个线程, * 所有线程都将被开启进入死循环,不断从mNetworkQueue网络队列取出请求,然后去网络Network请求数据 * The network dispatchers. */ private NetworkDispatcher[] mDispatchers; /** * 缓存调度器CacheDispatcher继承了Thread类,本质是一个线程,这个线程将会被开启进入一个死循环, * 不断从mCacheQueue缓存队列取出请求,然后去缓存Cache中查找结果,如果没有请求则阻塞 * The cache dispatcher. */ private CacheDispatcher mCacheDispatcher; /** * 任务完成监听器队列 * 这个队列保留了很多监听器,这些监听器都是监听RequestQueue请求队列的,而不是监听单独的某个请求。 * RequestQueue中每个请求完成后,都会回调这个监听队列里面的所有监听器。 * 这是RequestQueue的统一管理的体现 */ private List<RequestFinishedListener> mFinishedListeners = new ArrayList<RequestFinishedListener>(); /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create * @param delivery A ResponseDelivery interface for posting responses and errors */ public RequestQueue(Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) { mCache = cache; mNetwork = network; mDispatchers = new NetworkDispatcher[threadPoolSize]; mDelivery = delivery; } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests * @param threadPoolSize Number of network dispatcher threads to create */ public RequestQueue(Cache cache, Network network, int threadPoolSize) { this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper()))); } /** * Creates the worker pool. Processing will not begin until {@link #start()} is called. * * @param cache A Cache to use for persisting responses to disk * @param network A Network interface for performing HTTP requests */ public RequestQueue(Cache cache, Network network) { this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); } /** * 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(); } } /** * Stops the cache and network dispatchers. */ public void stop() { if (mCacheDispatcher != null) { mCacheDispatcher.quit(); } for (int i = 0; i < mDispatchers.length; i++) { if (mDispatchers[i] != null) { mDispatchers[i].quit(); } } } /** * Gets a sequence number. */ public int getSequenceNumber() { return mSequenceGenerator.incrementAndGet(); } /** * Gets the {@link Cache} instance being used. */ public Cache getCache() { return mCache; } /** * 一个简单的过滤接口,在cancelAll()方法里面被使用 * A simple predicate or filter interface for Requests, for use by * {@link RequestQueue#cancelAll(com.android.volley.RequestQueue.RequestFilter)}. */ public interface RequestFilter { public boolean apply(Request<?> request); } /** * 根据过滤器规则,取消相应请求 * Cancels all requests in this queue for which the given filter applies. * @param filter The filtering function to use */ public void cancelAll(RequestFilter filter) { synchronized (mCurrentRequests) { for (Request<?> request : mCurrentRequests) { if (filter.apply(request)) { request.cancel(); } } } } /** * 根据标记取消相应请求 * Cancels all requests in this queue with the given tag. Tag must be non-null * and equality is by identity. */ public void cancelAll(final Object tag) { if (tag == null) { throw new IllegalArgumentException("Cannot cancelAll with a null tag"); } cancelAll(new RequestFilter() { @Override public boolean apply(Request<?> request) { return request.getTag() == tag; } }); } /** * Adds a Request to the dispatch queue. * @param request The request to service * @return The passed-in 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); } //设置请求序号,自动+1获取 // 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)) { // 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.为该key插入null,表明现在有一个请求在工作 mWaitingRequests.put(cacheKey, null); mCacheQueue.add(request); } return request; } } /** * Called from {@link Request#finish(String)}, indicating that processing of the given request * has finished. * * <p>Releases waiting requests for <code>request.getCacheKey()</code> if * <code>request.shouldCache()</code>.</p> */ <T> void finish(Request<T> request) { // Remove from the set of requests currently being processed. synchronized (mCurrentRequests) { mCurrentRequests.remove(request); } synchronized (mFinishedListeners) { for (RequestFinishedListener<T> listener : mFinishedListeners) { listener.onRequestFinished(request); } } if (request.shouldCache()) {//如果该请求要被缓存 synchronized (mWaitingRequests) { String cacheKey = request.getCacheKey(); Queue<Request<?>> waitingRequests = mWaitingRequests.remove(cacheKey); //移除等候缓存队列中的相同请求 if (waitingRequests != null) { if (VolleyLog.DEBUG) { VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", waitingRequests.size(), cacheKey); } // Process all queued up requests. They won't be considered as in flight, but // that's not a problem as the cache has been primed by 'request'. //这里需要注意,一个request完成以后,会将waitingRequests里面所有相同的请求, // 都加入到mCacheQueue缓存队列中,这就意味着,这些请求从缓存中取出结果就好了, // 这样就避免了频繁相同网络请求的开销。这也是Volley的亮点之一。 mCacheQueue.addAll(waitingRequests); } } } } public <T> void addRequestFinishedListener(RequestFinishedListener<T> listener) { synchronized (mFinishedListeners) { mFinishedListeners.add(listener); } } /** * Remove a RequestFinishedListener. Has no effect if listener was not previously added. */ public <T> void removeRequestFinishedListener(RequestFinishedListener<T> listener) { synchronized (mFinishedListeners) { mFinishedListeners.remove(listener); } } }
/** * OkHttp backed {@link HttpStack HttpStack} that does not * use okhttp-urlconnection * OkHttp的执行器,可用于替换原框架自带的HttpUrlConnection执行器 * 参考: https://gist.github.com/bryanstern/4e8f1cb5a8e14c202750 * https://gist.github.com/ceram1/8254f7a68d81172c1669 */ public class OkHttpStack implements HttpStack { private final OkHttpClient mClient; /** * Create a OkHttpStack with default OkHttpClient. */ public OkHttpStack() { this.mClient=new OkHttpClient(); } public OkHttpStack(OkHttpClient client) { this.mClient = client; } @Override public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { OkHttpClient client = mClient.clone(); int timeoutMs = request.getTimeoutMs(); client.setConnectTimeout(timeoutMs, TimeUnit.MILLISECONDS); client.setReadTimeout(timeoutMs, TimeUnit.MILLISECONDS); client.setWriteTimeout(timeoutMs, TimeUnit.MILLISECONDS); //根据volley的请求request,生成okhttp的builder构建器 //OkHttpUtils.get().url(url).build().execute(new CallBack());链式调用最后执行 //全部对应操作都在Builder中,所有操作完成后返回当前最新的builder com.squareup.okhttp.Request.Builder okHttpRequestBuilder = new com.squareup.okhttp.Request.Builder(); okHttpRequestBuilder.url(request.getUrl());//设置url KLog.i(request.getUrl().toString()); Map<String, String> headers = request.getHeaders();//添加request的header for (final String name : headers.keySet()) { okHttpRequestBuilder.addHeader(name, headers.get(name)); } //添加额外自定义header for (final String name : additionalHeaders.keySet()) { okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name)); } //根据volley请求的request,设置对应的builder,请求类型 setConnectionParametersForRequest(okHttpRequestBuilder, request); //执行后得到相应response com.squareup.okhttp.Request okHttpRequest = okHttpRequestBuilder.build(); Call okHttpCall = client.newCall(okHttpRequest);//不能被执行2次,每次new Response okHttpResponse = okHttpCall.execute(); //将得到的response,转换为BasicHttpResponse返回 return entityFromOkHttpResponse(okHttpResponse); } private static BasicHttpResponse entityFromOkHttpResponse(Response okHttpResponse) throws IOException { final int responseCode = okHttpResponse.code(); //先判断相应码,为-1,IO异常,直接抛出异常 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(parseProtocol(okHttpResponse.protocol()), okHttpResponse.code(), okHttpResponse.message()); BasicHttpResponse response = new BasicHttpResponse(responseStatus); //生成response相应的body实体 BasicHttpEntity entity = new BasicHttpEntity(); ResponseBody body = okHttpResponse.body(); entity.setContent(body.byteStream()); entity.setContentLength(body.contentLength()); entity.setContentEncoding(okHttpResponse.header("Content-Encoding"));//从头部获取ContentEncoding if (body.contentType() != null) { entity.setContentType(body.contentType().type()); } //设置ENTITY response.setEntity(entity); //遍历响应消息头部,将信息加入BasicHttpResponse里面 Headers responseHeaders = okHttpResponse.headers(); for (int i = 0, len = responseHeaders.size(); i < len; i++) { final String name = responseHeaders.name(i), value = responseHeaders.value(i); if (name != null) { response.addHeader(new BasicHeader(name, value)); } } KLog.i(response.getStatusLine()); return response; } @SuppressWarnings("deprecation") private static void setConnectionParametersForRequest(com.squareup.okhttp.Request.Builder builder, Request<?> request) throws IOException, AuthFailureError { switch (request.getMethod()) { case Request.Method.DEPRECATED_GET_OR_POST: // Ensure backwards compatibility. Volley assumes a request with a null body is a GET. byte[] postBody = request.getPostBody(); if (postBody != null) { builder.post(RequestBody.create(MediaType.parse(request.getPostBodyContentType()), postBody)); } break; case Request.Method.GET: builder.get(); break; case Request.Method.DELETE: builder.delete(); break; case Request.Method.POST: builder.post(createRequestBody(request)); break; case Request.Method.PUT: builder.put(createRequestBody(request)); break; case Request.Method.HEAD: builder.head(); break; case Request.Method.OPTIONS: builder.method("OPTIONS", null); break; case Request.Method.TRACE: builder.method("TRACE", null); break; case Request.Method.PATCH: builder.patch(createRequestBody(request)); break; default: throw new IllegalStateException("Unknown method type."); } } private static ProtocolVersion parseProtocol(final Protocol p) { switch (p) { case HTTP_1_0: return new ProtocolVersion("HTTP", 1, 0); case HTTP_1_1: return new ProtocolVersion("HTTP", 1, 1); case SPDY_3: return new ProtocolVersion("SPDY", 3, 1); case HTTP_2: return new ProtocolVersion("HTTP", 2, 0); } throw new IllegalAccessError("Unkwown protocol"); } private static RequestBody createRequestBody(Request r) throws AuthFailureError { final byte[] body = r.getBody(); if (body == null) return null; return RequestBody.create(MediaType.parse(r.getBodyContentType()), body); } }里面有一些是我当时加的备注,在来简单解析下,首先 HttpStack,作为网络传输层统一调用接口,就是在进行网络操作的时候调用HttpStack.performRequest(Request)
Request request = new Request.Builder() .url("https://github.com/gongjr") .header("User-Agent", "OkHttp Headers.java") .addHeader("Accept", "application/json; q=0.5") .addHeader("Accept", "application/vnd.github.v3+json") .build(); Response response = client.newCall(request).execute();细心的朋友可能注意到上面使用了
Call okHttpCall = client.newCall(okHttpRequest);每次new的方式,避免其被再次调用,因为retry机制在volley中,在上一层策略处理.下面简单的从okhttp源码来认识下okhttp库的构成
首先来说下,为何引入了Rxjava来进行异步调度?
/** * 执行请求任务,并返回一个RxJava的Observable类型 */ public static Observable<Result> getResult(final Request<?> request, Object tag) { addRequest(request,tag); return RxBus.getDefault().take(Result.class) .filter(new Func1<Result, Boolean>() { @Override public Boolean call(Result result) { return request.getUrl().equals(result.url); } }) .take(1); }第二步,在需要发起网络请求的地方,发起请求,获得被观察者,订阅,定义时间响应
/** * 获取数据,更新页面list列表 * 同时异步进行数据库缓存,页面提示缓存结果信息 */ public void getGoodsInfoWithAsyncResult(){ Observable<Result> observable=HttpController.getInstance().getQueryQcWithAsyncResult(getUpdateTime(QcKey), new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject data) { try { if (data.getString("msg").equals("ok")) { Gson gson = new Gson(); JSONObject datainfo = data.getJSONObject("data"); String info = datainfo.getString("info"); List<GoodsInfo> lHttpGoodsInfos = gson.fromJson(info, new TypeToken<List<GoodsInfo>>() { }.getType()); mGoodsInfoAdapter.refreshDate(lHttpGoodsInfos);//刷新页面list列表 } else { showShortTip("商品信息获取失败! " + data.getString("msg")); dismissLoadingDF(); } } catch (JSONException e) { showShortTip("商品信息数据解析失败! "); dismissLoadingDF(); e.printStackTrace(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { dismissLoadingDF(); showShortTip(VolleyErrorHelper.getMessage(error, mActivity)); } }); /** * 后台缓存数据处理耗时操作 */ Func1 saveCacheBackThread=new Func1<Result, ResultMap>() { @Override public ResultMap call(Result result) { JSONObject data=null; ResultMap lMap=new ResultMap(); try { String jsonString = new String(result.data, HttpHeaderParser.parseCharset(result.header, HTTP.UTF_8)); data=new JSONObject(jsonString); lMap.setMsg(data.getString("msg")); lMap.setErrcode(data.getString("errcode")); if (data.getString("msg").equals("ok")) { Gson gson = new Gson(); JSONObject datainfo = data.getJSONObject("data"); String info = datainfo.getString("info"); List<GoodsInfo> lHttpGoodsInfos = gson.fromJson(info, new TypeToken<List<GoodsInfo>>() { }.getType()); if (lHttpGoodsInfos != null && lHttpGoodsInfos.size() > 0) { DataSupport.saveAll(lHttpGoodsInfos); //数据库缓存数据,以及相关耗时操作 }else KLog.i("无数据"); }else { KLog.i("errcode:"+data.getString("errcode")+" msg:"+data.getString("msg")); } }catch (Exception e){ e.printStackTrace(); } return lMap; } }; /** * 主线程响应处理事件 */ Action1 updateMain=new Action1<ResultMap>() { @Override public void call(ResultMap data) { if (data.getErrcode().equals("0")) { showShortTip("商品信息缓存成功!"); } else { showShortTip("缓存失败!" + data.getMsg()); } } }; /** * 观察者对象,订阅被观察者,在事件响应时进行相关处理 */ Subscription subscription = observable .filter(new Func1<Result, Boolean>() { @Override public Boolean call(Result result) { return result.data != null; } }) .map(saveCacheBackThread)//订阅事件处理注册 .subscribeOn(Schedulers.io())// 指定 subscribe() 发生在 IO 线程 .observeOn(AndroidSchedulers.mainThread())// 指定 Subscriber 的回调发生在主线程 .subscribe(updateMain);//回调事件注册 }
//NetworkDispatcher网络调度器中执行网络操作,拿到返回 NetworkResponse networkResponse = mNetwork.performRequest(request); //基于Rxjava的订阅机制,执行异步响应分发 if (networkResponse.data != null) { RxBus.getDefault().post(new Result(request.getUrl(), networkResponse.headers, networkResponse.data)); } //正常的响应分发 ResponseDelivery.postResponse(request, response);这样就加入一个异步响应机制.用不到的话就无视,有特殊需求可以调用处理.