Android基础:使用注解Annotation Processor实现一个简单的路由

1,362 阅读2分钟

一、Annotation Processor简介

Annotation Processor表示注解处理器;在java代码的编译阶段,Annotation Processor会对注解进行处理,在这个机制上,我们可以自定义Annotation Processor来实现对注解的不同处理。
在Dagger、butterknife等开源库中都使用到了Annotation Processor。

二、背景描述

假设:需要创建一个路由管理类RouterManager()来实现对不同的scheme做不同的分发,即这个类可以接收一个string参数,然后根据这个string参数跳转不同的逻辑;我们可以怎么实现呢?

2.1 定义Router接口如下

public interface IRouter {
    void dispatch();
}

2.2 实现IRouter

public class CodeRouter implements IRouter {
    @Override
    public void dispatch() {
        Log.d(ROUTER_TAG, "CodeRouter");
    }
}

或者:

public class HttpRouter implements IRouter {
    @Override
    public void dispatch() {
        Log.d(ROUTER_TAG, "HttpRouter");
    }
}

2.3 新建RouterManager类

public class RouterManager {
    /**
    * key是scheme value是对应的类名;
    */
    private HashMap<String, String> map = new HashMap<>();
    /**
    * key是scheme value是对应的类示例;
    */
    private HashMap<String, IRouter> routerMap = new HashMap<>();

    private static final class Host {
        private static final RouterManager instance = new RouterManager();
    }

    private RouterManager() {
    }

    /**
    * 单例模式
    */
    public static RouterManager getInstance() {
        return Host.instance;
    }
    
    /**
    * 初始化Router列表
    */
    public void initRouter() {
        RouterManager.getInstance().register("bc://code", "com.bc.router.CodeRouter");
        RouterManager.getInstance().register("bc://http", "com.bc.router.HttpRouter");
    }

    /**
    * 注册Router
    */
    public void register(String uri, String className) {
        if (className != null && uri != null) {
            map.put(uri, className);
            routerMap.put(uri, null);
        }
    }

    /**
    * 输出所有Router
    */
    public void showAllScheme() {
        System.out.println("RouterManager:" + map.toString());
    }

    /**
    * 根据scheme执行不同的分发逻辑
    */
    public boolean dispatch(String scheme) {
        try {
            if (routerMap.containsKey(scheme)) {
                IRouter router = routerMap.get(scheme);
                if (router == null) {
                    router = (IRouter) Class.forName(map.get(scheme)).newInstance();
                    routerMap.put(scheme, router);
                }
                router.dispatch();
                return true;
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return false;
    }
}

2.4 初始化及使用

class Activity{
    
    public void onCreate() {
        RouterManager.getInstance().initRouter();
    }
    
    public void onClick() {
        RouterManager.getInstance().dispatch("bc://code");
    }
}

这种实现方式的缺点是:每次有新增的Router时都需要在RouterManager.initRouter中调用一次注册register方法。在有多个模块时这种方式不够灵活,下面改用注解+Annotation Processor的实现方法。

三、Annotation Processor的实现方法

(1)新建Java Library模块用于存放annotation以及基础RouterManager。
(2)新建Java Library模块lib_compiler用于AnnotationProcessor注解处理过程。
image.png

3.1 自定义注解RouterProvider

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface RouterProvider {
    public String uri() default "";
}

3.2 在IRouter实现类上添加注解

@RouterProvider(uri = "bc://code")
public class CodeRouter implements IRouter {
    @Override
    public void dispatch() {
        Log.d(ROUTER_TAG, "CodeRouter");
    }
}

或者

@RouterProvider(uri = "bc://http")
public class HttpRouter implements IRouter {
    @Override
    public void dispatch() {
        Log.d(ROUTER_TAG, "HttpRouter");
    }
}

3.3 新建RouterManager

与之前实现唯一不同地方在于initRouter(),这里调用了一个TestRouterInit.initRouter()方法来实现初始化,TestRouterInit及其initRouter()方法都是在后面的Annotation Processor过程中动态生成的,定义如下:

public class RouterManager {

    public static final String INIT_CLASS = "com.bc.router.TestRouterInit";
    public static final String INIT_PACKAGE = "com.bc.router";
    public static final String INIT_SIMPLE_CLASS = "TestRouterInit";
    public static final String INIT_METHOD = "initRouter";
    public static final String ROUTER_TAG = "router";
    
    /**
    * key是scheme value是对应的类名;
    */
    private HashMap<String, String> map = new HashMap<>();
    /**
    * key是scheme value是对应的类示例;
    */
    private HashMap<String, IRouter> routerMap = new HashMap<>();

    private static final class Host {
        private static final RouterManager instance = new RouterManager();
    }

    private RouterManager() {
    }

    /**
    * 单例模式
    */
    public static RouterManager getInstance() {
        return Host.instance;
    }
    
    /**
    * 初始化Router列表:唯一不同地方
    */
    public void initRouter() {
        try {
            //调用动态生成的文件
            Class.forName(INIT_CLASS).getMethod(INIT_METHOD).invoke(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
    * 注册Router
    */
    public void register(String uri, String className) {
        if (className != null && uri != null) {
            map.put(uri, className);
            routerMap.put(uri, null);
        }
    }

    /**
    * 输出所有Router
    */
    public void showAllScheme() {
        System.out.println("RouterManager:" + map.toString());
    }

    /**
    * 根据scheme执行不同的分发逻辑
    */
    public boolean dispatch(String scheme) {
        try {
            if (routerMap.containsKey(scheme)) {
                IRouter router = routerMap.get(scheme);
                if (router == null) {
                    router = (IRouter) Class.forName(map.get(scheme)).newInstance();
                    routerMap.put(scheme, router);
                }
                router.dispatch();
                return true;
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return false;
    }
}

3.4 新建Annotation Processor模块

新建Annotation Processor模块lib_compiler,该模块需要依赖auto-service、javapoet,其build.gradle文件如下:

apply plugin: 'java-library'
apply plugin: 'kotlin'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    // auto-service
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    // javapoet
    implementation 'com.squareup:javapoet:1.11.1'
    // 这个模块存放着我们需要处理的注解
    implementation project(path: ":annotation")
}

其中:
(1)auto-service:AutoService是Google开源的用来方便生成符合ServiceLoader规范的开源库
(2)Javapoet:JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件。

3.5 自定义的Annotation Processor

继承AbstractProcessor来自定义Annotation Processor,并处理注解:

@AutoService(Processor.class)
public class TestRouterProcessor extends AbstractProcessor {

    public static final String ROOT_INIT = RouterManager.INIT_PACKAGE;
    public static final String INIT_CLASS = RouterManager.INIT_SIMPLE_CLASS;
    public static final String INIT_METHOD = RouterManager.INIT_METHOD;

    @Override public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    //这个方法非常必要,否则将不会执行到process()方法
    @Override public Set<String> getSupportedAnnotationTypes() {
        return Collections.singleton(RouterProvider.class.getCanonicalName());
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        if (annotations == null || annotations.isEmpty()) {
            return false;
        }
        try {
            //使用javapoet来动态生成代码:初始化函数init()
            MethodSpec.Builder mainMethodBuilder = MethodSpec.methodBuilder(INIT_METHOD)
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class);
            for (Element elementItem : env.getElementsAnnotatedWith(RouterProvider.class)) {
                if (!(elementItem instanceof TypeElement)) {
                    continue;
                }
                //获取注解中的内容
                TypeElement element = (TypeElement) elementItem;
                String className = element.getQualifiedName().toString();
                String uri = element.getAnnotation(RouterProvider.class).uri();
               // 方法内的代码是对注解挨个调用了register()方法 mainMethodBuilder.addStatement("$T.getInstance().register($S,$S)", RouterManager.class, uri, className);
            }
            //使用javapoet来动态生成代码:初始化类com.bc.router.TestRouterInit
            TypeSpec testRouterInit = TypeSpec.classBuilder(INIT_CLASS)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(mainMethodBuilder.build())
                    .build();
            JavaFile javaFile = JavaFile.builder(ROOT_INIT, testRouterInit)
                    .build();
            Filer filer = processingEnv.getFiler();
            javaFile.writeTo(filer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}

以上自定义的注解处理过程,最终生成了一个类TestRouterInit:

public class TestRouterInit() {
    public static void initRouter() {
        RouterManager.getInstance().register("bc://code", "com.bc.router.CodeRouter");
        RouterManager.getInstance().register("bc://http", "com.bc.router.HttpRouter");
    }
}

3.6 依赖自定义的Annotation Processor

annotationProcessor project(":lib_compiler")

在模块下添加了annotationProcessor后,自定义的AnnotationProcessor就会在编译过程中对注解进行处理。

3.7 初始化及使用

同上;

class Activity{
    
    public void onCreate() {
        RouterManager.getInstance().initRouter();
    }
    
    public void onClick() {
        RouterManager.getInstance().dispatch("bc://code");
    }
}

The End

欢迎关注我,一起解锁更多技能:BC的主页~~💐💐💐 个人信息汇总.png