為啥要寫這個?
之前一直使用,之前修改過鴻洋的
OKHttp
增加了緩存功能。但對
OKhttpUtils
并沒有使用過,前幾天按網上例子用了,感覺确實簡約多了。總覺得
Retrofit
就是個注解版
Retrofit
,應該寫個簡易版本很容易,就是個編譯時注解呗。于是沒看源碼寫個簡單版本。現在已經可以集合
OKHttp
,
Rxjava
。我試圖去想
Gson
作者是咋寫的。肯定有人說又造重複的輪子,放心,寫完我也不用,因為真的隻是demo,隻是為了增加自己程式設計的能力。還有我這個簡易版本和Retrofit原理是不是一樣我真不知道,我沒看過源碼,寫完準備再去看!哈哈。github位址:https://github.com/huangyanbin/SimpleRetrofit
Retrofit
咋開始呢?
我想着邊寫邊改,于是我首先建了個寫了
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
的
上場了,不需要寫啥Xml什麼的,隻需要
auto-service
在 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
類,APIService這個類主要是用于設定
clazz.getName()+Imp
的配置,比如
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