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

源代码
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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容