天天看點

動手撸個Retrofit

為啥要寫這個?

之前一直使用

OKHttp

,之前修改過鴻洋的

OKhttpUtils

增加了緩存功能。但對

Retrofit

并沒有使用過,前幾天按網上例子用了,感覺确實簡約多了。總覺得

Retrofit

就是個注解版

OKHttp

,應該寫個簡易版本很容易,就是個編譯時注解呗。于是沒看源碼寫個簡單版本。現在已經可以集合

Rxjava

,

Gson

。我試圖去想

Retrofit

作者是咋寫的。肯定有人說又造重複的輪子,放心,寫完我也不用,因為真的隻是demo,隻是為了增加自己程式設計的能力。還有我這個簡易版本和Retrofit原理是不是一樣我真不知道,我沒看過源碼,寫完準備再去看!哈哈。github位址:https://github.com/huangyanbin/SimpleRetrofit

咋開始呢?

我想着邊寫邊改,于是我首先建了個

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架構可以優雅生成代碼。百度查下就應該會用了,比較簡單。

Build project

還是不會調用

HttpProcessor

類呢,原來還需要我們告訴它在哪,這個時候

google

auto-service

上場了,不需要寫啥Xml什麼的,隻需要

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=
           
添加一個

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

攔截到

Get

Post

注解,然後再生成一個類,實作建立的

Http

請求接口,萬事開頭難,我們先在擷取

Get

Post

注解:
@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);
        ...
           
擷取到了

Get

Post

注解,然後就是擷取注解類的包名了:
//疊代
    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 = ;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 = ; 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(,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

,

Call

enqueue(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())");
        }
    }
           
還有就是如何

Converter

CallAdapter

,兩個其實邏輯是一樣的。隻不過

CallAdapterFatory

需要方法傳回類型的泛型,上面已經得到了。啦啦啦
//轉換接口
public interface IConverterFactory<T> {

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

}

//CallAdapter 接口
public interface ICallAdapterFactory {

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

}
           

Rxjava

Okttp

結合在一起:
@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

注解,其他

Put

Delete

等請求就不寫了。取一反三而已,還可以自定義

IConverterFactory

ICallAdapterFactory

.當然真正的

Retrofit

比我寫的複雜多了。後續有時間把多種緩存

http cache

功能加上。

github位址:https://github.com/huangyanbin/SimpleRetrofit