美文网首页Android知识
10分钟实现一个简单的Retrofit框架

10分钟实现一个简单的Retrofit框架

作者: 相见浮生 | 来源:发表于2017-06-20 18:11 被阅读324次

    前言

    Retrofit 是一个赞誉无数的 Android 网络框架,它的优点和强大无需我们多说。今天我们主要通过实现一个简单的 Retrofit 框架(只能够进行 Get 请求)来了解它内部核心运作机制。


    注解抽取

    Retrofit 中有多达 25 个注解,由于我们只是实现 Retrofit 的 Get 功能,因此我们只用抽取其中的 Get 、Field 二个注解接口即可。

    **Get.java **

    @Documented
    @Target(METHOD)
    @Retention(RUNTIME)
    public @interface GET {
      String value() default "";
    }
    

    **Field.java **

    @Documented
    @Target(PARAMETER)
    @Retention(RUNTIME)
    public @interface Field {
      String value();
    }
    

    实现 Retrofit 类

    Retrofit 类是 Retrofit 框架最核心的工作类,它通过 Builder 模式来构建各种请求参数。它的作用非常简单,通过动态代理来获取到我们自定义的方法接口实例,然后在解析该方法接口的注解参数以后,再调用Okhttp框架去执行网络请求。

    Retrofit.java

    
    public final class Retrofit {
    
        /**
         * 该map的作用用于缓存Retrofit解析以后的方法,因为注解解析是相对耗时的。
         */
        private final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
    
        private String baseUrl;
    
        public Retrofit(String baseUrl) {
            this.baseUrl = baseUrl;
        }
    
        public String getBaseUrl() {
            return baseUrl;
        }
    
        /**
         * 框架核心方法,用于构建接口实例
         *
         * @param service
         * @param <T>
         * @return
         */
        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 {
    
                            //1 验证是否是接口
                            if (!service.isInterface()) {
                                throw new IllegalArgumentException("API declarations must be interfaces.");
                            }
    
                            //2 进行构建接口实例,同时进行方法注解、方法参数注解解析,这些参数解析完成以后,我们就可以把注解解析后的url进行拼接
                            ServiceMethod serviceMethod =
                                    loadServiceMethod(method, args);
    
                            //3 调用 http 框架执行网络请求
                            OkHttpCall okHttpCall = new OkHttpCall(serviceMethod);
    
                            return okHttpCall;
                        }
                    }
            );
        }
    
        /**
         * 进行构建接口实例
         *
         * @param method  调用的接口方法
         * @param args 方法参数内容
         * @return
         */
        private ServiceMethod loadServiceMethod(Method method, Object[] args) {
            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, args).build();
                    serviceMethodCache.put(method, result);
                }
            }
            return result;
        }
    
    
        /**
         * Retrofit 的构建类,除了 baseUrl 以外,其他的配置参数都是可选的,在这里我们只添加 baseUrl。
         */
        public static final class Builder {
    
            private String baseUrl;
    
            public Builder baseUrl(String baseUrl) {
                this.baseUrl = baseUrl;
                return this;
            }
    
            public Retrofit build() {
                if (baseUrl == null) {
                    throw new IllegalStateException("Base URL required.");
                }
                return new Retrofit(baseUrl);
            }
        }
    
    }
    
    

    方法、方法参数注解解析

    在 Retrofit 类中的 create 方法中,我们提到Retrofit 的工作原理分为三步:

    • 一 校验接口
    • 二 构建接口实例,解析注解内容
    • 三 拼接 Url,执行网络请求

    接下来,我们来进行编写第二步。

    ServiceMethod.java

    public class ServiceMethod {
    
        private Builder mBuilder;
    
        public ServiceMethod(Builder builder) {
            this.mBuilder = builder;
        }
    
        /**
         * 获取具体网络请求类型
         *
         * @return methodName
         */
        public String getMethodName() {
            return mBuilder.methodName;
        }
    
        /**
         * @return 请求url,这里GET请求我们需要进行url参数拼接,在实际源码中我们其实是在Okhttpcall中调用requestbuilder进行统一拼接的。
         */
        public String getBaseUrl() {
    
            if (mBuilder.methodName.equals("GET")) {
    
                StringBuffer sb = new StringBuffer();
                sb.append(mBuilder.retrofit.getBaseUrl())
                        .append(mBuilder.relativeUrl);
    
                Map<String, Object> parameterMap = getParameter();
    
                if (parameterMap != null) {
                    Set<String> keySet = parameterMap.keySet();
                    if (keySet.size() > 0) {
                        sb.append("?");
                    }
                    for (String key : keySet) {
                        sb.append(key).append("=").append(parameterMap.get(key)).append("&");
                    }
                    sb.deleteCharAt(sb.length() - 1);
                }
                return sb.toString();
            }
    
            return mBuilder.retrofit.getBaseUrl();
        }
    
        public Map<String, Object> getParameter() {
            return mBuilder.parameterMap;
        }
    
        /**
         * 用于解析注解
         */
        static final class Builder {
            
            final Retrofit retrofit;
            final Method method;
            final Annotation[] methodAnnotations;
            final Annotation[][] parameterAnnotationsArray;
    
            private Map<String, Object> parameterMap = new HashMap<>();
            private Object[] args;
            private String methodName;
            private String relativeUrl;
    
            Builder(Retrofit retrofit, Method method, Object[] args) {
                this.retrofit = retrofit;
                this.method = method;
                // 方法注解列表
                this.methodAnnotations = method.getAnnotations();
                // 方法参数注解列表
                this.parameterAnnotationsArray = method.getParameterAnnotations();
                // 方法参数内容列表
                this.args = args;
            }
    
    
            public ServiceMethod build() {
    
                // 遍历方法注解
                for (Annotation annotation : methodAnnotations) {
                    parseMethodAnnotation(annotation);
                }
    
                // 遍历方法参数注解
                int parameterCount = parameterAnnotationsArray.length;
                for (int p = 0; p < parameterCount; p++) {
                    Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
                    parseParameter(p, parameterAnnotations);
                }
    
                return new ServiceMethod(this);
            }
    
            /**
             * 解析方法注解,获取方法注解中的值,用于后续拼接url地址
             *
             * @param annotation
             */
            private void parseMethodAnnotation(Annotation annotation) {
                if (annotation instanceof GET) {
                    parseHttpMethodAndPath("GET", ((GET) annotation).value());
                }
            }
    
    
            private void parseHttpMethodAndPath(String httpMethod, String value) {
                if (httpMethod.equals("GET")) {
                    methodName = "GET";
                    this.relativeUrl = value;
                }
            }
    
            /**
             * 解析方法参数注解
             *
             * @param p                    方法参数值的index
             * @param parameterAnnotations 方法参数注解数组
             */
            private void parseParameter(int p, Annotation[] parameterAnnotations) {
                // 方法参数值
                Object value = args[p];
                // 遍历参数注解
                for (Annotation annotation : parameterAnnotations) {
                    //首先需要判断参数注解类型
                    if (annotation instanceof Field) {
                        Field field = (Field) annotation;
                        // 参数名称(接口参数名称)
                        String key = field.value();
                        parameterMap.put(key, value);
                    }
                }
            }
        }
    
    }
    

    执行网络请求

    在解析了注解内容,拼接了 Url 地址以后,我们的简单框架就只剩下执行网络请求这一步了,这一步难度不大,所以直接上代码:

    Call.java

    
    /**
     * 网络请求接口
     */
    public interface Call extends Cloneable {
    
        /**
         * 同步请求
         * @return
         * @throws IOException
         */
        String execute() throws IOException;
    
        /**
         * 异步请求
         * @param callback
         */
        void enqueue(Callback callback);
    
    }
    
    

    OkHttpCall.java

    
    public class OkHttpCall implements Call {
    
        private ServiceMethod mServiceMethod;
    
        private static OkHttpClient client;
    
        static {
            client = new OkHttpClient();
        }
    
        public OkHttpCall(ServiceMethod serviceMethod) {
            this.mServiceMethod = serviceMethod;
        }
    
        @Override
        public String execute() throws IOException {
            if (mServiceMethod.getMethodName().equals("GET")) {
                Request request = new Request.Builder()
                        .url(mServiceMethod.getBaseUrl())
                        .build();
                Response response = client.newCall(request).execute();
                return response.body().string();
            }
            return null;
        }
    
        @Override
        public void enqueue(final Callback callback) {
            if (mServiceMethod.getMethodName().equals("GET")) {
                Request request = new Request.Builder()
                        .url(mServiceMethod.getBaseUrl())
                        .build();
                client.newCall(request).enqueue(new okhttp3.Callback() {
                    @Override
                    public void onFailure(okhttp3.Call call, IOException e) {
                        callback.onFailure(e);
                    }
    
                    @Override
                    public void onResponse(okhttp3.Call call, Response response) throws IOException {
                        callback.onResponse(response.body().string());
                    }
                });
            }
        }
    
    }
    
    

    测试

    我们的简单框架到了这里就已经编写完成了,我们开始进行测试,看看有无问题。

    请求接口地址

    AppService.java

    public interface AppService {
    
        //http://android.secoo.com/appservice/cartAndBrand.action?v=2.0
        @GET("/appservice/cartAndBrand.action")
        OkHttpCall getCartAndBrand(@Field("v") String v);
    
    }
    
    

    MainActivity.java

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Retrofit retrofit = new Retrofit.Builder().baseUrl("http://android.secoo.com").build();
            AppService appService = retrofit.create(AppService.class);
            final OkHttpCall okHttpCall = appService.getCartAndBrand("2.0");
            okHttpCall.enqueue(new Callback() {
                @Override
                public void onResponse(String response) {
                    Log.e("response data",response);
                }
    
                @Override
                public void onFailure(Throwable t) {
                    Log.e("error",t.getMessage());
                }
            });
        }
    }
    

    按照代码执行一下,最后控制台日志成功输出 Json 字符串,代表我们的测试是成功的。

    Paste_Image.png

    相关文章

      网友评论

        本文标题:10分钟实现一个简单的Retrofit框架

        本文链接:https://www.haomeiwen.com/subject/nxdaqxtx.html