安卓移动架构10-手写Okhttp框架

移动架构10-手写Okhttp框架

官方地址:https://github.com/square/okhttp

为了学习Okhttp框架,手写一个框架。用法和Okhttp框架一样。

HttpClient client = new HttpClient.Builder().build();
Request request = new Request.Builder()
        .url("http://www.kuaidi100.com/query?type=yuantong&postid=11111111111")
        .build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, Throwable throwable) {
        showReult(showResult, throwable.getMessage());
    }

    @Override
    public void onResponse(Call call, Response response) {
        showReult(showResult, response.getBody());
    }
});

下面按照框架的使用步骤,来讲解手写Okhttp的思路。

一、创建HttpClient

HttpClient是框架的入口,使用建造者模式来创建。

主要作用是创建调度器、创建Http连接池、添加拦截器。

/**
 * 创建HttpClient
 */
public HttpClient build() {
    if (null == dispatcher) {
        dispatcher = new Dispatcher();
    }
    if (null == httpConnectionPool) {
        httpConnectionPool = new HttpConnectionPool();
    }
    return new HttpClient(this);
}

1.Dispatcher

Dispatcher:调度器,管理所有的请求任务

Dispatcher执行请求任务需要先检查最大请求数和相同的host的最大请求数,如果没超过就加入运行队列,使用ExecutorService来执行;如果超过了,就加入等待队列。

/**
 * 执行请求任务
 *
 * @param call
 */
public void enqueue(Call.AsyncCall call) {
    //不能超过最大请求数,同时执行相同的host请求不能超过最大host数
    Log.e("Dispatcher", "同时有:" + runningAsyncCalls.size());
    Log.e("Dispatcher", "host同时有:" + runningCallsForHost(call));
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
        Log.e("Dispatcher", "提交执行");
        runningAsyncCalls.add(call);
        executorService().execute(call);
    } else {
        Log.e("Dispatcher", "等待执行");
        readyAsyncCalls.add(call);
    }
}

当一个请求完成时,就执行等待队列中的任务。

/**
 * 执行等待队列中的请求
 */
private void promoteCalls() {
    //同时请求达到上限
    if (runningAsyncCalls.size() >= maxRequests) {
        return;
    }
    //没有等待执行请求
    if (readyAsyncCalls.isEmpty()) {
        return;
    }
    for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
        Call.AsyncCall call = i.next();
        //同一host同时请求为达上限
        if (runningCallsForHost(call) < maxRequestsPerHost) {
            i.remove();
            runningAsyncCalls.add(call);
            executorService().execute(call);
        }
        //到达同时请求上限
        if (runningAsyncCalls.size() >= maxRequests) {
            return;
        }
    }
}

2.HttpConnectionPool

HttpConnectionPool:连接池,用来缓存、复用Http连接。

每次添加Http连接时,都检查清理无效的连接。

/**
 * 检查需要移除的连接返回下次检查时间
 */
long cleanup(long now) {
    long longestIdleDuration = -1;
    synchronized (this) {
        for (Iterator i = connections.iterator(); i.hasNext(); ) {
            HttpConnection connection = i.next();
            //获得闲置时间 多长时间没使用这个了
            long idleDuration = now - connection.lastUsetime;
            //如果闲置时间超过允许
            if (idleDuration > keepAliveDuration) {
                connection.closeQuietly();
                i.remove();
                Log.e("Pool", "移出连接池");
                continue;
            }
            //获得最大闲置时间
            if (longestIdleDuration < idleDuration) {
                longestIdleDuration = idleDuration;
            }
        }
        //下次检查时间
        if (longestIdleDuration >= 0) {
            return keepAliveDuration - longestIdleDuration;
        } else {
            //连接池没有连接 可以退出
            cleanupRunning = false;
            return longestIdleDuration;
        }
    }
}

二、创建Request

Request是请求信息,主要作用是保存请求头、请求方法、请求地址、请求体。

/**
 * 请求信息
 */
public class Request {
    //请求头
    private Map headers;
    //请求方法
    private String method;
    //请求地址
    private HttpUrl url;
    //请求体
    private RequestBody body;
    ...
}    

HttpUrl:请求地址,用来保存请求协议、主机名、请求路径和端口号;

RequestBody:请求体,用来指定字符编码,拼接请求参数。

//拼接请求参数
public String body() {
    StringBuffer sb = new StringBuffer();
    for (Map.Entry entry : encodedBodys.entrySet()) {
        sb.append(entry.getKey())
                .append("=")
                .append(entry.getValue())
                .append("&");
    }
    if (sb.length() != 0) {
        sb.deleteCharAt(sb.length() - 1);
    }
    return sb.toString();
}

三、创建Call

Call:请求任务。用来保存Request、HttpClient和请求状态(是否执行过)。

public Call(HttpClient client, Request request) {
    this.client = client;
    this.request = request;
}

四、调用Call

1.调用过程

Call.enqueue(),实际是调度器在调用AsyncCall。

//异步调用
public Call enqueue(Callback callback) {
    //同一请求执行一次
    synchronized (this) {
        if (executed) {
            throw new IllegalStateException("Already Executed");
        }
        executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(callback));
    return this;
}

AsyncCall是一个Runnable,用来获取响应,执行回调函数。

@Override
public void run() {
    //是否已经通知过callback
    boolean signalledCallback = false;
    try {
        //获取响应
        Response response = getResponse();
        if (canceled) {
            signalledCallback = true;
            callback.onFailure(Call.this, new IOException("Canceled"));
        } else {
            signalledCallback = true;
            callback.onResponse(Call.this, response);
        }
    } catch (IOException e) {
        if (!signalledCallback) {
            callback.onFailure(Call.this, e);
        }
    } finally {
        client.dispatcher().finished(this);
    }
}

getResponse():获取响应,是使用责任链的模式层层调动的。可以理解为递归。

Response getResponse() throws IOException {
    ArrayList interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new InterceptorRetry());
    interceptors.add(new InterceptorHeaders());
    interceptors.add(new InterceptorConnection());
    interceptors.add(new InterceptorCallService());
    InterceptorChain interceptorChain = new InterceptorChain(interceptors, 0, this, null);
    return interceptorChain.proceed();
}

2.责任链

InterceptorChain:责任链,是框架的核心。责任链会调用拦截器,而每个拦截器都会调用下一个拦截器获取Response,调用之前做请求初始化操作,调用之后做响应处理操作。

/**
 * 调用拦截器,获取Response
 */
public Response proceed(HttpConnection connection) throws IOException {
    Interceptor interceptor = interceptors.get(index);
    InterceptorChain next = new InterceptorChain(interceptors, index + 1, call, connection);
    Response response = interceptor.intercept(next);
    return response;
}

InterceptorRetry:请求失败时,重新请求

public Response intercept(InterceptorChain chain) throws IOException {
    Log.e("interceprot", "重试拦截器....");
    Call call = chain.call;
    IOException exception = null;
    for (int i = 0; i < chain.call.client().retrys(); i++) {
        if (call.isCanceled()) {
            throw new IOException("Canceled");
        }
        try {
            //调用下一个拦截器
            Response response = chain.proceed();
            return response;
        } catch (IOException e) {
            exception = e;
        }
    }
    throw exception;
}

InterceptorHeaders:添加请求头信息

public Response intercept(InterceptorChain chain) throws IOException {
    Log.e("interceprot","Http头拦截器....");
    Request request = chain.call.request();
    Map headers = request.headers();
    headers.put(HttpCodec.HEAD_HOST, request.url().getHost());
    headers.put(HttpCodec.HEAD_CONNECTION, HttpCodec.HEAD_VALUE_KEEP_ALIVE);
    if (null != request.body()) {
        String contentType = request.body().contentType();
        if (contentType != null) {
            headers.put(HttpCodec.HEAD_CONTENT_TYPE, contentType);
        }
        long contentLength = request.body().contentLength();
        if (contentLength != -1) {
            headers.put(HttpCodec.HEAD_CONTENT_LENGTH, Long.toString(contentLength));
        }
    }
    //调用下一个拦截器
    return chain.proceed();
}

InterceptorConnection:通过Socket创建Http连接,使用连接池来获取Http连接

public Response intercept(InterceptorChain chain) throws IOException {
    Log.e("interceprot","连接拦截器....");
    Request request = chain.call.request();
    HttpClient client = chain.call.client();
    HttpUrl url = request.url();
    String host = url.getHost();
    int port = url.getPort();
    HttpConnection connection = client.connectionPool().get(host, port);
    if (null == connection) {
        connection = new HttpConnection();
    } else {
        Log.e("call", "使用连接池......");
    }
    connection.setRequest(request);
    try {
        //调用下一个拦截器
        Response response = chain.proceed(connection);
        if (response.isKeepAlive()) {
            client.connectionPool().put(connection);
        }
        return response;
    } catch (IOException e) {
        throw e;
    }
}

InterceptorCallService:发送Http请求

public Response intercept(InterceptorChain chain) throws IOException {
    Log.e("interceprot", "通信拦截器....");
    HttpCodec httpCodec = chain.httpCodec;
    HttpConnection connection = chain.connection;
    InputStream is = connection.call(httpCodec);
    //HTTP/1.1 200 OK 空格隔开的响应状态
    String statusLine = httpCodec.readLine(is);
    //读取响应头
    Map headers = httpCodec.readHeaders(is);
    //是否保持连接
    boolean isKeepAlive = false;
    if (headers.containsKey(HttpCodec.HEAD_CONNECTION)) {
        isKeepAlive = headers.get(HttpCodec.HEAD_CONNECTION).equalsIgnoreCase(HttpCodec
                .HEAD_VALUE_KEEP_ALIVE);
    }
    int contentLength = -1;
    if (headers.containsKey(HttpCodec.HEAD_CONTENT_LENGTH)) {
        contentLength = Integer.valueOf(headers.get(HttpCodec.HEAD_CONTENT_LENGTH));
    }
    //分块编码数据
    boolean isChunked = false;
    if (headers.containsKey(HttpCodec.HEAD_TRANSFER_ENCODING)) {
        isChunked = headers.get(HttpCodec.HEAD_TRANSFER_ENCODING).equalsIgnoreCase(HttpCodec
                .HEAD_VALUE_CHUNKED);
    }
    String body = null;
    if (contentLength > 0) {
        byte[] bytes = httpCodec.readBytes(is, contentLength);
        body = new String(bytes);
    } else if (isChunked) {
        body = httpCodec.readChunked(is);
    }
    String[] status = statusLine.split(" ");
    connection.updateLastUseTime();
    //返回响应结果
    return new Response(Integer.valueOf(status[1]), contentLength, headers, body, isKeepAlive);
}

3.创建Http连接

HttpConnection:使用Socket建立http连接,支持Https协议

/**
 * 创建socket,支持Https协议
 */
private void createSocket() throws IOException {
    if (null == socket || socket.isClosed()) {
        HttpUrl url = request.url();
        //需要sslsocket
        if (url.getProtocol().equalsIgnoreCase(HTTPS)) {
            socket = SSLSocketFactory.getDefault().createSocket();
        } else {
            socket = new Socket();
        }
        socket.connect(new InetSocketAddress(url.getHost(), url.getPort()));
        os = socket.getOutputStream();
        is = socket.getInputStream();
    }
}

HttpCodec:封装http协议,读写http请求

/**
 * 封装http协议,并发送http请求
 */
public void writeRequest(OutputStream os, Request request) throws IOException {
    StringBuffer protocol = new StringBuffer();

    //请求行
    protocol.append(request.method());
    protocol.append(SPACE);
    protocol.append(request.url().getFile());
    protocol.append(SPACE);
    protocol.append(VERSION);
    protocol.append(CRLF);

    //http请求头
    Map headers = request.headers();
    for (Map.Entry entry : headers.entrySet()) {
        protocol.append(entry.getKey());
        protocol.append(COLON);
        protocol.append(SPACE);
        protocol.append(entry.getValue());
        protocol.append(CRLF);
    }
    protocol.append(CRLF);

    //http请求体 如果存在
    RequestBody body = request.body();
    if (null != body) {
        protocol.append(body.body());
    }

    //发送http请求
    os.write(protocol.toString().getBytes());
    os.flush();
}

/**
 * 读取响应头
 */
public Map readHeaders(InputStream is) throws IOException {
    HashMap headers = new HashMap<>();
    while (true) {
        String line = readLine(is);
        //读取到空行 则下面的为body
        if (isEmptyLine(line)) {
            break;
        }
        int index = line.indexOf(":");
        if (index > 0) {
            String name = line.substring(0, index);
            // ": "移动两位到 总长度减去两个("\r\n")
            String value = line.substring(index + 2, line.length() - 2);
            headers.put(name, value);
        }
    }
    return headers;
}

/**
 * 按行读取http响应数据
 */
public String readLine(InputStream is) throws IOException {
    try {
        byte b;
        boolean isMabeyEofLine = false;
        //标记
        byteBuffer.clear();
        byteBuffer.mark();
        while ((b = (byte) is.read()) != -1) {
            byteBuffer.put(b);
            // 读取到/r则记录,判断下一个字节是否为/n
            if (b == CR) {
                isMabeyEofLine = true;
            } else if (isMabeyEofLine) {
                //上一个字节是/r 并且本次读取到/n
                if (b == LF) {
                    //获得目前读取的所有字节
                    byte[] lineBytes = new byte[byteBuffer.position()];
                    //返回标记位置
                    byteBuffer.reset();
                    byteBuffer.get(lineBytes);
                    //清空所有index并重新标记
                    byteBuffer.clear();
                    byteBuffer.mark();
                    String line = new String(lineBytes);
                    return line;
                }
                isMabeyEofLine = false;
            }
        }
    } catch (Exception e) {
        throw new IOException(e);
    }
    throw new IOException("Response Read Line.");
}

/**
 * 按字节数组读取http响应数据
 */
public byte[] readBytes(InputStream is, int len) throws IOException {
    byte[] bytes = new byte[len];
    int readNum = 0;
    while (true) {
        readNum += is.read(bytes, readNum, len - readNum);
        //读取完毕
        if (readNum == len) {
            return bytes;
        }
    }
}

/**
 * 按块读取http响应数据
 * 分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,
 * 允许HTTP由应用服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分。
 * 分块传输编码只在HTTP协议1.1版本(HTTP/1.1)中提供。
 */
public String readChunked(InputStream is) throws IOException {
    int len = -1;
    boolean isEmptyData = false;
    StringBuffer chunked = new StringBuffer();
    while (true) {
        //解析下一个chunk长度
        if (len < 0) {
            String line = readLine(is);
            line = line.substring(0, line.length() - 2);
            len = Integer.valueOf(line, 16);
            //chunk编码的数据最后一段为 0\r\n\r\n
            isEmptyData = len == 0;
        } else {
            //块长度不包括\r\n  所以+2将 \r\n 读走
            byte[] bytes = readBytes(is, len + 2);
            chunked.append(new String(bytes));
            len = -1;
            if (isEmptyData) {
                return chunked.toString();
            }
        }
    }
}

最后

代码:https://gitee.com/yanhuo2008/AndroidCommon/tree/master/ToolHttp

你可能感兴趣的:(安卓移动架构10-手写Okhttp框架)