美文网首页学习之鸿蒙&Android
Android知名三方库OKHttp(二) - 手写简易版

Android知名三方库OKHttp(二) - 手写简易版

作者: 信仰年輕 | 来源:发表于2021-06-23 22:59 被阅读0次

源代码
GitHub源代码

本文目标

手写实现okhttp简易流程(仅供学习)

基本使用

    public void click(View view) {
        //1.1创建okHttpClient
        OkHttpClient okHttpClient = new OkHttpClient();
        //1.2创建RequestBody对象
        RequestBody requestBody = new RequestBody()
                .type(RequestBody.FORM)
                .addParam("userName", "beijing")
                .addParam("password", "123456");
        //1.3创建Request对象
        Request request = new Request
                .Builder()
                .post(requestBody)
                .headers(getHeaderParams())
                .url("https://api.devio.org/as/user/login")
                .builder();

        //2.把Request对象封装成call对象
        Call call = okHttpClient.newCall(request);

        //3.发起异步请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("TAG", e.toString());
                show(e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String string = response.string();
                Log.e("TAG", string);
                show(string);
            }
        });
    }
  • 1.创建okHttpClient和创建Request对象(配置的请求信息封装)
  • 2.把Request对象封装成call对象
  • 3.发起同步请求或异步请求
    我们从okHttpClient开始实现

1.1OkHttpClient

首先先看OkHttpClient类

//1.1创建okHttpClient
 OkHttpClient okHttpClient = new OkHttpClient();
/**
 * Author: 信仰年轻
 * Date: 2021-06-23 17:30
 * Email: hydznsqk@163.com
 * Des: OkHttp客户端对象
 */
public class OkHttpClient {

    Dispatcher dispatcher;

    public OkHttpClient(Builder builder) {
        this.dispatcher = builder.dispatcher;
    }

    public OkHttpClient() {
        this(new Builder());
    }

    public Call newCall(Request request) {
        return RealCall.newCall(request, this);
    }

    public static class Builder {
        Dispatcher dispatcher;

        public Builder() {
            dispatcher = new Dispatcher();
        }

        public OkHttpClient builder() {
            return new OkHttpClient(this);
        }
    }
}

可以看出来该对象中只是持有Dispatcher 对象和创建Call对象的newCall方法

1.2RequestBody

然后因为我这里用的是form表单,所以我们先看下RequestBody 是怎么写的

        //1.2创建RequestBody对象
        RequestBody requestBody = new RequestBody()
                .type(RequestBody.FORM)
                .addParam("userName", "beijing")
                .addParam("password", "123456");

具体如下

/**
 * Author: 信仰年轻
 * Date: 2021-06-23 17:31
 * Email: hydznsqk@163.com
 * Des:请求体
 */
public class RequestBody {

    // 表单格式
    public static final String FORM = "multipart/form-data";

    // 参数,文件
    private final HashMap<String, Object> params;
    private String boundary = createBoundary();
    private String type;
    private String startBoundary = "--" + boundary;
    private String endBoundary = startBoundary + "--";

    public RequestBody() {
        params = new HashMap<>();
    }

    private String createBoundary() {
        return "OkHttp"+ UUID.randomUUID().toString();
    }
    // 都是一些规范
    public String getContentType() {
        return type + ";boundary = " + boundary;
    }

    // 多少个字节要给过去,写的内容做一下统计
    public long getContentLength() {
        long length=0;
        Set<Map.Entry<String, Object>> entries = params.entrySet();

        for(Map.Entry<String, Object> entry:entries){
            String key = entry.getKey();
            Object value = entry.getValue();
            if(value instanceof String){
                String text = getText(key, (String) value);
                Log.e("TAG",text);
                length+=text.getBytes().length;
            }
        }

        if(params.size()!=0){
            length+=endBoundary.getBytes().length;
        }
        return length;
    }

    //写内容
    public void onWriteBody(OutputStream outputStream) throws IOException {
        Set<Map.Entry<String, Object>> entries = params.entrySet();
        for(Map.Entry<String, Object> entry:entries){
            String key = entry.getKey();
            Object value = entry.getValue();
            if(value instanceof String){
                String text = getText(key, (String) value);
                outputStream.write(text.getBytes());
            }
        }
        if(params.size()!=0){
            outputStream.write(endBoundary.getBytes());
        }
    }

    /**
     startBoundary + "\r\n"
     Content-Disposition; form-data; name = "pageSize"
     Context-Type: text/plain


     1
     */
    private String getText(String key, String value) {
        return startBoundary+"\r\n"+
                "Content-Disposition: form-data; name = \""+key+"\"\r\n"+
                "Context-Type: text/plain\r\n"+
                "\r\n"+
                value+
                "\r\n";
    }

    public RequestBody addParam(String key, String value) {
        params.put(key, value);
        return this;
    }

    public RequestBody type(String type) {
        this.type = type;
        return this;
    }

}

都是一些固定格式,然后也是通过IO流的方式去写内容

1.3Request

        //1.3创建Request对象
        Request request = new Request
                .Builder()
                .post(requestBody)
                .headers(getHeaderParams())
                .url("https://api.devio.org/as/user/login")
                .builder();
/**
 * Author: 信仰年轻
 * Date: 2021-06-23 17:31
 * Email: hydznsqk@163.com
 * Des:把配置的请求信息封装成Request对象,包含url,method请求方式,headers头信息,RequestBody请求体
 */
public class Request {

    final String url;//url
    final Method method;//请求方式
    final Map<String, String> headers;//头信息
    final RequestBody requestBody;//请求体,用于post请求

    private Request(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = builder.headers;
        this.requestBody = builder.requestBody;
    }

    public static class Builder {
        String url;//url
        Method method;//请求方式
        Map<String, String> headers;//头信息
        RequestBody requestBody;//请求体,用于post请求

        public Builder() {
            method = Method.GET;
            headers = new HashMap<>();
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder get() {
            method = Method.GET;
            return this;
        }

        public Builder post(RequestBody body) {
            method = Method.POST;
            this.requestBody = body;
            return this;
        }

        public Builder headers(String key, String value) {
            headers.put(key, value);
            return this;
        }

        public Builder headers( Map<String, String> map) {
            headers.putAll(map);
            return this;
        }
        public Request builder() {
            return new Request(this);
        }
    }
}

老样子,把配置的请求信息封装成Request对象,包含url,method请求方式,headers头信息,RequestBody请求体,运用了builder设计模式

2.Call

        //2.把Request对象封装成call对象
        Call call = okHttpClient.newCall(request);

把Request传给okHttpClient
client.newCall(request);调用进去,会发现是RealCall在调用

public class OkHttpClient {
  public Call newCall(Request request) {
       return RealCall.newCall(request, this);
  }
}

下面的是顶层Call接口

/**
 * Author: 信仰年轻
 * Date: 2021-06-23 17:28
 * Email: hydznsqk@163.com
 * Des: 请求的Call顶层接口
 */
public interface Call {
    /**
     * 发起异步请求
     * @param callback
     */
    void  enqueue(Callback callback);

    /**
     * 发起同步请求
     * @return
     */
    Response execute();
}

下面是RealCall 真正发起请求的Call对象

/**
 * Author: 信仰年轻
 * Date: 2021-06-23 17:31
 * Email: hydznsqk@163.com
 * Des: 真正发起请求的Call对象
 */
public class RealCall implements Call {

    private OkHttpClient client;
    private Request originalRequest;

    public RealCall(Request originalRequest,OkHttpClient client) {
        this.client = client;
        this.originalRequest = originalRequest;
    }

    public static Call newCall(Request request, OkHttpClient okHttpClient) {
        return new RealCall(request,okHttpClient);
    }

    //异步请求
    @Override
    public void enqueue(Callback callback) {
        //异步交给线程池
        AsyncCall asyncCall = new AsyncCall(callback);
        client.dispatcher.enqueue(asyncCall);
    }

    //同步请求
    @Override
    public Response execute() {
        return null;
    }

    final class AsyncCall extends NamedRunnable{

        Callback callback;

        public AsyncCall(Callback callback){
            this.callback=callback;
        }

        @Override
        protected void execute() {
            // 来这里,开始访问网络 Request -> Response
            Log.e("TAG","execute");
            // 基于 HttpUrlConnection , OkHttp = Socket + okio(IO)
            final Request request = originalRequest;
            try {
                URL url = new URL(request.url);

                HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

                if(urlConnection instanceof HttpsURLConnection){
                    HttpsURLConnection httpsURLConnection = (HttpsURLConnection) urlConnection;
                    // https 的一些操作
                    // httpsURLConnection.setHostnameVerifier();
                    // httpsURLConnection.setSSLSocketFactory();
                }
                // urlConnection.setReadTimeout();

                // 写东西
                urlConnection.setRequestMethod(request.method.name);
                urlConnection.setDoOutput(request.method.doOutput());
                //Post方式不能缓存,需手动设置为false
                urlConnection.setUseCaches(false);

                RequestBody requestBody = request.requestBody;
                if(requestBody != null){
                    // 头信息
                    urlConnection.setRequestProperty("Content-Type",requestBody.getContentType());
                    urlConnection.setRequestProperty("Content-Length",Long.toString(requestBody.getContentLength()));
                }

                //自己定义的头信息 header,里面有token和boarding-pass
                Map<String, String> headers = request.headers;
                if(headers!=null){
                    Set<Map.Entry<String, String>> entries = headers.entrySet();
                    for(Map.Entry<String, String> entry:entries){
                        urlConnection.setRequestProperty(entry.getKey(), entry.getValue());//设置请求头
                    }
                }

                urlConnection.connect();

                // 写内容
                if(requestBody != null){
                    requestBody.onWriteBody(urlConnection.getOutputStream());
                }

                int statusCode = urlConnection.getResponseCode();
                if(statusCode == 200) {
                    InputStream inputStream = urlConnection.getInputStream();
                    Response response = new Response(inputStream);
                    callback.onResponse(RealCall.this,response);
                }else{
                    InputStream inputStream = urlConnection.getInputStream();
                    Response response = new Response(inputStream);
                    callback.onFailure(RealCall.this,new IOException(response.string()));
                }
                // 进行一些列操作,状态码 200
            } catch (IOException e) {
                callback.onFailure(RealCall.this,e);
            }
        }
    }
}

上述代码是用了HttpURLConnection 这种很原始的方式进行网络请求的,因为这里是okhttp的简易版,在类的结构上是一致的,只是底层引擎不同,真正的OkHttp 是Socket + okio(IO)实现的

3.Call的异步请求

        //3.发起异步请求
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Log.e("TAG", e.toString());
                show(e.toString());
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String string = response.string();
                Log.e("TAG", string);
                show(string);
            }
        });

其实就是调用RealCall的enqueue方法

    //异步请求
    @Override
    public void enqueue(Callback callback) {
        //异步交给线程池
        AsyncCall asyncCall = new AsyncCall(callback);
        client.dispatcher.enqueue(asyncCall);
    }

接下来我们来看下线程池

/**
 * Author: 信仰年轻
 * Date: 2021-06-23 17:35
 * Email: hydznsqk@163.com
 * Des: 分发器,主要是用线程池
 */
public class Dispatcher {

    private ExecutorService executorService;

    /**
     * 用线程池开启异步请求,然后就会AsyncCall中的run方法
     * @param call
     */
    public void enqueue(RealCall.AsyncCall call) {
        executorService().execute(call);
    }

    /**
     * 创建线程池,而且是单例
     */
    public synchronized ExecutorService executorService() {
        if (executorService == null) {
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "okhttp");
                    thread.setDaemon(false);
                    return thread;
                }
            });
        }
        return executorService;
    }
}

在onResponse方法中看到了可以把Response通过IO流转成字符串

/**
 * Author: 信仰年轻
 * Date: 2021-06-23 17:31
 * Email: hydznsqk@163.com
 * Des: 响应,通过inputStream解析服务器返回来的数据为String
 */
public class Response {
    private final InputStream inputStream;// Skin

    public Response(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    //IO流解析成字符串
    public String string() {
        return convertStreamToString(inputStream);
    }

    public String convertStreamToString(InputStream is) {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }
}

基本上到这里核心代码就写完了,具体的可以参考Demo

相关文章

网友评论

    本文标题:Android知名三方库OKHttp(二) - 手写简易版

    本文链接:https://www.haomeiwen.com/subject/hoeiyltx.html