手?jǐn)]丐中丐版OkHttp

手?jǐn)]丐中丐版OkHttp

通過簡單實(shí)現(xiàn)的方式來加深框架的理解,在實(shí)現(xiàn)過程中思考源碼是怎么實(shí)現(xiàn)的,為什么這么做。

Request 和 Response

根據(jù)網(wǎng)絡(luò)的基礎(chǔ),首先創(chuàng)建Request請求和Response響應(yīng)兩個基礎(chǔ)類。

Request包含請求地址,請求頭,請求方式(GET/POST等)。支持設(shè)置請求地址,其他參數(shù)直接給填充上。

public class Request {
    final Map<String, String> headers;
    final String url;
    final String method;

    public Request(String url) {
        this.url = url;
        this.method = "GET";
        this.headers = new HashMap<>();
        this.headers.put("Content-Type", "application/json;charset=UTF-8");
    }

}

Response包含發(fā)起請求的Request信息,響應(yīng)頭,響應(yīng)狀態(tài)碼和結(jié)果。

public class Response {
    final Request request;
    final Map<String, String> headers;
    final int code;
    final String message;

    public Response(Request request) {
        this.request = request;
        this.headers = new HashMap<>();
        this.headers.put("Content-Type", "application/json;charset=UTF-8");
        this.code = 200;
        this.message = "Success";
    }
}

創(chuàng)建一個Call接口,當(dāng)前只包含一個execute方法,用來執(zhí)行Request并返回結(jié)果。

public interface Call {

    Response execute(Request request);

}

創(chuàng)建一個Activity用來測試流程。

  1. 實(shí)現(xiàn)Call接口
  2. 創(chuàng)建請求對象
  3. 使用call.execute執(zhí)行請求,返回對象
    public class HttpTestActivity extends AppCompatActivity {
    
        public static final String TAG = "HttpTestActivity";
        private TextView tvResponse;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_http);
            final Call call = new Call() {
                @Override
                public Response execute(Request request) {
                    return new Response(request);
                }
            };
            tvResponse = findViewById(R.id.tv_response);
            findViewById(R.id.btn_request).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Response response = call.execute(new Request("http://www.baidu.com"));
                    Log.e(TAG, response.toString());
                    tvResponse.setText(response.toString());
                }
            });
    
        }
    }

查看控制臺打印的結(jié)果

Response{
    request=Request{
    headers={Content-Type=application/json;charset=UTF-8},
     url='http://www.baidu.com,
     method='GET
    },
     headers={Content-Type=application/json;charset=UTF-8},
     code=200,
     message='Success
    }

我們首先打通了這條路,實(shí)現(xiàn)了單機(jī)版的同步請求。接下來對流程上的每一項(xiàng)進(jìn)行填充,實(shí)現(xiàn)異步執(zhí)行,攔截器等功能。

異步任務(wù)

早在Android4.X時代,谷歌官方就禁止了在主線程中執(zhí)行網(wǎng)絡(luò)請求。所以一個網(wǎng)絡(luò)請求框架必須要具備異步請求的功能。

首先改造接口類call,增加enqueue方法,讓請求放入任務(wù)隊列中等待執(zhí)行。此外由于是異步操作,需要定義回調(diào)接口。

//回調(diào)接口
public interface Callback {

    void onResponse(Response response);
}


public interface Call {

    Response execute(Request request);

    void enqueue(Request request,Callback callback);
}

接下來定義線程池,進(jìn)行入隊任務(wù)的處理。在源碼中線程池定義在Dispatch類中進(jìn)行所有任務(wù)的管理,由于是丐中丐,直接創(chuàng)建RealCall類將線程池定義在此。

public class RealCall implements Call {

    public static final String TAG = "RealCall";
    private @Nullable
    ExecutorService executorService;

    public synchronized ExecutorService executorService() {

        if (executorService == null) {
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>());
        }
        return executorService;
    }

    @Override
    public Response execute(Request request) {
        return new Response(request);
    }

    @Override
    public void enqueue(final Request request, final Callback callback) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                //打印線程信息
                Log.e(TAG, "run: " + Thread.currentThread().getName());
                try {
                    //模擬網(wǎng)絡(luò)請求耗時
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                callback.onResponse(new Response(request));
            }
        };
        executorService().execute(runnable);
    }
}

我們的網(wǎng)絡(luò)請求往往是短暫的,次數(shù)較多的任務(wù)。所以采用的是與Executors.newCachedThreadPool()一致的設(shè)置,源碼中設(shè)置了線程工廠用來設(shè)置線程的名字。在Activity中點(diǎn)擊按鈕觸發(fā)測試。會在不同的線程執(zhí)行任務(wù)。

      call.enqueue(new Request("http://www.baidu.com"), new Callback() {
                @Override
                public void onResponse(Response response) {
                    Log.e(TAG, response.toString());
                }
            });

    --------------------------------
    E/RealCall: run: pool-1-thread-1
    E/RealCall: run: pool-1-thread-2
    E/RealCall: run: pool-1-thread-3
    E/RealCall: run: pool-1-thread-4
    E/RealCall: run: pool-1-thread-4
    E/RealCall: run: pool-1-thread-3
    E/RealCall: run: pool-1-thread-2
    E/RealCall: run: pool-1-thread-3
    E/RealCall: run: pool-1-thread-2

攔截器的實(shí)現(xiàn)

在源碼中分別有interceptors 和 networkInterceptors 兩塊攔截器,分別代表網(wǎng)絡(luò)請求前對Request進(jìn)行處理和在請求后對響應(yīng)結(jié)果Response進(jìn)行處理。

public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {

  final List<Interceptor> interceptors;
  final List<Interceptor> networkInterceptors;
}

在實(shí)際場景中,我們會對OkHttpClient設(shè)置統(tǒng)一的token信息,在請求完成后對結(jié)果進(jìn)行打印。接下來就通過責(zé)任鏈模式來實(shí)現(xiàn)攔截器。

仿造源碼實(shí)現(xiàn)攔截器接口類

public interface Interceptor {

    Response intercept(Chain chain);

    interface Chain {
        Request request();

        Response proceed(Request request);
    }
}

定義處理Request,執(zhí)行網(wǎng)絡(luò)請求(用Request獲取Response),處理Response三種類型的攔截器。

public static class NetWorkInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
        // 執(zhí)行網(wǎng)絡(luò)請求,先執(zhí)行之前對request的處理,調(diào)用AddParamsInterceptor
        chain.proceed(chain.request());
        try {
            //模擬網(wǎng)絡(luò)請求耗時
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new Response(chain.request());
    }
}

public static class AddParamsInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
        // 對Request進(jìn)行處理
        Request request = chain.request();
        request.headers.put("token", "SElLIFlpTWpGbEcwSVQ1dit1Wm86ejdCbUhFVnRrcTFTV0FPWGNMVWJuZDF0ekY4dHpRbUkwRys1V21DMXYvWT06MTU4NTY1MjYyMjQ0NA==");
        return chain.proceed(request);
    }
}

public static class LoggerInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) {
    
        Response response = chain.proceed(chain.request());
        //對response進(jìn)行操作,先之前網(wǎng)絡(luò)請求前的request
        Log.e(TAG, "intercept: " + response.toString());
        return response;
    }
}

改寫enqueue為責(zé)任鏈調(diào)用。

@Override
public void enqueue(final Request request, final Callback callback) {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //打印線程信息
            Log.e(TAG, "run: " + Thread.currentThread().getName());


            // 請求發(fā)起者
            Interceptor.Chain next = new Interceptor.Chain() {
                @Override
                public Request request() {
                    return request;
                }

                @Override
                public Response proceed(Request request) {
                    return null;
                }
            };

            for (final Interceptor interceptor : interceptors) {
                final Interceptor.Chain finalNext = next;
                Interceptor.Chain chain = new Interceptor.Chain() {
                    @Override
                    public Request request() {
                        return finalNext.request();
                    }

                    @Override
                    public Response proceed(Request request) {
                        return interceptor.intercept(finalNext);
                    }
                };
                next = chain;
            }

            callback.onResponse(next.proceed(request));
        }
    };
    executorService().execute(runnable);
}

責(zé)任鏈這塊的實(shí)現(xiàn)寫了很久,這種設(shè)計模式平時是真沒用過。關(guān)聯(lián)了下事件分發(fā)機(jī)制大概好理解些。再者思考下樹的遍歷。

//先序遍歷
find(Node root){
    System.out.println(root.value);
    find(root.left);
    find(root.right);
}

//中序遍歷
find(Node root){
    find(root.left);
    System.out.println(root.value);
    find(root.right);
}

//后序遍歷
find(Node root){
    find(root.left);
    find(root.right);
    System.out.println(root.value);
}

與上面三種狀態(tài)的攔截器處理類似。

其他

優(yōu)秀的框架肯定有優(yōu)秀的封裝,上面丐中丐只是梳理了異步請求與攔截器的實(shí)現(xiàn)與源碼差距甚大,甚至根本沒有走通網(wǎng)絡(luò)。在OkHttpClient源碼中使用了門面模式,來管理攔截器列表,分發(fā)實(shí)例,設(shè)置超時時間,管理連接池等,同時使用Builder模式實(shí)現(xiàn)默認(rèn)參數(shù)設(shè)置與鏈?zhǔn)絼?chuàng)建。
除以上設(shè)計模式以外需要掌握線程池的默認(rèn)設(shè)置,為什么?如何管理連接池?

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

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

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