动手写个Retrofit简易版

1,751 阅读7分钟

Retrofit简易版本


为啥要写这个?

之前一直使用OKHttp,之前修改过鸿洋的OKhttpUtils增加了缓存功能。但对Retrofit并没有使用过,前几天按网上例子用了,感觉确实简约多了。总觉得Retrofit就是个注解版OKHttp,应该写个简易版本很容易,就是个编译时注解呗。于是没看源码写个简单版本。现在已经可以集合Rxjava,Gson。我试图去想Retrofit作者是咋写的。肯定有人说又造重复的轮子,放心,写完我也不用,因为真的只是demo,只是为了增加自己编程的能力。。github地址:github.com/huangyanbin…

咋开始呢?

我想着边写边改,于是我首先建了个module写了Get注解类,用于等下解析用。

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

真是很简单吧,然后就是建了个类HttpProcessor继承AbstractProcessor类,结果发现死活导不了AbstractProcessor类,坑爹啊,只好百度了,原来module必须用java library。只有删了重新建。

接着就是写HttpProcessor了,肯定有人问AbstractProcessor类干嘛的,建议百度。因为我也是百度的,哈哈。查完就知道主要就是 public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv)这个方法来干活的。就是编译的时候调用该方法,我们可以通过这个方法来自动生成代码。

问题又来了,咋生成代码。squareup 这个javapoet框架可以优雅生成代码。百度查下就应该会用了,比较简单。

 compile 'com.squareup:javapoet:1.9.0'

Build project还是不会调用HttpProcessor类呢,原来还需要我们告诉它在哪,这个时候googleauto-service上场了,不需要写啥Xml什么的,只需要

compile 'com.google.auto.service:auto-service:1.0-rc3'

HttpProcessor类上增加注解

@AutoService(Processor.class)

还有咋debug编译Build,方便我们看我们到底生成什么鬼东西。在gradle.properties添加两行代码

org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

添加一个Remote调试,然后在终端输入gradlew clean assembleDebug.然后可以快乐的debug了,如果你还是不会,去网上看下资料就会了。

第一步

使用Retrofit我们一般都是新建接口,然后写个抽象方法,类似下面的。

 @Get("{query}/pm10.json")
    Call<List<PM25>> getWeather(@Path("query") String query, @Query("city")String city,@Query("token")String token);

或者这样

 @Get("{query}/pm10.json")
    Observable<List<PM25>> getWeather(@Path("query") String query, @Query("city") String city, @Query("token") String token);

我第一反应,应该用HttpProcessor拦截到GetPost注解,然后再生成一个类,实现新建的Http请求接口,万事开头难,我们先在获取GetPost注解:

 @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        //获取到所有的Get注解
        Set<? extends Element> getSet = roundEnv.getElementsAnnotatedWith(Get.class);
        //获取到所有Post注解
        Set<? extends Element> postSet = roundEnv.getElementsAnnotatedWith(Post.class);
        //放入新的Set集合里面
        HashSet<Element> allSet = new HashSet<>();
        allSet.addAll(getSet);
        allSet.addAll(postSet);
        ...

获取到了GetPost注解,然后就是获取注解类的包名了:

    //迭代
    for (Element e : allSet) {
            //判断注解在方法上面
            if (e.getKind() != ElementKind.METHOD) {
                onError("Builder annotation can only be applied to method", e);
                return false;
            }
            //获取包名
            String packageName = elementUtils.getPackageOf(e).getQualifiedName().toString();
            ...

然后我们要依次解析我们的方法。我们先建一个类AnnotatedClass用于放注解接口相关信息以及生成类代码,然后在建AnnotatedMethod类放方法相关信息以及生成方法代码。感觉很复杂?一步步来:

首先我们获取包名,每个方法对应一个AnnotatedMethod类:

//将element转成方法Element
   ExecutableElement element = (ExecutableElement) e;
   //创建一个方法生成类
   AnnotatedMethod annotatedMethod = new AnnotatedMethod(element);
   //获取类名(包含包名的),以便生成AnnotatedClass类
   String qualifiedClassName = annotatedMethod.getQualifiedClassName();

将类名和AnnotatedClass做为key-value放在map,中,保证不会重复生成类代码:

    AnnotatedClass annotatedClass;
    //判断是否已经有这个AnnotatedClass类了
    if(classMap.containsKey(qualifiedClassName)){
        annotatedClass = classMap.get(qualifiedClassName);
    }else{
         //生成AnnotatedClass类
         annotatedClass = new AnnotatedClass(packageName,annotatedMethod.getSimpleClassName()
        ,annotatedMethod.getClassElement());
         classMap.put(qualifiedClassName,annotatedClass);
    }
    //将方法加入annotatedClass类
     annotatedClass.addMethod(annotatedMethod);
     onNote("retrofit build ---"+element.getSimpleName()+"--- method", e);

迭代出来调用生成AnnotatedClass代码:

//迭代调用annotatedClass方法生成类代码
 for (Map.Entry<String, AnnotatedClass> annotatedClassEntry : classMap.entrySet()) {
            AnnotatedClass annotatedClass = annotatedClassEntry.getValue();
            annotatedClass.generateCode(elementUtils,filer);
        }

如何生成代码(核心)

TypeSpec就是用于生成类信息的,采用Build方式来完成。

 public void generateCode(Elements elementUtils, Filer filer) {
        //获取接口名
        TypeName classType = TypeName.get(classElement.asType());

        TypeSpec.Builder typeBuilder =
        //类名 接口名+imp imp随便写的。
        TypeSpec.classBuilder(className+"Imp")
        //类访问权限
                    .addModifiers(Modifier.PUBLIC)
        //接口 实现我们包含Get注解的接口            
                    .addSuperinterface(classType)
                    //继承APIService类 ,这个类主要是辅助完成很多工作,等下会介绍
                    .superclass(APIService.class);
        //迭代生成方法代码            
        for (int i = 0;i < methods.size();i++) {
            AnnotatedMethod m = methods.get(i);
            MethodSpec methodSpec = m.generateMethodSpec();
            if(methodSpec !=null) {
                typeBuilder.addMethod(methodSpec);
            }
        }
        //创建一个java File
        JavaFile javaFile = JavaFile.builder(packageName, typeBuilder.build()).build();
        try {
            //写java文件
            javaFile.writeTo(filer);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

方法生成代码复杂很多,每行都注释。MethodSpec就是方法生成的类,也是通过build方式在构造的。思路就是拼接一个方法,在里面获取出用于请求Call,通过IConverterFactory转换成我们需要返回的类型,通过ICallAdapterFactory将请求回掉类型转换成我们需要的类型,我没有将所有代码都通过javapoet生成,而是通过继承APIService类,因为javapoet写起来确实比写代码累多了。哈哈!

public MethodSpec generateMethodSpec() {
        ExecutableElement methodElement = getMethodElement();
        //获取一个BaseAnnotatedParse 用于Get和Post不同解析
        BaseAnnotatedParse parse = getParser();
        if (parse == null) {
            return null;
        }
        //获取注解的url
        String url = parse.getUrl(methodElement);
        //获取方法返回类型
        TypeName returnType = TypeName.get(methodElement.getReturnType());
        //获取所有方法形参
        List<? extends VariableElement> params = methodElement.getParameters();
        //获取方法名
        String methodName = methodElement.getSimpleName().toString();
        //构造一个方法
        MethodSpec.Builder methodBuilder = MethodSpec
                .methodBuilder(methodName)
                .addModifiers(Modifier.PUBLIC)
                .returns(returnType);
        //通过拼接可以得到对应method类,用于请求后由于泛型擦除导致无法得到Type      
        StringBuffer methodFieldStr = new StringBuffer(" $T method = this.getClass().getMethod(\"" + methodName + "\"");
        //迭代参数
        for (int i = 0; i < params.size(); i++) {
            //获取参数Element
            VariableElement paramElement = params.get(i);
            //参数名称
            String paramName = paramElement.getSimpleName().toString();
            //参数类型 包含泛型
            TypeName paramsTypeName = TypeName.get(paramElement.asType());
            //添加参数
            methodBuilder.addParameter(paramsTypeName, paramName);
            //去除泛型
            String paramsTypeStr =  paramsTypeName.toString();
            if(paramsTypeStr.contains("<")){
                paramsTypeStr = paramsTypeStr.substring(0,paramsTypeStr.indexOf("<"));
            }
            methodFieldStr.append("," + paramsTypeStr + ".class");
            //判断形参是否包含Path注解,放入pathMap中
            Path path = paramElement.getAnnotation(Path.class);
            if (path != null) {
                String value = path.value();
                pathMap.put(value, paramName);
            }
             //判断形参是否包含Query注解,放入queryMap中
            Query query = paramElement.getAnnotation(Query.class);
            if (query != null) {
                String value = query.value();
                queryMap.put(value, paramName);
            }
        }
        methodFieldStr.append(")");
        methodBuilder.addStatement("String url = $S", url);
        //替换所有的Path
        for (Map.Entry<String, String> entry : pathMap.entrySet()) {
            methodBuilder.addStatement("url =  url.replaceAll(\"\\\\{$N\\\\}\",$N)"
                    , entry.getKey(), entry.getValue());
        }

        String returnTypeName = returnType.toString();
          //获取返回类型的泛型
        String generic = returnTypeName.substring(returnTypeName.indexOf("<"));
        //解析head 和 query
        parse.parse(methodElement, methodBuilder, queryMap);
        //创建Call
        methodBuilder.addStatement("$T$N call = new $T$N(createCall(request))", Call.class, generic, Call.class, generic);
        //设置CallAdapterFactory
        methodBuilder.addStatement("call.setCallAdapterFactory(getCallAdapterFactory())");
        methodBuilder.beginControlFlow("try");
        methodBuilder.addStatement(methodFieldStr.toString(), Method.class);
        //设置返回类型的泛型
        methodBuilder.addStatement("setCallGenericReturnType(method,call)");
        methodBuilder.endControlFlow();
        methodBuilder.beginControlFlow("catch (Exception e)");
        methodBuilder.addStatement("e.printStackTrace()");
        methodBuilder.endControlFlow();
        //最后通过ConverterFactory()转换成返回类型
        methodBuilder.addStatement("$T convertCall = ($T)(getConverterFactory().converter(call))", returnType, returnType);
        methodBuilder.addStatement("return convertCall");
        return methodBuilder.build();

    }

我们在APIService里面获取返回类型的泛型,最后传给Call,Callenqueue(Callback calback)传给callback,这样callback就知道它该解析成什么类型了。。。

 protected void setCallGenericReturnType(Method method,Call<?> call){
        Type type = method.getGenericReturnType();
        if (type instanceof ParameterizedType) {
            Type genericType = ((ParameterizedType) type).getActualTypeArguments()[0];
            call.setGenericType(genericType);
        }
    }

设置head

 public void setHead(ExecutableElement methodElement, MethodSpec.Builder methodBuilder) {
        if(methodElement.getAnnotation(Head.class) != null){
            Head header =  methodElement.getAnnotation(Head.class);
            String[] headerStr = header.value();
            methodBuilder.addStatement("$T.Builder headBuilder = new $T.Builder()", Headers.class,Headers.class);
            for (String headStr : headerStr) {
                methodBuilder.addStatement("headBuilder.add(\"$N\")", headStr);
            }
            methodBuilder.addStatement("requestBuilder.headers(headBuilder.build())");
        }
    }

还有就是如何ConverterCallAdapter,两个其实逻辑是一样的。只不过CallAdapterFatory需要方法返回类型的泛型,上面已经得到了。啦啦啦

//转换接口
public interface IConverterFactory<T> {

    <R> T converter(Call<R> call);

}

//CallAdapter 接口
public interface ICallAdapterFactory {

    <T> T converter(Response response, Type returnType);

}

RxjavaOkttp结合在一起:

 @Override
    public <R> Observable<R> converter(final Call<R> call) {
        return Observable.create(new ObservableOnSubscribe<R>() {
            @Override
            public void subscribe(final ObservableEmitter<R> e) throws Exception {
                call.enqueue(new Callback<R>() {
                    @Override
                    public void onResponse(okhttp3.Call call, R response) {
                        e.onNext(response);
                        e.onComplete();
                    }
                    @Override
                    public void onFailure(okhttp3.Call call, IOException e1) {
                        e.onError(e1);
                        e.onComplete();
                    }
                });
            }
        });

    }

最后通过Retrofit create来获取实现类的对象,虽然Class是一个接口,但是实际上获取的是clazz.getName()+Imp类,APIService这个类主要是用于设置Retrofit的配置,比如baseUrl等.

 public  <T> T create(Class<T> clazz) {
        String impClazz = clazz.getName()+"Imp"
        try {
            Class childClazz = Class.forName(impClazz);
            T t = (T) childClazz.newInstance();
            APIService apiService = (APIService)t;
            apiService.setOkHttpClient(builder.client);
            apiService.setConverterFactory(builder.converterFactory);
            apiService.setBaseUrl(builder.baseUrl);
            apiService.setCallAdapterFactory(builder.callAdapterFactory);
            return t;
        }catch (ClassNotFoundException e){
            throw new RetrofitException("ClassNotFoundException "+impClazz);
        } catch (IllegalAccessException e) {
            throw new RetrofitException("IllegalAccessException "+impClazz);
        } catch (InstantiationException e) {
            throw new RetrofitException("InstantiationException "+impClazz);
        }
    }

未完待续

总结

用了两天时间写这个思路实现,感觉这个最难的就是泛型,因为泛型会编译之后会被擦除,最后投机取巧了,用方法获取泛型,然后将泛型Type传给Callback。完成了Get, Post,Path Query,QuertMap,Head注解,其他PutDelete等请求就不写了。取一反三而已,还可以自定义IConverterFactoryICallAdapterFactory.当然真正的Retrofit比我写的复杂多了。后续有时间把多种缓存http cache功能加上。
github地址:github.com/huangyanbin…