實(shí)現(xiàn)一個(gè)簡單的Retrofit(二)

在上一篇《實(shí)現(xiàn)一個(gè)簡單的Retrofit(一)》中使用 java 的動(dòng)態(tài)代理和注解實(shí)現(xiàn)的 Retrofit 的大體框架,可以實(shí)現(xiàn)一個(gè)基本的 url 的 get請求,如下:

interface SimpleRequest{
    @GET
    String goBaidu(@Url String url);
}

SimpleRequest simpleRequest = new Retro.
                Builder().
                build().
                create(SimpleRequest.class);
println(simpleRequest.goBaidu("http://www.baidu.com"));

在這篇文章中我們需要實(shí)現(xiàn) Retrofit 的特性 CallAdapter。

在構(gòu)建 Retrofit 的實(shí)例的時(shí)候我們通常會調(diào)用 addCallAdapterFactory 方法,這個(gè)方法是什么作用我這里不多贅述,可以參考:http://square.github.io/retrofit/ 。

HttpCall

在上一篇中我們簡單的將 http 請求放在了 ServiceMethod 的 call 方法中,這樣做顯然是不妥當(dāng)?shù)?。這里我們?yōu)?http 請求抽象出一個(gè)名為 HttpCall 的類,代碼如下:

public class HttpCall {

    private ServiceMethod mServiceMethod ;

    interface Callback{
        void onResponse(Response response);
    }

    public HttpCall(ServiceMethod serviceMethod){
        mServiceMethod = serviceMethod;
    }

    public Response execute(){
        HttpURLConnection connection;
        Response response ;
        try {
            String urlStr = mServiceMethod.getUrl();
            URL url = new URL(urlStr);
            connection = (HttpURLConnection) url.openConnection();

            connection.setDoInput(true);

            //set http method
            connection.setRequestMethod(mServiceMethod.getHttpMethod());

            //set header
            mServiceMethod.applyHeader(connection);

            response = new Response(connection);

            return response;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    public void submit(final Callback callback){
        Response response = execute();
        if(callback != null){
            callback.onResponse(response);
        }
    }
}

此類比較簡單,主要有 execute 和 submit 兩個(gè)方法,前者直接執(zhí)行 http 請求并返回 Response,后者使用 Callback 的方式來傳遞 Response。

這里出現(xiàn)了一個(gè) Response 對象,在上一節(jié)中我們只是簡單的在 call 方法中返回了一個(gè)表示返回內(nèi)容的 String 對象,這樣也是不合理的,如果返回內(nèi)容是一個(gè)加密的字節(jié)數(shù)組或者發(fā)生了異常,顯然單單 String 是無法滿足的,所以這里還需要給 http 響應(yīng)做一個(gè)簡單封裝。

public class Response {

    private int code = -1;

    private HttpURLConnection connection;

    private String errorMsg = "";

    public Response(HttpURLConnection httpURLConnection) throws IOException {
        connection = httpURLConnection;
        code = connection.getResponseCode();
    }

    @Override
    public String toString() {
        return " response code : " + code + "  \n errorMsg : " + errorMsg;
    }

    public boolean isError() {
        return code >= 400;
    }

    public int getResponseCode() {
        return code;
    }

    public String getResponseBodyAsString() {
        if (code >= 400) {
            throw new RuntimeException("response error , code = " + code);
        }
        InputStream inputStream = null;
        ByteArrayOutputStream bos = null;
        try {
            inputStream = connection.getInputStream();
            bos = new ByteArrayOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = inputStream.read(buffer)) > 0) {
                bos.write(buffer, 0, len);
            }
            String body = new String(bos.toByteArray());
            return body;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            connection.disconnect();
        }
        return "";
    }

    public String getResponseErrorMsg(){
        if (code < 400) {
            throw new RuntimeException("not error , code = " + code);
        }
        InputStream inputStream = null;
        ByteArrayOutputStream bos = null;
        try {
            inputStream = connection.getErrorStream();
            bos = new ByteArrayOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = inputStream.read(buffer)) > 0) {
                bos.write(buffer, 0, len);
            }
            String body = new String(bos.toByteArray());
            return body;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            connection.disconnect();
        }
        return "";
    }
}

Response 類可以通過 isError 方法判斷請求是否出錯(cuò),然后通過 getResponseBodyAsString 和 getResponseErrorMsg 方法獲得對應(yīng)數(shù)據(jù),如果此時(shí)需要 InputStream 的話也可再擴(kuò)展這個(gè)類,這里為了簡單起見沒有進(jìn)行擴(kuò)展。

此時(shí)將 ServiceMethod 中的方法改為:

public Object call(){
    return new HttpCall(this);
}

將接口改為:

interface SimpleRequest {
    @GET
    HttpCall go(@Url String url);
}

測試代碼調(diào)整為如下:

    @Test
    public void testAnnotation_Url() {
        SimpleRequest simpleRequest = new Retro.
                Builder().
                build().
                create(SimpleRequest.class);
        HttpCall httpCall = simpleRequest.go("http://www.baidu.com");
        Response response = httpCall.execute();
        if(! response.isError()){
            println(response.getResponseBodyAsString());
        }
    }

運(yùn)行接口OK。

到這里一個(gè)封裝相對較好的 http 請求庫已經(jīng)有了,但看著總覺得復(fù)雜了些,這里明明可以直接返回給我們 Response 對象,為什么還要多此一舉先返回個(gè) HttpCall 呢?對了,有這想法是正確的,畢竟我們要追求代碼的簡潔性,但在確保簡潔性的同時(shí)有不能丟失了它的擴(kuò)展性,所以這里對 HttpCall 的處理就交個(gè) CallAdapter 。

CallAdapter

CallAdapter 的責(zé)任就是將原本要返回的 HttpCall 對象做一次適配,將其轉(zhuǎn)換成其他相對適合你使用的類,CallAdapter 定義如下:

public interface CallAdapter<T> {
    T adapter(HttpCall httpCall);
}

很簡單,只是一個(gè)接口,這里的泛型 T 就是目標(biāo)類型。假設(shè)我們需要在 Http 請求上使用觀察者模式,當(dāng)有請求得到響應(yīng)后將 Response 通知給所有觀察者。我們定義一個(gè)名為 ObservableCallAdapter 的類:

public class ObservableCallAdapter implements CallAdapter<Observable> {
    @Override
    public Observable adapter(final HttpCall httpCall) {
        return new Observable(){
            @Override
            public synchronized void addObserver(Observer observer) {
                super.addObserver(observer);
                httpCall.submit(new HttpCall.Callback() {
                    @Override
                    public void onResponse(Response response) {
                        setChanged();
                        notifyObservers(response);
                    }
                });
            }
        };

    }
}

然后將 ServiceCall 的 call 方法修改成如下:

    public Object call(HttpCall httpCall){
        CallAdapter callAdapter = mCallAdapters.get(0);
        return callAdapter.adapter(httpCall);
    }

此時(shí)調(diào)用 ObservableCallAdapter 的 adapter 方法,返回的是一個(gè)被觀察對象,在 ObservableCallAdapter 中可以發(fā)現(xiàn)我們重寫了 Observable 的 addObserver 方法,當(dāng)有觀察者被添加進(jìn)來的時(shí)候就去進(jìn)行 http 請求并在得到 Response 后通知給觀察者。

我們來對我們的 SimpleRequest 做一個(gè)修改:

    interface SimpleRequest {
        @GET
        Observable go(@Url String url);
    }

測試代碼調(diào)整如下:

    @Test
    public void testAnnotation_Url() {
        SimpleRequest simpleRequest = new Retro.
                Builder().callAdapter(new ObservableCallAdapter()).
                build().
                create(SimpleRequest.class);
        simpleRequest.go("http://www.baidu.com").addObserver(new Observer() {
            @Override
            public void update(Observable o, Object arg) {
                if(arg instanceof Response){
                    if(((Response) arg).isError()){
                        println(((Response) arg).getResponseBodyAsString());
                    }
                }
            }
        });
    }

這里總結(jié)一下 CallAdapter 的作用,如果你不滿足與 HttpCall 提供的方法,可以使用 CallAdapter 進(jìn)行封裝,然后提供更具靈活性的 http 響應(yīng)回調(diào)。

總結(jié)

CallAdapter 用途廣泛,在 Retrofit 中有大家熟知的 RxJava2CallAdapter,可以將請求響應(yīng)轉(zhuǎn)換成 RxJava 中的 Observable 對象并提供響應(yīng)的訂閱功能。
另外提一下,在下一篇中我們將實(shí)現(xiàn) Retrofit 中一個(gè)比較牛的特性:Converter 。

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

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

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