Android知名三方庫OKHttp(二) - 手寫簡易版

源代碼
GitHub源代碼

本文目標(biāo)

手寫實現(xiàn)okhttp簡易流程(僅供學(xué)習(xí))

基本使用

    public void click(View view) {
        //1.1創(chuàng)建okHttpClient
        OkHttpClient okHttpClient = new OkHttpClient();
        //1.2創(chuàng)建RequestBody對象
        RequestBody requestBody = new RequestBody()
                .type(RequestBody.FORM)
                .addParam("userName", "beijing")
                .addParam("password", "123456");
        //1.3創(chuàng)建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.發(fā)起異步請求
        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.創(chuàng)建okHttpClient和創(chuàng)建Request對象(配置的請求信息封裝)
  • 2.把Request對象封裝成call對象
  • 3.發(fā)起同步請求或異步請求
    我們從okHttpClient開始實現(xiàn)

1.1OkHttpClient

首先先看OkHttpClient類

//1.1創(chuàng)建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 對象和創(chuàng)建Call對象的newCall方法

1.2RequestBody

然后因為我這里用的是form表單,所以我們先看下RequestBody 是怎么寫的

        //1.2創(chuàng)建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";

    // 參數(shù),文件
    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();
    }
    // 都是一些規(guī)范
    public String getContentType() {
        return type + ";boundary = " + boundary;
    }

    // 多少個字節(jié)要給過去,寫的內(nèi)容做一下統(tǒng)計
    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;
    }

    //寫內(nèi)容
    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流的方式去寫內(nèi)容

1.3Request

        //1.3創(chuàng)建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設(shè)計模式

2.Call

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

把Request傳給okHttpClient
client.newCall(request);調(diào)用進去,會發(fā)現(xiàn)是RealCall在調(diào)用

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 {
    /**
     * 發(fā)起異步請求
     * @param callback
     */
    void  enqueue(Callback callback);

    /**
     * 發(fā)起同步請求
     * @return
     */
    Response execute();
}

下面是RealCall 真正發(fā)起請求的Call對象

/**
 * Author: 信仰年輕
 * Date: 2021-06-23 17:31
 * Email: hydznsqk@163.com
 * Des: 真正發(fā)起請求的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() {
            // 來這里,開始訪問網(wǎng)絡(luò) 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方式不能緩存,需手動設(shè)置為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());//設(shè)置請求頭
                    }
                }

                urlConnection.connect();

                // 寫內(nèi)容
                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()));
                }
                // 進行一些列操作,狀態(tài)碼 200
            } catch (IOException e) {
                callback.onFailure(RealCall.this,e);
            }
        }
    }
}

上述代碼是用了HttpURLConnection 這種很原始的方式進行網(wǎng)絡(luò)請求的,因為這里是okhttp的簡易版,在類的結(jié)構(gòu)上是一致的,只是底層引擎不同,真正的OkHttp 是Socket + okio(IO)實現(xiàn)的

3.Call的異步請求

        //3.發(fā)起異步請求
        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);
            }
        });

其實就是調(diào)用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: 分發(fā)器,主要是用線程池
 */
public class Dispatcher {

    private ExecutorService executorService;

    /**
     * 用線程池開啟異步請求,然后就會AsyncCall中的run方法
     * @param call
     */
    public void enqueue(RealCall.AsyncCall call) {
        executorService().execute(call);
    }

    /**
     * 創(chuàng)建線程池,而且是單例
     */
    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流轉(zhuǎn)成字符串

/**
 * Author: 信仰年輕
 * Date: 2021-06-23 17:31
 * Email: hydznsqk@163.com
 * Des: 響應(yīng),通過inputStream解析服務(wù)器返回來的數(shù)據(jù)為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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文目標(biāo) 搞明白OKHttp的源碼同步請求和異步請求基本流程 基本使用 同步請求 異步請求 1.創(chuàng)建okHttpC...
    信仰年輕閱讀 712評論 0 2
  • 對于 Android Developer 來說,很多開源庫都是屬于開發(fā)必備的知識點,從使用方式到實現(xiàn)原理再到源碼解...
    業(yè)志陳閱讀 587評論 0 0
  • 前言 閱讀過上一篇對網(wǎng)絡(luò)編程的概述一文后,應(yīng)該對網(wǎng)絡(luò)編程有一個大體的概念了。從本文開始,將會開始對OkHttp的源...
    yjy239閱讀 1,329評論 0 3
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來的情緒。表情可以傳達很多信息。高興了當(dāng)然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,512評論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 7,650評論 0 4

友情鏈接更多精彩內(nèi)容