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
類呢,原來還需要我們告訴它在哪,這個時候auto-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
攔截到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 = 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
,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地址:github.com/huangyanbin…