天天看點

Android 利用APT對網絡請求進行極簡封裝

團隊并行開發,每個人對自己子產品的api進行封裝,

public interface UserAPI {


    @POST("/mobile/settings/login.html")
    @FormUrlEncoded
    Flowable<UserEntity> doLogin(@QueryMap Map<String, Object> map);


    @POST("/mobile/settings/logout.html")
    @FormUrlEncoded
    Flowable<Boolean> doLogout();



}
           
public interface IRetrofitWeather {

    /**
     * retrofit 封裝
     * @param location
     */
    @FormUrlEncoded
    @POST("telematics/v3/weather?")
    Call<WeatherApiData> getWeather(@Field("location") String location,
                                  @Field("output") String ouput,
                                  @Field("ak") String ak);


}
           

然後利用Retrofit的create方法建立出對應的apiservice進行網絡請求,各個子產品都需要含有apiservice執行個體的簡單單例封裝,那麼多個子產品就有多個單例,這個時候就需要工廠模式進行設計。

@Override
    public void getWeather(String location, Callback<WeatherApiData> callback) {

        IRetrofitWeather api = retrofit.create(IRetrofitWeather.class);

        Call<WeatherApiData> call = api.getWeather(location, APIConstant.URL.OUTPUT, APIConstant.URL.AK);

        call.enqueue(callback);

    }
           

如果子產品需要調用User子產品的接口時,就會有很麻煩。而且不可避免的抽象工廠需要設計多個抽象類和方法,使得設計非常繞,層次多,類多。那麼我們利用APT(Annotation Processing Tool)機制,動态的生成HttpClient可以解決以上問題。

@HttpClient
public interface UserAPI 
           

首先,我們在每個需要進行網絡調用的api加上@HttpClient标簽,

public final class HttpClient {
  /**
   * @created by apt
   */
  public static Flowable<LoginEntity> login(String username, String password, Map<String, Object> map) {
    return Api.getInstance().retrofit.create(com.jason.UserService.class).login(username,password,map).compose(RxSchedulers.io_main());
  }
  public static Flowable<WeatherEntity> getWeather(String location, Map<String, Object> map) {
    return Api.getInstance().retrofit.create(com.jason.WeatherService.class).getWeather(location,map).compose(RxSchedulers.io_main());
  }
}
           

在編譯時系統HttpClientProccessor運作時會自動生成HttpClient類,并且各個子產品的api方法都會在HttpClient中生成,HttpClient中封裝了Retrofit通用單例和Rxjava的通用部分,免去了上面封裝需要的callback。

HttpClient.login(username, pwd, defaultMap)
                .subscribe(loginEntity -> {
                    Log.i("LoginPresenter", "onNext userEntity:"+loginEntity);
                    getView().loginSuccess(loginEntity);
                }, throwable -> {
                    Log.i("LoginPresenter", "error msg:"+throwable.toString());
                })
           

各個子產品使用時可直接使用HttpClient中的public靜态方法,Rxjava免去了我們去寫另外一個包含callback或listener的接口,也就不需要工廠類做解耦設計。

public class HttpClientProcessor implements IProcessor {
    @Override
    public void process(RoundEnvironment roundEnv, AnnotationProcessor mAbstractProcessor) {
        String CLASS_NAME = "HttpClient";
        
        TypeSpec.Builder tb = classBuilder(CLASS_NAME).addModifiers(PUBLIC, FINAL).addJavadoc("@API factory created by apt");
        try {
            for (TypeElement element : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(HttpClient.class))) {
                mAbstractProcessor.mMessager.printMessage(Diagnostic.Kind.NOTE, "正在處理: " + element.toString());
                for (Element e : element.getEnclosedElements()) {
                    ExecutableElement executableElement = (ExecutableElement) e;
                    MethodSpec.Builder methodBuilder =
                            MethodSpec.methodBuilder(e.getSimpleName().toString())
                                    .addJavadoc("@created by apt")
                                    .addModifiers(PUBLIC, STATIC);

                        methodBuilder.returns(TypeName.get(executableElement.getReturnType()));
                        String paramsString = "";
                        for (VariableElement ep : executableElement.getParameters()) {
                            methodBuilder.addParameter(TypeName.get(ep.asType()), ep.getSimpleName().toString());
                            paramsString += ep.getSimpleName().toString() + ",";
                        }
                        methodBuilder.addStatement(
                                "return $T.getInstance()" +
                                        ".service.$L($L)" /*+
                                        ".compose($T.io_main())"*/
                                , ClassName.get("com.jason.api", "OkHttpClient")
                                , e.getSimpleName().toString()
                                , paramsString.substring(0, paramsString.length() - 1)
                                
                        tb.addMethod(methodBuilder.build());
                    
                }
            }
            JavaFile javaFile = JavaFile.builder(Utils.PackageName, tb.build()).build();// 生成源代碼
            javaFile.writeTo(mAbstractProcessor.mFiler);// 在 app module/build/generated/source/apt 生成一份源代碼
        } catch (FilerException e) {
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
           

HttpClientProccessor的代碼,利用AOP 切片程式設計的思想,在編譯時去周遊所有Java檔案,包含@HttpClient注解的類的方法,都會引入到新生成的HttpClient這個類中。

public final class HttpClient {
  /**
   * @created by apt
   */
  public static Flowable<LoginEntity> login(String username, String password, Map<String, Object> map) {
    return OkHttpClient.getInstance().retrofit.create(com.jason.UserService.class).login(username,password,map).compose(RxSchedulers.io_main());
  }
  public static Flowable<WeatherEntity> getWeather(String location, Map<String, Object> map) {
    return OkHttpClient.getInstance().retrofit.create(com.jason.WeatherService.class).getWeather(location,map).compose(RxSchedulers.io_main());
  }
}
           

Api是對對各個子產品的通用的Retrofit serviceApi,我們看到HttpClient将Weather和User的接口都加入進來,所有接口都在HttpClient中,HttpClient是自動生成,

利用APT的方式,我們生成HttpClient的方式免去了多人去修改工廠類的情況,這種方式由于編譯時需要周遊的關系,編譯時會慢一點。

感謝North文章的幫助

http://www.jianshu.com/p/dca3e2c8608a