动手撸一个简易的Retrofit

一个简易的Retrofit

今天,我们使用泛型、注解、反射和动态代理,来写一个简易的Retrofit,巩固一下之前讲到的知识。

Retrofit的使用,在这里就不多做介绍了,相信大家也都有使用过了。

这里我们使用高德地图的天气Api,首先定义一个接口。

public interface WeatherApi {
    @POST("/v3/weather/weatherInfo")
    Call postWeather(@Field("city") String city, @Field("key") String key);


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

然后,看到这里有4个注解,POST GET Field Query,好,我们来写这4个注解。

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

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

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

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

然后在我们的MainActivity中使用这个接口。

public class MainActivity extends AppCompatActivity {

    private WeatherApi weatherApi;
    private Retrofit retrofit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherApi = retrofit.create(WeatherApi.class);
    }
}

这里,就需要创建我们的Retrofit类,还要实现create这个方法了。

public class Retrofit {

    public <T> T create(final Class<T> service) {
        return null;
    }
}

先把方法创建出来,再思考怎么实现。
这里,因为传入的类的类型,我们并不确定。传入任何一个Api的接口类都是可以的吧,所以,这里使用了泛型来接收传入的参数。
由于,在这里,我们传入的类型不确定,传入哪一个接口也不知道,所以,我希望不管传入什么接口,我都可以代理他,所以这里考虑使用了动态代理。
好,接下来,我们实现这个方法,先使用动态代理的方法,把动态代理对象创建出来。

public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        });
    }

这里,我们就得到了通过接口类创建的动态代理对象,由于使用了泛型,所以这里传入了WeatherApi的class对象,所以我们得到的也是一个实现了WeatherApi的实例对象,那么就可以调用,WeatherApi所定义的接口方法了。

由于动态代理的关键,就在invoke这个方法,不管调用了对象的哪个方法,都会回调到这个方法,所以我们实际的网络访问的所有操作,要写在这个invoke方法里。

那么这里我们要做的是什么呢?回调我们这个invoke方法,我们能得到访问了哪一个方法,那么是不是就可以拿到方法中所有的注解了,包括方法上的注解和方法参数的注解。我们就可以通过注解里边的信息,生成我们最后访问需要的url和body(Post方法)。

我们访问的方法,经过了一次解析,就生成了我们想要的url参数,那么,第二次访问的时候,应该是不需要第二次解析了,因为方法的参数是不会发生改变的。所以,这里我们考虑使用Map将我们解析到的结果缓存起来。这里创建一个类ServiceMethod来保存解析出来的相关信息。使用构造者模式。这里简单提一下构造者模式的好处。一个类有很多属性,那么使用了构造者模式后,很多你不需要关心的属性,在自身的构造者方法里就给你初始化好了,这里你只需要设置你需要关心的属性就可以了。

public class ServiceMethod {

    public ServiceMethod(Builder builder) {

    }

    public static class Builder {
        private final Retrofit retrofit;

        public Builder(Retrofit retrofit, Method method) {
            this.retrofit = retrofit;
        }

        public ServiceMethod build() {
            return new ServiceMethod(this);
        }
    }
}

这样在build方法里去解析我们的method的各种参数就可以了。

然后,先不去管这个ServiceMethod的具体实现,我们先在我们写的Retrofit中使用它。这里我们建立这个方法loadServiceMethod,首先在缓存中查找,如果找到了就拿来用,如果没有就创建一个。

public class Retrofit {

    private final Map<Method, ServiceMethod> serviceMethodCache = new HashMap<>();

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

    private ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result = serviceMethodCache.get(method);
        if (result != null) return result;
        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
                result = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }

}

loadServiceMethod这里简单讲解一下,在开始的时候从缓存里通过method查找,有没有缓存。如果有则返回,如果没有,这里我们使用了一个同步锁,防止多线程同时操作,产生的不安全操作。
然后在同步锁的方法里,我们又判断了一次缓存中是否存在,这里是因为可能你在被锁阻塞的时候,上一个线程已经创建了serviceMethod,并放在了缓存里。这时候,由于上一个线程运行完毕,阻塞的地方可以进入,这时候实际上缓存中已经存在了。如果不再进行一次判断,就会再次创建,导致浪费资源。所以,这里在同步锁的方法里再次从缓存中取一次,如果仍然没有,则通过ServiceMethod的Builder构造器去创建这个实例。然后把它放在缓存当中。

这里ServiceMethod这个类的使用已经写好了,接下来就要去实现ServiceMethod这个类里的具体方法了。这里我们调用了build(),这个方法,所以先从build这个方法下手。在build里,我们应该进行方法注解的解析。

public static class Builder {
        private final Retrofit retrofit;
        private final Annotation[] methodAnnotations;
        private final Annotation[][] parameterAnnotations;
        private String httpMethod;
        private String relativeUrl;
        private boolean hasBody = false;

        public Builder(Retrofit retrofit, Method method) {
            this.retrofit = retrofit;
            // 获取方法上的所有注解
            methodAnnotations = method.getAnnotations();
            // 获取方法参数上的所有注解,参数可以有多个,每个参数的注解也可以有多个,所以是一个二位数组
            parameterAnnotations = method.getParameterAnnotations();
        }

        public ServiceMethod build() {

            /**
             * 解析方法上的注解,只处理POST和GET
             */
            for (Annotation methodAnnotation : methodAnnotations) {
                if (methodAnnotation instanceof POST) {
                    // 判断注解是否是POST注解
                    this.httpMethod = "POST";
                    this.relativeUrl = ((POST) methodAnnotation).value();
                    this.hasBody = true;
                } else if (methodAnnotation instanceof GET) {
                    // 判断注解是否是GET注解
                    this.httpMethod = "GET";
                    this.relativeUrl = ((GET) methodAnnotation).value();
                    this.hasBody = false;
                }
            }
            return new ServiceMethod(this);
        }
    }

这里,我们通过反射的方法,获取方法上的注解,拿到注解中的参数,记录下来我们想要的数据。这里记录了httpMethod、relativeUrl和basBody。然后,再通过方法参数上的注解,获得我们访问url所使用的参数。

这里我们再进行一个思考,通过反射的方法,我能拿到的只有url访问参数中的key值,因为这个key值是设置在注解中的,通过反射是可以拿到这个key值。但是value呢,value只有在真正调用这个方法的时候,我们才会通过动态代理中的invoke方法,其中的参数拿到。那么,在只解析注解的时候,我就需要把解析出来的key值先保存起来,然后在真正调用这个方法的时候,再把key值拿出来,组装成我们使用的完整的访问参数。所以,这里创建一个类ParameterHandler,来记录key值,然后在调用方法的时候,将value值传入进来,最后通过apply方法回调到serviceMethod,将组装好的key和value传回去。

public abstract class ParameterHandler {
	//serviceMethod: 回调方法,将key和value组装好传回servicemethod
    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 FiledParameterHandler extends ParameterHandler {
        String key;

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

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

接下来,我们解析方法参数上的注解:

public ServiceMethod build() {

            。。。

            /**
             * 解析方法参数上的注解
             */
            // 获得方法的个数
            int length = parameterAnnotations.length;
            // 建立一个以方法的个数为长度的数组
            parameterHandler = new ParameterHandler[length];
            for (int i = 0; i < length; i++) {
                // 一个方法参数上的所有注解
                Annotation[] annotations = parameterAnnotations[i];
                for (Annotation annotation : annotations) {
                    // 解析参数上的注解 Field 和 Query
                    if (annotation instanceof Field) {
                        String value = ((Field) annotation).value();
                        parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);
                    } else if (annotation instanceof Query) {
                        String value = ((Query) annotation).value();
                        parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);
                    }
                }
            }
            return new ServiceMethod(this);
        }

好,至此,我们将注解全部解析完毕,然后就是在调用方法时拼装成我们想要的url和参数,从而进行访问url。

在ServiceMethod类上建立一些我们会用到的成员变量,包括Builder构造器解析好的一些数据和我们访问网络需要使用到的一些参数和对象。

我们知道,Retrofit这个框架本身它是没有访问网络的功能的,它就是使用的Okhttp来访问的网络,然后将url地址和参数等进行了一层封装,所以我们要在我们的Retrofit里引入Okhttp,才可以访问网络。

这里,我们的Retrofit也是使用构造者模式的。

public class Retrofit {

    ...
    final Call.Factory callFactory;
    final HttpUrl baseUrl;

    public Retrofit(Builder builder) {
        this.callFactory = builder.callFactory;
        this.baseUrl = builder.baseUrl;
    }

    ...

    public static class Builder {

        private HttpUrl baseUrl;
        private Call.Factory callFactory;

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

        public Builder callFactory(Call.Factory callFactory) {
            this.callFactory = callFactory;
            return this;
        }

        public Retrofit build() {
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }
            if (this.callFactory == null) {
                this.callFactory = new OkHttpClient();
            }
            return new Retrofit(this);
        }
    }
}

这里可以从源码看到,OkHttpClient继承了CallFactory,所以这里使用了CallFactory。

然后在ServiceMethod里,我们就可以使用Retrofit定义好的,baseUrl和CallFactory。
在ServiceMethod里建立一个invoke方法,将动态代理里,访问的参数传进去。

public class ServiceMethod {

    private final Call.Factory callFactory;
    private final String relativeUrl;
    private final boolean hasBody;
    private final ParameterHandler[] parameterHandler;
    private FormBody.Builder formBuilder;
    HttpUrl baseUrl;
    String httpMethod;
    HttpUrl.Builder urlBuilder;

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

        //如果是有请求体,创建一个okhttp的请求体对象
        if (hasBody) {
            formBuilder = new FormBody.Builder();
        }
    }

    public Object invoke(Object[] args) {

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

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

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

        Request request = new Request.Builder().url(url).method(httpMethod, formBody).build();
        return callFactory.newCall(request);
    }

    public void addQueryParameter(String key, String value) {
        if (urlBuilder == null) {
            urlBuilder = baseUrl.newBuilder(relativeUrl);
        }
        urlBuilder.addQueryParameter(key, value);
    }

    public void addFiledParameter(String key, String value) {
        formBuilder.add(key, value);
    }

    ...
}

最后在我们的MainActivity中调用我们写好的Retrofit。

public class MainActivity extends AppCompatActivity {

    private WeatherApi weatherApi;
    private Retrofit retrofit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherApi = retrofit.create(WeatherApi.class);
        retrofit = new Retrofit.Builder().baseUrl("https://restapi.amap.com").build();
    }

    public void get(View view) {
        Call call = weatherApi.getWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {

            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                response.close();
            }
        });

    }

    public void post(View view) {
        okhttp3.Call call = weatherApi.postWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {

            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                response.close();
            }
        });
    }
}

至此,我们的简易版的Retrofit就制作完成了。当然Retrofit不只这么点功能,还有强大的拦截器、转换器等。这里,只探讨使用了动态代理这一部分。
下边是完整的代码。

MainActivity.class

public class MainActivity extends AppCompatActivity {

    private WeatherApi weatherApi;
    private Retrofit retrofit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherApi = retrofit.create(WeatherApi.class);
        retrofit = new Retrofit.Builder().baseUrl("https://restapi.amap.com").build();
    }

    public void get(View view) {
        Call call = weatherApi.getWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {

            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                response.close();
            }
        });

    }

    public void post(View view) {
        okhttp3.Call call = weatherApi.postWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {

            }

            @Override
            public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
                response.close();
            }
        });
    }
}

Retrofit.java

public class Retrofit {

    final Map<Method, ServiceMethod> serviceMethodCache = new HashMap<>();
    final Call.Factory callFactory;
    final HttpUrl baseUrl;

    public Retrofit(Builder builder) {
        this.callFactory = builder.callFactory;
        this.baseUrl = builder.baseUrl;
    }

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

    private ServiceMethod loadServiceMethod(Method method) {
        ServiceMethod result = serviceMethodCache.get(method);
        if (result != null) return result;
        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
                result = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }

    public static class Builder {

        private HttpUrl baseUrl;
        private Call.Factory callFactory;

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

        public Builder callFactory(Call.Factory callFactory) {
            this.callFactory = callFactory;
            return this;
        }

        public Retrofit build() {
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }
            if (this.callFactory == null) {
                this.callFactory = new OkHttpClient();
            }
            return new Retrofit(this);
        }
    }
}

ServiceMethod.java

public class ServiceMethod {

    private final Call.Factory callFactory;
    private final String relativeUrl;
    private final boolean hasBody;
    private final ParameterHandler[] parameterHandler;
    private FormBody.Builder formBuilder;
    HttpUrl baseUrl;
    String httpMethod;
    HttpUrl.Builder urlBuilder;

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

        //如果是有请求体,创建一个okhttp的请求体对象
        if (hasBody) {
            formBuilder = new FormBody.Builder();
        }
    }

    public Object invoke(Object[] args) {

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

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

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

        Request request = new Request.Builder().url(url).method(httpMethod, formBody).build();
        return callFactory.newCall(request);
    }

    public void addQueryParameter(String key, String value) {
        if (urlBuilder == null) {
            urlBuilder = baseUrl.newBuilder(relativeUrl);
        }
        urlBuilder.addQueryParameter(key, value);
    }

    public void addFiledParameter(String key, String value) {
        formBuilder.add(key, value);
    }

    public static class Builder {
        private final Retrofit retrofit;
        private final Annotation[] methodAnnotations;
        private final Annotation[][] parameterAnnotations;
        private String httpMethod;
        private String relativeUrl;
        private boolean hasBody = false;
        private ParameterHandler[] parameterHandler;

        public Builder(Retrofit retrofit, Method method) {
            this.retrofit = retrofit;
            // 获取方法上的所有注解
            methodAnnotations = method.getAnnotations();
            // 获取方法参数上的所有注解,参数可以有多个,每个参数的注解也可以有多个,所以是一个二位数组
            parameterAnnotations = method.getParameterAnnotations();
        }

        public ServiceMethod build() {

            /**
             * 解析方法上的注解,只处理POST和GET
             */
            for (Annotation methodAnnotation : methodAnnotations) {
                if (methodAnnotation instanceof POST) {
                    // 判断注解是否是POST注解
                    this.httpMethod = "POST";
                    this.relativeUrl = ((POST) methodAnnotation).value();
                    this.hasBody = true;
                } else if (methodAnnotation instanceof GET) {
                    // 判断注解是否是GET注解
                    this.httpMethod = "GET";
                    this.relativeUrl = ((GET) methodAnnotation).value();
                    this.hasBody = false;
                }
            }

            /**
             * 解析方法参数上的注解
             */
            // 获得方法的个数
            int length = parameterAnnotations.length;
            // 建立一个以方法的个数为长度的数组
            parameterHandler = new ParameterHandler[length];
            for (int i = 0; i < length; i++) {
                // 一个方法参数上的所有注解
                Annotation[] annotations = parameterAnnotations[i];
                for (Annotation annotation : annotations) {
                    // 解析参数上的注解 Field 和 Query
                    if (annotation instanceof Field) {
                        String value = ((Field) annotation).value();
                        parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);
                    } else if (annotation instanceof Query) {
                        String value = ((Query) annotation).value();
                        parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);
                    }
                }
            }
            return new ServiceMethod(this);
        }
    }
}

ParameterHandler.java

package com.example.enjoy.retrofit;

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 FiledParameterHandler extends ParameterHandler {
        String key;

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值