Android NoHttp 框架作者带你看源码(一)

版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003

现在市场的Http框架很多,比如我们熟知的NoHttp、Retrofit、Volley、android-async-http等上层框架,HttpURLConnection、OkHttp、HttpClient等底层框架,今天不说孰好孰坏,今天我带大家来分析NoHttp的源码,教大家如何来看NoHttp的源码。

今天我们由浅到深,深入浅出的分析第一个模块:请求模块

支持作者可以去Github上关注NoHttp源代码:https://github.com/yanzhenjie/NoHttp,。

更多NoHttp相关文章请看NoHttp博客专栏:http://blog.csdn.net/column/details/nohttp.html

NoHttp 源码该如何入手

想要看一个框架的源码,首先就要学会怎么用,最起码基础用法要会,也不必深入或者精通。那么我们先来看下NoHttp的异步请求和同步请求的简单用法。

切入NoHttp

作为一个优秀的网络框架,必定时提供了异步请求、同步请求两种方式给开发者的。

NoHttp的异步,我们先来看一个最简单实践:

RequestQueue queue = NoHttp.newRequestQueue();
Request<String> sReq = new StringRequest(url, POST);
...
queue.add(what sReq, new OnResponseListener() {
    public void onStart(int what){}
    public void onFinish(int what){}
    public void onSucceed(int what, Response<String> response){}
    public void onFailed(int what, Response<String> response){}
});

来做个简单的分析,首先是new了一个队列,然后创建了一个String的Request,之后把这个请求添加到队列,等待请求网络后响应,完事儿,就是这么简单。

NoHttp同步请求,再来看一个最简单实例:

Request<String> request = new StringRequest(url, GET);
Response<String> response = NoHttp.startRequestSync(request);

这个更简单了,创建了一个请求,然后调用NoHttp同步请求的方法拿到请求结果。

从哪里开始看起

看了异步请求和同步请求后我们来分析一下,应该从哪里开始看NoHttp的源码。

从上面的代码中看到,异步请求和同步请求最大的区别是:异步请求需要用队列发送,用Listener接受请求结果,而同步请求是直接请求到结果,那么最大的可能就是异步请求是建立在同步请求这个基础上的。所以我们直接看异步请求,在开始之前我已经画好了几个图。

NoHttp流程图

这个图是NoHttp异步请求时,从主线程调用网络到拿到结果的流程图,这里非常有必要对这些类做个说明。

名称 类型 默认实现类 说明
RequestQueue - 请求队列
RequestDispatcher - 轮询队列的线程
IRestParser 接口 RestParser 解析网络结果,针对IParserRequest
IRestProtocol 接口 RestProtocol 按照Http协议请求网络处理请求结果,针对IProtocolRequest
BasicConnection 抽象类 - 提供基础的访问网络能力,针对IBaseRequst,更改底层为OkHttp时只需要改它
IBaseRequst 接口 BaseRequest 提供通用的网络设置、参数设置、Cookie、重试机制,下载和请求模块通用
IProtocolRequest 接口 ProtocolRequest 请求模块的协议,例如缓存机制
IParserRequest 接口 ParserRequest 泛型,提供解析请求结果ByteArray为泛型
Request 接口 RestRequest 提供异步请求的参数记录功能,如what、Listener等

注意:其中BasicConnection是网络访问的基础类,所以它提供了请求网络、发送数据、上传文件、拿到服务器响应头、拿到服务器响应body、拿到服务器的流等基础网络功能。

NoHttp动态流程图

如果上面的图还是不够显然,我们可以结合下面的图再看一边动态流程图就更加显而易懂了:

从上面的两张图可以看到,任何一个请求都是从主线程开始发起,先把Request添加到RequestQueue(队列)中,队列此时会启动子线程,再由ReuqestDispatcher(请求分发者)把队列中请求按照一定的顺序分发给子线程去执行网络操作,ReuqestDispatcher拿到结果后,用Handler发送到主线程,这个过程就是NoHttp的整个异步请求的过程。

撸源码

接着上面的分析,我们现在开始撸源码,那么先入手的入手的就是RequestQueue了。

RequestQueue

我们先来看看NoHttp怎么创建RequestQueue

public static RequestQueue newRequestQueue() {
    return newRequestQueue(DEFAULT_REQUEST_THREAD_SIZE);
}

public static RequestQueue newRequestQueue(int threadPoolSize) {
    return newRequestQueue(DiskCacheStore.INSTANCE, threadPoolSize);
}

public static RequestQueue newRequestQueue(Cache<CacheEntity> cache, int threadPoolSize) {
    return newRequestQueue(RestProtocol.getInstance(cache), threadPoolSize);
}

public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) {
    return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize);
}

public static RequestQueue newRequestQueue(IRestParser implRestParser, int threadPoolSize) {
    RequestQueue requestQueue = new RequestQueue(implRestParser, threadPoolSize);
    requestQueue.start();
    return requestQueue;
}

这里有5个方法可以创建ReuqestQueue,但是每个方法最终都会来到最后方法,最后一个方法需要两个参数,第一个参数是IRestParser,也就是响应解析者,第二个参数是threadPoolSize,也就是队列中启动的线程的数量,创建好ReuqestQueue后,调用start()方法启动队列,这时就可以往队列中添加请求了。

既然到这里我们就必须去看看ReuqestQueue的源码了,我已经把原文中的英文注释改为中文了,方便大家理解:

public class RequestQueue {

    /**
     * 保存没有完成的请求,包括正在执行的请求。
     */
    private final BlockingQueue<Request<?>> mUnFinishQueue;
    /**
     * 保存没有执行的请求,不包括正在执行的请求。
     */
    private final BlockingQueue<Request<?>> mRequestQueue;
    /**
     * Http 请求结果解析器。
     */
    private final IRestParser mImplRestParser;

    /**
     * 线程池。
     */
    private RequestDispatcher[] mDispatchers;

    public RequestQueue(IRestParser implRestParser, int threadPoolSize) {
        mImplRestParser = implRestParser;
        mDispatchers = new RequestDispatcher[threadPoolSize];
    }

    /**
     * 启动线程池,轮询请求队列。
     */
    public void start() {
        stop();
        for (int i = 0; i < mDispatchers.length; i++) {
            RequestDispatcher networkDispatcher = new RequestDispatcher(..., mImplRestParser);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    /**
     * 1. 添加一个请求到队列,如果队列中请求数没有满,则会立即执行,否则等待前面的请求执行完成后再执行。
     * 2. 在真正添加到队列前检查当前要添加的请求是否在队列中,如果重复添加则无任何操作。
     */
    public <T> void add(int what, Request<T> request, OnResponseListener<T> responseListener) {
        ...
    }

    /**
     * 没有开始执行的请求数量。
     */
    public int unStartSize() {}

    /**
     * 没有完成的请求数量,包括正在执行的请求。
     */
    public int unFinishSize() {}

    /**
     * 停止队列,使三个线程停止,不进行轮询队列。
     */
    public void stop() {}

    /**
     * 根绝sign取消所有用sign打标的请求。
     */
    public void cancelBySign(Object sign) {}

    /**
     * 取消队列中所有请求。
     */
    public void cancelAll() {}
}

结合上文所说的,这里贴出了所有方法和最重要的实现。在构造方法中,创建了一个threadPoolSize大小的RequestDispatcher数组,调用start()的时候为数组的每一个index赋值一个真正的RequestDispatcher线程,并启动这个线程去轮询Queue,同时在创建RequestDispatcher的时候把我们创建队列的时候的IRestParser穿进去了,说明真正的调用网络还是在RequestDispatcher这个子线程中,具体怎么调用并处理接着看下文。关于队列和线程的知识点这里不讲太多,只讲NoHttp在这里的设计,关于更多多线程、队列和任务优先级顺序的知识,请看严振杰的视频讲解

RequestDispatcher

我们上面说到RequestDispatcher是一个线程,而且它在轮询队列里面的请求,根绝开始的流程图它也负责发送请求结果到主线程,所以它应该NoHttp异步请求中最重要的类之一了,我们不能放过它:

public class RequestDispatcher extends Thread {
    // 线程轮询是否停止。
    private boolean mQuit = false;

    // 停止线程轮询。
    public void stop() {
        mQuit = true;
    }

    ...

    @Override
    public void run() {
        while (!mQuit) { // 线程轮询没有停止则无限循环。
            final Request<?> request;
            try {
                // 轮询请求,如果队列中请求为空则阻塞。
                request = mRequestQueue.take();
            } catch (InterruptedException e) {
                continue;
            }

            if (request.isCanceled()) {// 如果请求已经被取消则进行下一个请求。
                continue;
            }

            // 记录下what和listener,等待结果回调。
            final int what = request.what();
            final OnResponseListener<?> responseListener = request.responseListener();

            request.start(); // 标志请求开始。
            // 发送请求开始回调到主线程。
            final ThreadPoster startThread = new ThreadPoster(what, responseListener);
            startThread.onStart();
            PosterHandler.getInstance().post(startThread);

            // 用IRestParser发送请求,并解析结果,这就是上文中说过的。
            Response<?> response = mIRestParser.parserRequest(request);

            // 执行完请求,从队列移除。
            mUnFinishQueue.remove(request);

            // 发送请求完成回调到主线程。
            final ThreadPoster finishThread = new ThreadPoster(what, responseListener);
            finishThread.onFinished();
            PosterHandler.getInstance().post(finishThread);
            request.finish();// 标志网络请求已经完成。

            // 发送请求结果到主线程,如果请求在请求过程中被取消,则不发送。
            if (request.isCanceled())
                Logger.d(request.url() + " finish, but it‘s canceled.");
            else {
                final ThreadPoster responseThread = new ThreadPoster(what, responseListener);
                responseThread.onResponse(response);
                PosterHandler.getInstance().post(responseThread);
            }
        }
    }

    private class ThreadPoster implements Runnable {

        public static final int COMMAND_START = 0;
        public static final int COMMAND_RESPONSE = 1;
        public static final int COMMAND_FINISH = 2;

        private final int what;
        private final OnResponseListener responseListener;

        private int command;
        private Response response;

        public ThreadPoster(int what, OnResponseListener<?> responseListener) {
            this.what = what;
            this.responseListener = responseListener;
        }

        public void onStart() {
            this.command = COMMAND_START;
        }

        public void onResponse(Response response) {
            this.command = COMMAND_RESPONSE;
            this.response = response;
        }

        public void onFinished() {
            this.command = COMMAND_FINISH;
        }

        @Override
        public void run() {
            if (responseListener != null) {
                if (command == COMMAND_START) // 开始回调。
                    responseListener.onStart(what);
                else if (command == COMMAND_FINISH) // 结束回调。
                    responseListener.onFinish(what);
                else if (command == COMMAND_RESPONSE) {// 请求结果回调。
                    if (response.isSucceed()) {// 如果成功,回调成功方法。
                        responseListener.onSucceed(what, response);
                    } else { // 如果失败,回调失败方法。
                        responseListener.onFailed(what, response);
                    }
                }
            }
        }
    }
}

如果你认真看了我的注释结合我的解释也许就会明白很多了。

RequestDispatcher在线程没有被标志停止的情况下会一直循环调用Queue.take()轮询队列中的请求,如果线程中没有请求,由于Queue.take()的特性,这个子线程会处于阻塞状态,当然这不会使APP卡顿,因为它在子线程。当它每拿到一个Request先会判断请求是否被取消,如果是取消了的则去轮询下一个请求,如果没有取消会利用Handler发送一个Runnable回调Listener.onStart()方法通知主线程请求开始了,接着去执行Request,其中最重要的一句就是调用IRestParse的地方,这里正好印证了我们最开始讲的,在子线程中利用IRestParse去发送请求并解析结果成泛型:

// 用IRestParser发送请求,并解析结果,这就是上文中说过的。
Response<?> response = mIRestParser.parserRequest(request);

执行完请求后再次利用Handler发送一个Runnable回调Listener.onFinish()方法通知主线程网络请求执行完了:

// 发送请求完成回调到主线程。
final ThreadPoster finishThread = new ThreadPoster(what, responseListener);
finishThread.onFinished();
PosterHandler.getInstance().post(finishThread);
request.finish();

// 发送请求结果到主线程,如果请求在请求过程中被取消,则不发送。
if (request.isCanceled())
    Logger.d(request.url() + " finish, but it‘s canceled.");
else {
    final ThreadPoster responseThread = new ThreadPoster(what, responseListener);
    responseThread.onResponse(response);
    PosterHandler.getInstance().post(responseThread);
}

可以看到如果回调了onStart()则一定会回调onFinish(),所以我们在OnResponseListeneronStartonFinish非常适合做一个Dilaog的显示和关闭。

因为请求网络可能耗时比较长或者网络不好超时等因素,用户可能会取消这个请求,所以我们看到在回调了onFinish()后在发送结果到主线程前NoHttp又做了一个请求是否被取消的判断,综上所述我们得出一系列结论:

  1. 我们可以在onStart()时显示了一个Dialog,那么我们可以在onFinish()关闭Dialog
  2. 如果请求开始之前就取消了,那这个请求不会被执行。
  3. 如果请求已经开始了,但是被中途取消,onFinish()还是会被回调,所以在这里关闭Dialog是非常合适的;同时请求若是被中途取消,那么也一定不会回调onSucceed()onFailed()了,这里就涉及到取消请求了,用户退出当前页面会不会发生内存泄漏的问题,答案自然是不会。

IRestParser和它的实现类RestParser

看完了RequestQueueRequestDispatcher后发现,里面除了和主线程的交互外,就是和网络的交互和结果如果解析了。

由上可以看到在创建队列、启动子线程到真正的执行请求,最终都需要响应解析者IRestParser,我们来看下它的源代码:

public interface IRestParser {
    /**
     * 请求网络并且解析结果。
     */
    <T> Response<T> parserRequest(IParserRequest<T> request);
}

根据源码和流程图的结构来看,它负责针对IRestRequest解析出数据并返回Response,数据肯定是来自网络,因此它也怎么从网络拿到数据并解析成我们想要的泛型结果就是重点了。但它只是一个接口,想知道它是如何实现的,就得看NoHttp为它提供的默认实现类怎么请求网络并返回数据了。

我们选中IRestParser,键盘上按下Ctrl + T(也许你的快捷键跟我不一样)就看到了接口的实现类,同时NoHttp为它提供的默认实现在创建队列的时候可以看到RestProtocol

public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) {
    return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize);
}

我们可以看到源码是用RestParser.getInstance(iRestProtocol)单例模式从RestParser获取IRestParser的实例,因此下面接着就要撸RestParser的源码了:

public class RestParser implements IRestParser {

    private static RestParser _INSTANCE;

    private final IRestProtocol mIRestProtocol;

    public static IRestParser getInstance(IRestProtocol implRestConnection) {
        synchronized (RestParser.class) {
            if (_INSTANCE == null)
                _INSTANCE = new RestParser(implRestConnection);
            return _INSTANCE;
        }
    }

    private RestParser(IRestProtocol iRestProtocol) {
        this.mIRestProtocol = iRestProtocol;
    }

    @Override
    public <T> Response<T> parserRequest(IParserRequest<T> request) {
        long startTime = SystemClock.elapsedRealtime(); // 记录请求开始的时间。

        // 调用Http协议处理器IRestProtocol分析Request并完成网络请求拿到Response结果。
        ProtocolResult httpResponse = mIRestProtocol.requestNetwork(request);

        boolean isFromCache = httpResponse.isFromCache(); // 是否来自缓存的结果。
        Headers responseHeaders = httpResponse.responseHeaders(); // 服务器相应头。
        Exception exception = httpResponse.exception(); // 请求时是否发生异常。

        T result = null;
        byte[] responseBody = httpResponse.responseBody(); // 服务器响应内容。
        if (exception == null) {
            try {
                // 反调用IParserRequest去解析泛型结果。
                result = request.parseResponse(responseHeaders, responseBody);
            } catch (Throwable e) {
                exception = new ParseError("Parse data error: " + e.getMessage());
            }
        }
        return new RestResponse<T>(request, isFromCache, responseHeaders,
            result, SystemClock.elapsedRealtime() - startTime, exception);
    }

}

这里的单例和构造方法不多说,如果这都看不懂的话你可能不太适合做编程,我们接着分析parserRequest()方法。

parserRequest()中是真正的请求网络,这里可以理解为同步请求的开始,所以看到这里不是有了灵感,NoHttp的同步请求一并看了,这也是NoHttp设计上的优点,同步请求更多的内容看本文最后。继续说,parserRequest()开始时记录了请求开始时间,然后立刻调用网络协议处理器IRestProtocol请求网络:

// 调用Http协议处理器IRestProtocol分析Request并完成网络请求拿到Response结果。
ProtocolResult httpResponse = mIRestProtocol.requestNetwork(request);

完成网络协议请求后拿到ByteArray后调用IParserRequest.parseResponse()解析泛型结果,所以具体解析成什么结果是由IParserRequest决定的(题外话:因此我们在自定义请求时,继承RestRequest需要重写parseResponse解析结果),解析完ByteArray成泛型的结果后把异常、是否来自缓存和结果封装成RestResponse返回,剩下的事情就是上文中分析过的RequestDispatcher处理了,如果你忘记了RequestDispatcher的逻辑,可以回头去看看。

其实到这里这个过程就完了,但是大家肯定要吐槽我了:你特喵的扯什么淡呢,连一点点和http相关的都没看到。嗯这就对了,我们上面看到和Http相关的的网络请求是通过IRestProtoco这个接口发出的,so,接下来该撸IRestProtocol这个接口了。

IRestProtocol和它的实现类RestProtocol

IRestProtocol文章开头就说道了,它是一个接口,没啥好分析的,直接打开看源码:

public interface IRestProtocol {
    /**
     * 解析Http协议相关参数,完成网络请求。
     */
    ProtocolResult requestNetwork(IProtocolRequest request);
}

根据源码和流程图的结构来看,它负责针对IProtocolRequest解析Http协议参数,并发起网络请求返回请求的结果。因此我们需要关心的地方是它如何处理Http协议的参数,so我们接着看NoHttp为它提供的默认实现类怎么请求网络并返回数据了。

引入IRestProtocol

  • 选中IRestProtocol,键盘上按下Ctrl + T(也许你的快捷键跟我不一样)就看到了接口的实现类,或者回去看创建RequestQueue时,获取IRestParser的单例时:
public static RequestQueue newRequestQueue(Cache<CacheEntity> cache, int threadPoolSize) {
    return newRequestQueue(RestProtocol.getInstance(cache), threadPoolSize);
}

public static RequestQueue newRequestQueue(IRestProtocol iRestProtocol, int threadPoolSize) {
    return newRequestQueue(RestParser.getInstance(iRestProtocol), threadPoolSize);
}

public static RequestQueue newRequestQueue(IRestParser implRestParser, int threadPoolSize) {
    RequestQueue requestQueue = new RequestQueue(implRestParser, threadPoolSize);
    requestQueue.start();
    return requestQueue;
}

这里可以看到最终创建队列时需要一个IRestParser,而在上一个方法获取IRestParser时需要一个IRestProtocol接口作为参数,在最上面的方法中看到IRestProtocol的是由RestProtocol.getInstance(cache)生成的,所以NoHttp为IRestProtocol提供的默认实现类是RestProtocol

ProtocolResult requestNetwork(IProtocolRequest request);

由于这个类代码有点多,我们一点点的来分析,首先就是IRestProtocol必须要实现的方法requestNetwork()

public class RestProtocol extends BasicConnection implements IRestProtocol {

  @Override
  public ProtocolResult requestNetwork(IProtocolRequest request) {
    // 处理Http缓存头。
    CacheMode cacheMode = request.getCacheMode();
    String cacheKey = request.getCacheKey();
    CacheEntity cEntity = mCache.get(cacheKey);

    ProtocolResult result;
    // 根据缓存模式处理。
    switch (cacheMode) {
        case ONLY_READ_CACHE:// 只读缓存.
            if (cEntity == null) {
                return new ProtocolResult(null, null, true, new NotFoundCacheError("没找到缓存"));
            } else {
                return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
            }
        case ONLY_REQUEST_NETWORK:// 仅仅请求网络.
            result = getHttpResponse(request);
            break;
        case NONE_CACHE_REQUEST_NETWORK:// 先读缓存,没缓存再请求网络。
            if (cEntity == null) {
                result = getHttpResponse(request);// 请求网络。
            } else {
                return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
            }
            break;
        case REQUEST_NETWORK_FAILED_READ_CACHE:// 请求网络失败后读缓存。
            if (cEntity != null)
                setRequestCacheHeader(request, cEntity); // 缓存存在时设置缓存头。
            result = getHttpResponse(request);// 请求网络。
            break;
        default:// 按照Http标准协议,304。
            if (cEntity != null) {
                // 缓存没失效直接返回。
                if (cEntity.getLocalExpire() > System.currentTimeMillis()) {
                    return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
                }
                // 缓存失败但存在,时设置缓存头。
                setRequestCacheHeader(request, cEntity);
            }
            result = getHttpResponse(request);// 请求网络。
            break;
    }
    return handleResponseCache(request, cEntity, result); // 处理响应数据,缓存协议等。
}

大家千万不要忘记了看类的继承关系,RestProtocol实现了IRestProtocol接口,并且继承BasicConnection这个基类。

要注意的两点:

第一:NoHttp的几种缓存模式:

  • 只读缓存
  • 只请求缓存
  • 先读缓存,没有缓存再请求网络
  • 先请求网络,请求网络失败再读取缓存
  • 按照Http标准协议,重定向缓存机制

第二:此方法中出现的几个未知的方法:

  • setRequestCacheHeader(request, cacheEntity); // 为请求设置缓存头。
  • getHttpResponse(request); // 真正的请求网络。
  • handleResponseCache(request, cEntity, result); // 处理响应结果、缓存等。

setRequestCacheHeader(request, cacheEntity);

这个方法是在请求之前为Request添加和缓存协议有关的请求头,这是Http标准协议的内容,更多的协议讲解请看这篇博客:http://blog.csdn.net/yanzhenjie1003/article/details/50878323

private void setRequestCacheHeader(IProtocolRequest request, CacheEntity cacheEntity) {
    if (cacheEntity == null) { // 如果缓存为空,移除缓存相关请求头。
        request.headers().remove("If-None-Match");
        request.headers().remove("If-Modified-Since");
    } else { // 缓存不为空则添加相关请求头。
        Headers headers = cacheEntity.getResponseHeaders();
        String eTag = headers.getETag();
        if (eTag != null) {
            request.headers().set("If-None-Match", eTag);
        }
        long lastModified = headers.getLastModified();
        if (lastModified > 0) {
            request.headers().set("If-Modified-Since", HeaderUtil.formatMillisToGMT(lastModified));
        }
    }
}

这里需要解释一下Http的缓存协议了。如果服务器支持http标准的缓存,当我们第一次请求服务器的时候,服务器会在响应头中添加Last-Modified头,这个头的含义是服务器最后一次修改响应body的时间,如果服务器支持设置缓存有效期,还会添加一个E-Tag的头,这个头是可以理解为响应body的一个tag。客户端接受到响应头到,会把这两个相应头保存起来,在第二次请求的时候会首先检查响应body是否过期,如果没有过期则直接使用上次的响应body,也就是我们在requestNetwork()方法中看到的:

default:// 按照Http标准协议,304。
    if (cEntity != null) {
        // 缓存没失效直接返回。
        if (cEntity.getLocalExpire() > System.currentTimeMillis()) {
            return new ProtocolResult(cEntity.getResponseHeaders(), cEntity.getData(), true, null);
        }
        // 缓存失败但存在,时设置缓存头。
        setRequestCacheHeader(request, cEntity);
    }
    result = getHttpResponse(request);// 请求网络。
    break;

如果响应body过期,那么客户端应该重新请求服务器,并且在请求头中添加两个请求头,第一个是If-None-Match头,这个头的值应该是上次请求服务器时,服务器返回的E-Tag,第二个要添加的请求头是If-Modified-Since,这个头的值应该是上次服务器返回的Last-Modified的值。

服务器接受到请求后会用If-None-MatchIf-Modified-Since和服务器现在E-TagLast-Modified做对比,判断客户端上次的缓存是否过期。如果没有过期则返回304响应码,并且不会向响应body中写入内容,客户端接受到这个响应后,判断响应码是304时应该从客户端拿上次的缓存数据;如果过期则返回200段的响应码,并且会向响应body中写入新的内容,客户端接受到这个响应后,判断响应码是200段,应该重新从响应body中读取内容。

这就是Http标准协议中的缓存协议内容,NoHttp做到了完美的支持。

getHttpResponse(request);

/**
 * 真正的请求网络。
 */
private ProtocolResult getHttpResponse(IProtocolRequest request) {
    byte[] responseBody = null;
    Connection connection = getConnection(request); // 从BasicConnection拿到网络连接和响应内容。

    Headers responseHeaders = connection.responseHeaders();
    Exception exception = connection.exception();
    if (exception == null) {
        // 判断是否有响应内容,比如304响应码就没有body。
        if (hasResponseBody(request.getRequestMethod(), responseHeaders.getResponseCode()))
            try {
                // 把服务器响应body转为ByteArray。
                responseBody = IOUtils.toByteArray(connection.serverStream());
            } catch (IOException e) {// IOException.
                exception = e;
            }
    }
    IOUtils.closeQuietly(connection); // 关闭服务器流。
    // 返回响应内容。
    return new ProtocolResult(responseHeaders, responseBody, exception != null, exception);
}

其中Connection connection = getConnection(request); 。里面才是真正的建立网络请求,发送数据、上传文件等操作,我将会新开一篇博客专门来讲BasicConnection类。

handleResponseCache(request, cEntity, result);

private ProtocolResult handleResponseCache(IProtocolRequest request, CacheEntity localCacheEntity, ProtocolResult httpResponse) {
    boolean isFromCache = false;
    Headers responseHeaders = httpResponse.responseHeaders();
    byte[] responseBody = httpResponse.responseBody();
    Exception exception = httpResponse.exception();

    CacheMode cacheMode = request.getCacheMode();
    int responseCode = responseHeaders.getResponseCode();
    if (exception == null) {// 没有发生异常,请求成功。
        if (responseCode == 304) {
            isFromCache = true;
            if (localCacheEntity == null) { // 服务器304,但本地没有缓存,兼容服务器bug。
                responseBody = new byte[0];
            } else {
                // 更新相应头信息。
                localCacheEntity.getResponseHeaders().setAll(responseHeaders);
                responseHeaders = localCacheEntity.getResponseHeaders();
                localCacheEntity.setLocalExpire(HeaderUtil.getLocalExpires(responseHeaders));

                // 读取本地缓存数据。
                responseBody = localCacheEntity.getData();
            }
        } else if (responseBody != null) {// 响应码非304,并且有响应内容。
            if (localCacheEntity == null) { // 如果本地没缓存,则根据http协议进行缓存。
                // 解析服务器缓存头。
                localCacheEntity = HeaderUtil.parseCacheHeaders(...);
                // 这里解析出来也许为空,因为服务器可能不允许缓存:no-cache,no-store。
            } else {
                // 如果本地已经有缓存,则更新数据。
                localCacheEntity.getResponseHeaders().setAll(responseHeaders);
                localCacheEntity.setLocalExpire(HeaderUtil.getLocalExpires(responseHeaders));
                localCacheEntity.setData(responseBody);
            }
        }
        if (localCacheEntity != null) {
            // 解析响应头后服务区允许缓存或者已经有缓存,更新数据库缓存数据。
            mCache.replace(request.getCacheKey(), localCacheEntity);
        }
    } else if (cacheMode == CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE
        && localCacheEntity != null) {
        // 如果请求失败,但是缓存模式是请求失败后读缓存,那么读缓存数据。
        exception = null;
        isFromCache = true;
        responseHeaders = localCacheEntity.getResponseHeaders();
        responseBody = localCacheEntity.getData();
    }
    return new ProtocolResult(responseHeaders, responseBody, isFromCache, exception);
}

这个类的作用是根据服务器响应码、服务器响应头、本地数据等处理和缓存相关的逻辑,大家可以认真看下源码和逻辑。主要做的事情是,解析服务器响应码、响应内容,根据标准的http协议来决定是否缓存数据,或者更新上次的缓存数据。

总结

今天我们的请求模块到这里就解析完了,看完文章的同学也许还会有不明白的地方,欢迎大家在博客下方留言。

还剩下BasicConnection,因为它的能力请求网络、发送数据、上传文件、拿到服务器响应头、拿到服务器响应body、拿到服务器的流等内容较多,我会专门开一篇新的博客专门讲这个类。

  1. RequestQueue是请求队列,在主线程执行,主线程可以添加Request到请求队列中,等待子线程轮询读取Request去执行。
  2. RequestDispatcher是请求分发者,它是一个子线程,作用是轮询请求队列,拿到请求后调用IRestParser执行请求结果,负责发送开始请求、结束请求、把结果发送到主线程等重要任务。

    2.1 RequestQueueRequestDispatcher可以接受的请求类是Request接口,Request接口继承自IParserRequest接口,Request接口负责记录响应监听器Listener和监听器的what对象。

  3. IRestParser是请求结果解析者,它在子线程中运行,负责把从网络请求回来的ByteArray解析成开发者想要的泛型对象。

    3.1 IRestParser接口带有泛型,可以接受到的请求类是IParserRequest接口,IParserRequest接口继承自IRestPtotocol接口,IParserRequest接口负责具体实现解析成开发者想要的泛型对象,由IRestParser具体调用。

  4. IRestProtocol是协议处理器,它在子线程中运行,负责从网络请求结果,包括Header、Body(ByteArray),并且在请求到结果前后处理缓存协议(其他协议待加)等。

    4.1 IRestProtocol接口可以接受的请求类是IProtocolRequest接口,IProtocolRequest接口继承自IBasicRequest接口,IProtocolRequest接口负责记录客户端自定义Http协议等,例如缓存模式,缓存Key等。

  5. BasicConnection是基础的网络访问类,它的能力:能力请求网络、发送数据、上传文件、拿到服务器响应头、拿到服务器响应body、拿到服务器的流等,是下载模块和请求模块的通用网络请求模块。

    5.1 BasicConnection可以接受的请求类是IBasicRequest接口,IBasicRequest接口记录了基础的请求属性、通用的网络设置、参数设置、Cookie、重试机制,下载和请求模块通用。

版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003

时间: 2024-10-24 18:35:38

Android NoHttp 框架作者带你看源码(一)的相关文章

NoHttp和OkHttp的无缝结合 NoHttp框架作者带你看源码(二)

NoHttp和OkHttp的无缝结合 NoHttp框架作者带你看源码(二) 版权声明:转载必须注明本文转自严振杰的博客: http://blog.csdn.net/yanzhenjie1003 上一次带大家分析了NoHttp源码,知道我们可以替换NoHttp的底层为其他任何库,例如OkHttp.HttpURLConnection.HttpClient,那今天就带领大家一步步来实现替换NoHttp的底层为OkHttp. NoHttp源码分析的博客:http://blog.csdn.net/yanz

Android EventBus框架(二)之源码简单解析

上一篇,我们基本知道了EventBus的使用步骤,接下来先简单研究一下其中的源码.在分析源码之前,我们先回顾一下Java反射的知识点: JAVA反射机制 基本定义: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. Sun为提供的关于反射机制中的类: java.lang.Class; java.lang.reflect.Construct

[jvm解析系列][十四]动态代理和装饰模式,带你看源码深入理解装饰模式和动态代理的区别。

不知道大家知不知道设计模式中有一种叫做装饰,举一个简单的例子. 一天一个年轻领导小王讲话:咳咳,我们一定要xxx抓紧xxxx学习xxx的精神!好,今天的会议结束! 然后有一个老领导李同志接过来说:那个我在补充两点,个别同志xxx,一定要注意xxx.好散会. 然后另一天小王同志又在讲话:xxx两手都要抓,xxxx一定要注意. 这个时候老周同志出来了:嗯,小王讲的很好,我还有几点要补充xxxx. 那么很明显,小王同志的讲话方法不是很让人满意,那么老李领导或者老周领导可以接过来继续装修一下.其实这就是

Android开源框架Universal-Image-Loader学习三——UsingFreqLimitedMemoryCache源码阅读

 Universal-Image-Loader的内存缓存策略 1. 只使用的是强引用缓存 LruMemoryCache(这个类就是这个开源框架默认的内存缓存类,缓存的是bitmap的强引用) 2.使用强引用和弱引用相结合的缓存有 UsingFreqLimitedMemoryCache(如果缓存的图片总量超过限定值,先删除使用频率最小的bitmap) LRULimitedMemoryCache(这个也是使用的lru算法,和LruMemoryCache不同的是,他缓存的是bitmap的弱引用)

Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima

[转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识. 也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的 功能,ListView就不能滚动了?为什么图片轮播器里的图

(转) Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客开始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用Ima

Android AsyncTask完全解析,带你从源码的角度彻底理解

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.

[学习总结]7、Android AsyncTask完全解析,带你从源码的角度彻底理解

我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制.之前我也写过了一篇文章从源码层面分析了Android的异步消息处理机制,感兴趣的朋友可以参考 Android Handler.Message完全解析,带你从源码的角度彻底理解 . 不过为了更加方便我们在子线程中更新UI元素,Android从1.5版本就引入了一个AsyncTask类,使用它就可以非常灵活方便地从子线程切换到UI线程,我们本篇文章的主角也就正是它了. Asyn