Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
355 lines (260 sloc) 12 KB
title tags date keywords
自定义注解,打造自己的框架-最终篇
Android
2019-12-12 13:42:27 -0800
自定义注解
Annotation
AutoService
AbstractProcessor
反射
javapoet

该系列介绍自定义注解,完成如下功能。

  • @BindView 代替 findViewById
  • @ClickResponder 代替 setOnClickListener
  • @LongClickResponder 代替 setOnLongClickListener
  • @IntentValue 代替 getIntent().getXXX
  • @UriValue 代替 getQueryParameter
  • @BroadcastResponder 代替 registerReceiver
  • @RouterModule、@RouterPath 来进行反依赖传递调用 该系列源码在https://github.com/huangyuanlove/AndroidAnnotation 前几篇介绍了@BindView@ClickResponder@LongClickResponder@IntentValue@UriValue的实现,这一篇介绍一下@RouterModule@RouterPath的实现。

前提

在某些情况下,我们不得不为了某些情形而在代码层面妥协,写出了一坨又一坨的反人类代码。比如接手了一个古老的工程,A模块依赖B模块,但是现在的需求需要在B里面打开A页面,或者调用A的方法来做复杂的计算等等,于是就催生了这个注解。。。

声明注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)

public @interface RouterModule {
    String host();

    String schema() default "App";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface RouterPath {
    String value();
}

处理注解逻辑

这里我们新建一个处理器,专门用来处理这种逻辑,也重新生成另外一个辅助类

@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor {
    private Elements elementUtils;

    private Map<Element, TypeSpecWrapper> typeSpecWrapperMap = new HashMap<>();

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new LinkedHashSet<>();
        set.add(RouterModule.class.getCanonicalName());
        set.add(RouterPath.class.getCanonicalName());

        return set;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_7;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        typeSpecWrapperMap.clear();

        Set<? extends Element> routerModuleSet = roundEnv.getElementsAnnotatedWith(RouterModule.class);

        for (Element element : routerModuleSet) {
            RouterModule routerModule = element.getAnnotation(RouterModule.class);
            TypeSpecWrapper typeSpecWrapper = typeSpecWrapperMap.get(element);
            if (typeSpecWrapper == null) {
                String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
                ClassName hashMapClassName = ClassName.bestGuess("java.util.HashMap");
                ClassName methodClassName = ClassName.bestGuess("java.lang.reflect.Method");
                ClassName stringClassName = ClassName.bestGuess("java.lang.String");
                ParameterizedTypeName routerMapClassName = ParameterizedTypeName.get(hashMapClassName, stringClassName, methodClassName);

                ClassName targetClassName = ClassName.get(packageName, element.getSimpleName().toString());

                TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(routerModule.schema() + routerModule.host() + "$$Router")
                        .addField(routerMapClassName, "routerMap", Modifier.PRIVATE)
                        .addField(targetClassName, "target");
                typeSpecWrapper = new TypeSpecWrapper(typeSpecBuilder, "com.huangyuanlove.router");
                typeSpecWrapperMap.put(element, typeSpecWrapper);


                MethodSpec.Builder constructorBuilder = typeSpecWrapper.getMethodBuilder(MethodSpec.constructorBuilder().toString());
                if (constructorBuilder == null) {
                    constructorBuilder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addException(Exception.class);
                }
                constructorBuilder.addStatement("this.target =  new $T()", targetClassName)
                        .addStatement("this.routerMap = new $T()", routerMapClassName);


                List<? extends Element> lists = element.getEnclosedElements();
                for (Element element1 : lists) {
                    ExecutableElement temp = (ExecutableElement) element1;

                    RouterPath routerPath = temp.getAnnotation(RouterPath.class);
                    if (routerPath != null) {
                        constructorBuilder.addStatement("this.routerMap.put($S,target.getClass().getMethod($S,$L))", routerPath.value(), element1.getSimpleName().toString(), paramsClassString(temp));
                    }

                }

                typeSpecWrapper.putMethodBuilder(constructorBuilder);

                ClassName paramWrapperName = ClassName.bestGuess("com.huangyuanlove.view_inject_api.router.RouterParamWrapper");
                ClassName routerDelegateName = ClassName.bestGuess("com.huangyuanlove.view_inject_api.router.RouterDelegate");

                MethodSpec.Builder invokeBuilder = MethodSpec.methodBuilder("invoke")
                        .addModifiers(Modifier.PUBLIC)
                        .addException(Exception.class)
                        .addParameter(String.class, "path")
                        .addParameter(paramWrapperName, "paramWrapper")
                        .addStatement("$T method = this.routerMap.get($L)", methodClassName, "path")
                        .beginControlFlow("if(method == null)")
                        .addStatement(" throw new Exception(\"can not find method which map \" +path)")
                        .endControlFlow()
                        .addStatement("return $T.invoke(method,target,paramWrapper)", routerDelegateName)
                        .returns(Object.class);

                typeSpecWrapper.putMethodBuilder(invokeBuilder);

            }

        }


        for (Map.Entry<Element, TypeSpecWrapper> entry : typeSpecWrapperMap.entrySet()) {

            entry.getValue().writeTo(processingEnv.getFiler());
        }

        return true;
    }

    private String paramsClassString(ExecutableElement temp) {

        if (temp == null) {
            return null;
        }

        List<? extends VariableElement> parameters = temp.getParameters();

        if (parameters == null || parameters.size() == 0) {
            return null;
        }

        String[] result = new String[parameters.size()];

        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < parameters.size(); i++) {
            result[i] = parameters.get(i).asType().toString() + ".class";
            sb.append(parameters.get(i).asType().toString() + ".class");
            sb.append(",");
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }
}

主要就是收集注解信息,生成注解类,在调用对应注解路径时,调用api模块中对应的代理类RouterDelegate,最终由使用者调用Router中的RouterBuilder来调用对应路径注解的方法。

给使用者提供调用方法

api模块中,新建RouterParamWrapper作为我们注解参数的包装类

public class RouterParamWrapper {

    private Object []paramArray;

    public RouterParamWrapper(Object [] paramArray) {
        this.paramArray = paramArray;
    }
    public Object[] getParamArray() {
        return paramArray;
    }
}

api模块中,新建RouterDelegate作为我们注解的代理类,实际上也就是一个中间层

public class RouterDelegate {

    public static Object invoke(Method method, Object target, RouterParamWrapper paramWrapper) throws Exception{
       return  method.invoke(target,paramWrapper.getParamArray());
    }
}

然后提供给调用者一个调用方法

public class Router {

    public  interface  InvokeResultListener<T>{
        void  onError(Exception e);
        void onSuccess( T result);
    }

    private  static final String PACKAGE_NAME = "com.huangyuanlove.router";

    public static RouterBuilder to(String path){
        return new RouterBuilder(path);
    }

    public static class RouterBuilder{
        private String path;
        private Object[] paramArray;

        private RouterBuilder(String path) {
            this.path = path;
        }

        public RouterBuilder addParam(Object ... paramArray){
            this. paramArray = paramArray;
            return  this;

        }

        public void done(){
            done(null);
        }

        public void done(InvokeResultListener listener){

            try {
                Uri uri  = Uri.parse(path);
                String schema = uri.getScheme();
                String host = uri.getHost();
                String path = uri.getPath();

                Class routerInject = Class.forName(PACKAGE_NAME +"." + schema + host +"$$Router");
                Constructor constructor =  routerInject.getConstructor();
                constructor.setAccessible(true);
                RouterParamWrapper paramWrapper = new RouterParamWrapper(paramArray);

                Method invokeMethod = routerInject.getMethod("invoke",String.class,RouterParamWrapper.class);
                invokeMethod.setAccessible(true);
                Object result = invokeMethod.invoke(constructor.newInstance(),path,paramWrapper);
                if(listener!=null){
                    listener.onSuccess(result);
                }
            }catch (Exception e){

                if(listener!=null){
                    listener.onError(e);
                }
            }
        }
    }
}

这样使用者调用Router.to(String path).addParam(Object ... param).done就可以调用了。

使用

我们可以在一个模块中提供一个功能类,用来暴露出提供给其他模块使用的方法

@RouterModule(schema = "App",host = "main")
public class MainProvider {

    @RouterPath(value = "/toMain")
    public void startMain(Activity context, int id){
        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra("id",id);
        context.startActivity(intent);
    }

    @RouterPath(value = "/toMainWithResult")
    public void startMain(Activity context, String title,int requestCode){
        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra("title",title);
        context.startActivity(intent);
    }

    @RouterPath(value = "/getInt")
    public int getIntValue(String s){
        return s.length();
    }
}

然后我们就可以在其他模块中使用Router进行任意调用了,比如

@ClickResponder(idStr = "toAppMainActivity")
public void toAppMainActivity(View v){

    Router.to("App://main/toMain").addParam(this,123).done(new Router.InvokeResultListener() {
        @Override
        public void onError(Exception e) {
            Toast.makeText(EXT_MainActivity.this,e.toString(),Toast.LENGTH_SHORT).show();

        }

        @Override
        public void onSuccess(Object o) {

        }
    });

}

@ClickResponder(idStr = "invoke_main_method")
public void invokeMainMethod(View v){

    Router.to("App://main/getInt").addParam("12345678").done(new Router.InvokeResultListener<Integer>() {
        @Override
        public void onError(Exception e) {
            Toast.makeText(EXT_MainActivity.this,e.toString(),Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onSuccess(Integer result) {
            Toast.makeText(EXT_MainActivity.this,result+"",Toast.LENGTH_SHORT).show();

        }
    });

}

其实我也不想搞出来这种东西,但毕竟是时间紧、任务重,先这么用着吧。

结语

终于把注解这一块写完了,也算是对自己写注解的一个总结吧。


以上

You can’t perform that action at this time.