手寫(xiě)簡(jiǎn)易版 Retrofit

一混滔、Retrofit 基本使用

這里只實(shí)現(xiàn)最基本的使用烹笔,適配器和轉(zhuǎn)換器等并未使用伙窃。

先定義 Api 接口:

public interface Api {

    @GET("/v3/weather/weatherInfo")
    Call getWeather(@Query("city") String city, @Query("key") String key);

    @POST("/v3/weather/weatherInfo")
    Call postWeather(@Field("city") String city, @Field("key") String key);
}

分別使用 POST 和 GET 請(qǐng)求去獲取天氣數(shù)據(jù),這里使用的是高德地圖的 API颅夺。

接下來(lái)創(chuàng)建 Retrofit 對(duì)象朋截,獲取 Api 實(shí)例,用異步方式執(zhí)行 Api 中的請(qǐng)求:

    private void request() {
        SimpleRetrofit retrofit = new SimpleRetrofit.Builder().baseUrl("https://restapi.amap.com").build();
        Api api = retrofit.create(Api.class);
        Call getCall = api.getWeather("北京", "13cb58f5884f9749287abbead9c658f2");

        getCall.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(response.body().string());
            }
        });
    }

二吧黄、手寫(xiě)代碼

寫(xiě)代碼之前要了解一個(gè)前提部服,就是 Retrofit 其實(shí)是通過(guò)封裝 OKHttp 才擁有了網(wǎng)絡(luò)訪問(wèn)能力的,實(shí)際執(zhí)行網(wǎng)絡(luò)請(qǐng)求的是 OKHttp拗慨。Retrofit 要做的是為網(wǎng)絡(luò)請(qǐng)求接口生成動(dòng)態(tài)代理對(duì)象廓八,并在請(qǐng)求方法被調(diào)用時(shí),在動(dòng)態(tài)代理的 InvocationHandler 中解析注解赵抢,把要使用的網(wǎng)絡(luò)請(qǐng)求方法和參數(shù)解析出來(lái)生成 OKHttp 的 Request 對(duì)象剧蹂,最后由 OKHttp 發(fā)送請(qǐng)求。

SimpleRetrofit 時(shí)序圖

我們要實(shí)現(xiàn)的是一個(gè)極簡(jiǎn)版的 Retrofit(只是為了輔助更好的理解 Retrofit 框架)烦却,請(qǐng)求方法只實(shí)現(xiàn)了 GET宠叼、POST,參數(shù)注解只支持 @Query其爵、@Field:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GET {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface POST {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Query {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Field {
    String value();
}

Retrofit 使用 Builder 創(chuàng)建其對(duì)象:

public class SimpleRetrofit {

    HttpUrl baseUrl;
    Call.Factory callFactory;

    public SimpleRetrofit(Builder builder) {
        this.baseUrl = builder.baseUrl;
        this.callFactory = builder.callFactory;
    }
    
    static class Builder {

        HttpUrl baseUrl;
        Call.Factory callFactory;

        Builder baseUrl(String baseUrl) {
            this.baseUrl = HttpUrl.parse(baseUrl);
            return this;
        }

        public SimpleRetrofit build() {
            // 先做參數(shù)校驗(yàn)
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }

            if (callFactory == null) {
                callFactory = new OkHttpClient();
            }

            return new SimpleRetrofit(this);
        }
    }
}

Call.Factory 是生成 Call 對(duì)象的工廠冒冬,其唯一實(shí)現(xiàn)類為 OKHttpClient,調(diào)用其 newCall(Request) 方法可以生成 Call 對(duì)象摩渺,最后要通過(guò)這個(gè)方法把我們解析出來(lái)的數(shù)據(jù)封裝在 Request 中以生成 Call 對(duì)象简烤。

MyRetrofit 對(duì)象生成后,通過(guò)調(diào)用 create() 生成接口對(duì)象摇幻。很明顯是在 create() 中為接口生成了動(dòng)態(tài)代理横侦,同時(shí)做了接口方法的解析和調(diào)用:

    
    private final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
    
    public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
                        ServiceMethod serviceMethod = loadServiceMethod(method);
                        return serviceMethod.invoke(args);
                    }
                });
    }

    /**
     * DLC 方式獲取 ServiceMethod挥萌,如果沒(méi)有解析過(guò)就解析該方法
     * @param method 動(dòng)態(tài)代理執(zhí)行的接口方法
     * @return 解析后的方法對(duì)象
     */
    private ServiceMethod loadServiceMethod(Method method) {
        // 先不加鎖,避免性能損失
        ServiceMethod serviceMethod = serviceMethodCache.get(method);
        if (serviceMethod != null) return serviceMethod;

        // 避免多線程下重復(fù)解析
        synchronized (serviceMethodCache) {
            serviceMethod = serviceMethodCache.get(method);
            if (serviceMethod == null) {
                serviceMethod = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, serviceMethod);
            }
        }
        return serviceMethod;
    }

在 ServiceMethod 的 Builder 中解析方法和參數(shù)注解:

public class ServiceMethod {

    private final String httpMethod;
    private final Call.Factory callFactory;
    private final HttpUrl baseUrl;
    private final String relativeUrl;
    private final ParameterHandler[] parameterHandlers;

    private FormBody.Builder formBuilder;
    private FormBody formBody;
    private HttpUrl.Builder urlBuilder;

    public ServiceMethod(Builder builder) {
        baseUrl = builder.retrofit.baseUrl;
        callFactory = builder.retrofit.callFactory;
        httpMethod = builder.httpMethod;
        relativeUrl = builder.relativeUrl;
        parameterHandlers = builder.parameterHandlers;
        boolean hasBody = builder.hasBody;

        // 如果有請(qǐng)求體枉侧,創(chuàng)建一個(gè) OKHttp 請(qǐng)求體對(duì)象
        if (hasBody) {
            formBuilder = new FormBody.Builder();
        }
    }
    
    static class Builder {

        private final SimpleRetrofit retrofit;
        private final Method method;
        private final Annotation[] annotations;
        // 方法上有 n 個(gè)參數(shù)引瀑,每個(gè)參數(shù)又有 m 個(gè)注解,用一個(gè) nxm 的數(shù)組保存
        private final Annotation[][] parameterAnnotations;

        String httpMethod;
        boolean hasBody;
        String relativeUrl;
        ParameterHandler[] parameterHandlers;

        public Builder(SimpleRetrofit retrofit, Method method) {
            this.retrofit = retrofit;
            this.method = method;
            annotations = method.getAnnotations();
            parameterAnnotations = method.getParameterAnnotations();
        }
        
        public ServiceMethod build() {
            // 解析方法上的注解
            for (Annotation annotation : annotations) {
                if (annotation instanceof GET) {
                    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
                } else if (annotation instanceof POST) {
                    parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
                }
            }

            // 解析方法參數(shù)上的所有注解榨馁,把注解值存入 ParameterHandler[] 中
            int length = parameterAnnotations.length; // 方法上的參數(shù)個(gè)數(shù)
            parameterHandlers = new ParameterHandler[length];
            for (int i = 0; i < length; i++) {
                // 一個(gè)參數(shù)上的所有注解
                Annotation[] annotations = parameterAnnotations[i];
                parameterHandlers[i] = parseParameter(annotations);
            }

            return new ServiceMethod(this);
        }

        private ParameterHandler parseParameter(Annotation[] annotations) {

            // 根據(jù)注解類型創(chuàng)建對(duì)應(yīng)的 ParameterHandler
            ParameterHandler result = null;

            for (Annotation annotation : annotations) {
                ParameterHandler annotationAction = parseParameterAction(annotation, annotations);

                // 如果當(dāng)前檢查的注解并不是我們能處理的伤疙,就繼續(xù)遍歷下一個(gè)
                if (annotationAction == null) {
                    continue;
                }

                // 如果 result 不為 null 說(shuō)明之前遍歷時(shí)已經(jīng)找到了 SimpleRetrofit 能處理的注解
                // 不允許一個(gè)參數(shù)上被多個(gè) SimpleRetrofit 的參數(shù)注解標(biāo)注,拋異常
                if (result != null) {
                    throw new IllegalArgumentException("Multiple Retrofit annotations found, only one allowed.");
                }

                result = annotationAction;
            }

            // 遍歷完都沒(méi)找到說(shuō)明這個(gè)參數(shù)沒(méi)有被 SimpleRetrofit 注解標(biāo)注辆影,不應(yīng)該被檢查
            if (result == null) {
                throw new IllegalArgumentException("No Retrofit annotation found.");
            }

            return result;
        }

        private ParameterHandler parseParameterAction(Annotation annotation, Annotation[] annotations) {
            if (annotation instanceof Query) {
                String key = ((Query) annotation).value();
                return new ParameterHandler.QueryParameterHandler(key);
            } else if (annotation instanceof Field) {
                String key = ((Field) annotation).value();
                return new ParameterHandler.FieldParameterHandler(key);
            }

            return null;
        }

        private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
            // 規(guī)定一個(gè)方法上只能有一個(gè) httpMethod 注解,否則拋出異常
            if (this.httpMethod != null) {
                String message = String.format("Only one HTTP method is allowed. Found: %s and %s.",
                        this.httpMethod, httpMethod);
                throw new IllegalArgumentException(message);
            }

            this.httpMethod = httpMethod;
            this.hasBody = hasBody;

            if (value == null) {
                return;
            }

            this.relativeUrl = value;
        }
    }
       
}

build() 中負(fù)責(zé)解析方法注解和方法參數(shù)注解黍特。解析方法注解主要是為了獲取使用哪種 HTTP 方法(GET蛙讥、POST)、是否有請(qǐng)求體以及相對(duì)地址灭衷;解析方法參數(shù)注解是為了把被 @Field 或 @Query 注解的參數(shù)的值次慢,即網(wǎng)絡(luò)請(qǐng)求的 key 保存在 ParameterHandler[] 中。比如說(shuō)對(duì)于 getWeather():

    @GET("/v3/weather/weatherInfo")
    Call getWeather(@Query("city") String city, @Query("key") String key);

@Query 注解的值分別為 city翔曲、key迫像,那么就把 city 和 key 分別傳入 ParameterHandler[] 中保存,而這兩個(gè) key 對(duì)應(yīng)的 value 會(huì)在調(diào)用 ServiceMethod 的 invoke() 方法時(shí)傳入瞳遍。

ParameterHandler 的作用是保存網(wǎng)絡(luò)請(qǐng)求的 key闻妓,并把 key-value 回調(diào)給 ServiceMethod:

public abstract class ParameterHandler {

    abstract void apply(ServiceMethod serviceMethod, String value);

    static class QueryParameterHandler extends ParameterHandler {

        String key;

        public QueryParameterHandler(String key) {
            this.key = key;
        }

        @Override
        void apply(ServiceMethod serviceMethod, String value) {
            serviceMethod.addQueryParameter(key, value);
        }
    }

    static class FieldParameterHandler extends ParameterHandler {

        String key;

        public FieldParameterHandler(String key) {
            this.key = key;
        }

        @Override
        void apply(ServiceMethod serviceMethod, String value) {
            serviceMethod.addFieldParameter(key, value);
        }
    }
}

回調(diào)方法一個(gè)是處理 GET 請(qǐng)求的,一個(gè)是處理 POST 請(qǐng)求的:

    // get 請(qǐng)求掠械,把 key-value 拼接到 url 中
    public void addQueryParameter(String key, String value) {
        if (urlBuilder == null) {
            urlBuilder = baseUrl.newBuilder(relativeUrl);
        }

        urlBuilder.addQueryParameter(key, value);
    }

    // post 請(qǐng)求由缆,把 key-value 放到請(qǐng)求體中
    public void addFieldParameter(String key, String value) {
        formBuilder.add(key, value);
    }

最后在 invoke() 中生成 OKHttp 的 Request 對(duì)象并調(diào)用 CallFactory 的 newCall(Request) 生成 Call:

    public Object invoke(Object[] args) {
        for (int i = 0; i < parameterHandlers.length; i++) {
            ParameterHandler parameterHandler = parameterHandlers[i];
            parameterHandler.apply(this, args[i].toString());
        }

        if (urlBuilder == null) {
            urlBuilder = baseUrl.newBuilder(relativeUrl);
        }
        HttpUrl httpUrl = urlBuilder.build();

        if (formBuilder != null) {
            formBody = formBuilder.build();
        }

        Request request = new Request.Builder().url(httpUrl).method(httpMethod, formBody).build();
        return callFactory.newCall(request);
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市猾蒂,隨后出現(xiàn)的幾起案子均唉,更是在濱河造成了極大的恐慌,老刑警劉巖肚菠,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舔箭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蚊逢,警方通過(guò)查閱死者的電腦和手機(jī)层扶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)时捌,“玉大人怒医,你說(shuō)我怎么就攤上這事∩萏郑” “怎么了稚叹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵焰薄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我扒袖,道長(zhǎng)塞茅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任季率,我火速辦了婚禮野瘦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘飒泻。我一直安慰自己鞭光,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布泞遗。 她就那樣靜靜地躺著惰许,像睡著了一般。 火紅的嫁衣襯著肌膚如雪史辙。 梳的紋絲不亂的頭發(fā)上汹买,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音聊倔,去河邊找鬼晦毙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛耙蔑,可吹牛的內(nèi)容都是我干的见妒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼甸陌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼徐鹤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起邀层,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤返敬,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后寥院,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體劲赠,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年秸谢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凛澎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡估蹄,死狀恐怖塑煎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臭蚁,我是刑警寧澤最铁,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布讯赏,位于F島的核電站,受9級(jí)特大地震影響冷尉,放射性物質(zhì)發(fā)生泄漏漱挎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一雀哨、第九天 我趴在偏房一處隱蔽的房頂上張望磕谅。 院中可真熱鬧,春花似錦雾棺、人聲如沸膊夹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)割疾。三九已至,卻和暖如春嘉栓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拓诸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工侵佃, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奠支。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓馋辈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親倍谜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子迈螟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容