RxBus支持注解形式

    同事写了RxBus框架,但不支持注解形式,刚好大家都有需求,就加入了注解,在编译时期生成固定的注册与解注册代码。

常规用法

先声明一个需要监听事件的Listener
    public RxBus.OnEventListener textEventListener = new RxBus.OnEventListener() {
        @Override
        public void onEvent(Object o) {
            ExpEvent expEvent = (ExpEvent) o;
            RevFragment.this.postTV.setText(postTV.getText()+","+expEvent.value);
        }
    };
然后在初始化页面时注册 
 RxBus.getDefault().register(textEventListener, ExpEvent.class);
接着在页面销毁时解注册
 RxBus.getDefault().unregister(textEventListener);

使用注册用法

声明一个监听的Event方法,参数为接收的参数
 @InjectMethodBind
    public void textEvent(ExpE e) {
        postMethodTV.setText(postMethodTV.getText() + "," + (e.value));
    }
在页面初始话时注册
RxBus.getDefault().inject(this);
页面销毁时解注册
RxBus.getDefault().unInject(this);
编译时期自动生成的代码
public class RevFragment_BindInject implements Inject<RevFragment> {
    public RxBus.OnEventListener textEvent_bind;

    @Override
    public void inject(final RevFragment host) {
        textEvent_bind = new RxBus.OnEventListener() {
            @Override
            public void onEvent(final Object object) {
                ExpE o = (ExpE)object;
                host.textEvent(o);
            }
        };
        RxBus.getDefault().register(textEvent_bind,0,ExpE.class);
    }

    @Override
    public void unInject(final RevFragment host) {
        RxBus.getDefault().unregister(textEvent_bind);
    }
}

RxBus注解原理

注解(Annotation)
也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释.

元注解
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
  • @Target(用于描述注解的使用范围)
  1. CONSTRUCTOR:用于描述构造器
  2. FIELD:用于描述域
  3. LOCAL_VARIABLE:用于描述局部变量
  4. METHOD:用于描述方法
  5. PACKAGE:用于描述包
  6. PARAMETER:用于描述参数
  7. TYPE:用于描述类、接口(包括注解类型) 或enum声明
  • @Retention(表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效))
  1. SOURCE:在源文件中有效(即源文件保留 源码时)
  2. CLASS:在class文件中有效(即class保留 编译时)
  3. RUNTIME:在运行时有效(即运行时保留 运行时)
  • @Documented(描述其它类型的annotation应该被作为被标注的程序成员的公共API)
  1. Documented是一个标记注解,没有成员。
  • @Inherited (是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。)
  1. 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值
自定义一个注册
/**
 * Created by huilin on 2017/7/14.
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface InjectMethodBind {
    int type() default 0;
}
因为我们使用范围是在方法上,所以Target使用ElementType.METHOD,编译时期用到,所以Retention使用RetentionPolicy.CLASS。注解在方法上,参数名字就是方法名字type,参数类型就是方法返回类型int。

运行时读取注解方法
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:
       方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
  方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  方法3:boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
 Method[] fucs = clazz.getDeclaredMethods();
       
     for(Method fuc:fucs){
          if(fuc.isAnnotationPresent(InjectMethodBind.class)){
              InjectMethodBind bindView = (InjectMethodBind) fuc.getAnnotation(InjectMethodBind.class);
          }
     }
但是由于反射造成一定运行效率的损耗,所以我们会更青睐于编译时注解的框架,编译时生成代码。用到了APT(Annotation Processing Tool ),可以在代码编译期解析注解,并且生成新的 Java 文件,减少手动的代码输入。
编写一个编译时解析注解框架

rxbus-inject --API android模块
rxbus-annotation --注解相关 java模块
rxbus-compile– 注解处理器 java模块

第一步自定义注解,刚刚上边已经写了我们要用的自定义注解
第二步创建使用的API
public interface Inject<T> {
    void inject(T host);

    void unInject(T host);
}

我们需要一个绑定的入口inject与一个解绑的入口unInject,我们编译生成的代码将会是下面这样,两个方法参数都是当前需要接收页面所在的类,可以activity fragment 普通类...

public class RevFragment_BindInject implements Inject<RevFragment> {
    public RxBus.OnEventListener textEvent_bind;

    @Override
    public void inject(final RevFragment host) {
        textEvent_bind = new RxBus.OnEventListener() {
            @Override
            public void onEvent(final Object object) {
                ExpE o = (ExpE)object;
                host.textEvent(o);
            }
        };
        RxBus.getDefault().register(textEvent_bind,0,ExpE.class);
    }

    @Override
    public void unInject(final RevFragment host) {
        RxBus.getDefault().unregister(textEvent_bind);
    }
}
根据上面代码,就可以总结出inject()方法怎么写

public class EventInject {

    private static final HashMap<String, Inject> injectHashMap = new HashMap<>();

    public static void inject(Object host) {
        String className = host.getClass().getName();
        Inject inject = injectHashMap.get(className);

        if (inject == null) {
            try {
                Class<?> aClass = Class.forName(className + "_BindInject");
                inject = (Inject) aClass.newInstance();
                inject.inject(host);
                injectHashMap.put(className, inject);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else{
            injectHashMap.get(className).inject(host);
        }
    }

    public static void unInject(Object host) {
        String className = host.getClass().getName();
        Inject inject = injectHashMap.get(className);
        if (inject != null) {
            inject.unInject(host);
        }
    }
}
首先我们需要一个接口 Inject,然后为每一个注解类都生成一个对应的_BindInject类并且实现这个接口,然后实现具体的注入逻辑。在 inject() 方法中首先找到调用者对应的 Inject实现类,然后调用其内部的具体逻辑来达到注入的目的。
另外代码中使用到了一点反射,所以为了提高效率,避免每次注入的时候都去找 Inject对象,这里用一个 Map 将第一次找到的对象缓存起来,后面用的时候直接从 Map 里面取。到此,API 模块的设计基本搞定了,接下来就是去通过注解处理器为每一个注解类生成 Inject的实现类。

创建注解处理器
创建rxbus-compile – 注解处理器 java模块
添加所需要的依赖
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile project(':rxbus-annotation')//前面我们定义的注解
    compile 'com.squareup:javapoet:1.7.0'//是square公司的开源库。正如其名,java诗人,生成java源文件
    compile 'com.google.auto.service:auto-service:1.0-rc2'//是 Google的库,主要用于注解 Processor,对其生成 META-INF 配置信息。
}
编写模板代码注解处理器
@AutoService(Processor.class)
public class BindEventProcesser extends AbstractProcessor {
    /**
     * 文件辅助类
     */
    private Filer filer;
    /**
     * 元素辅助类
     */
    private Elements elements;
    /**
     * 存储注解类的映射
     */
    private HashMap<String, AnnotatedClass> annotatedClassHashMap;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        elements = processingEnv.getElementUtils();
        annotatedClassHashMap = new HashMap<>();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        annotations.clear();
        processEventMethod(roundEnv);
        for (AnnotatedClass annotatedClass : annotatedClassHashMap.values()){
            try {
                annotatedClass.generateFile().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }

    /**
     * 解析注解
     * @param roundEnv
     */
    private void processEventMethod(RoundEnvironment roundEnv) {
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(InjectMethodBind.class);
        for (Element element: elementsAnnotatedWith){
            AnnotatedClass annotatedClass = getAnnotatedClass(element);
            annotatedClass.addBindMethod(new BindEventMethod(element));
        }
    }

    /**
     * 指定使用的java版本
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     *
     *指定要被注解处理器处理的注解
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>();
        set.add(InjectMethodBind.class.getCanonicalName());
        return set;
    }

    private AnnotatedClass getAnnotatedClass(Element element) {
        TypeElement className = (TypeElement) element.getEnclosingElement();
        String fullName = className.getQualifiedName().toString();
        AnnotatedClass annotatedClass = annotatedClassHashMap.get(fullName);
        if (annotatedClass == null) {
            annotatedClass = new AnnotatedClass(className, elements);
            annotatedClassHashMap.put(fullName, annotatedClass);
        }
        return annotatedClass;
    }

}
具体变量含义已注释在代码中
用 @AutoService 来注解这个处理器,可以自动生成配置信息。
在 init() 可以初始化拿到一些实用的工具类在 getSupportedAnnotationTypes() 方法中返回所要处理的注解的集合。getSupportedSourceVersion() 方法中返回 Java 版本。这几个方法写法基本上都是固定的,重头戏在 process() 方法,可以获取到要处理的所有注解集合,遍历即可。
Element元素
Element 代表程序的元素,在注解处理过程中,编译器会扫描所有的Java源文件,并将源码中的每一个部分都看作特定类型的Element。它可以代表包、类、接口、方法、字段等多种元素种类,具体看getKind()方法中所指代的种类
PackageElement 表示一个包程序元素
TypeElement 表示一个类或接口程序元素
VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
ExecutableElement 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数
public class Sample         // TypeElement
       {  // TypeParameterElement

    private int num;        // VariableElement
    String name;            // VariableElement

    public Sample() {}      // ExecuteableElement

    public void setName(    // ExecuteableElement
            String name     // VariableElement
            ) {}
}
这些 Element 元素,相当于 XML 中的 DOM 树,可以通过一个元素去访问它的父元素或者子元素。element.getEnclosingElement();// 获取父元素    element.getEnclosedElements();// 获取子元素

BindEventMethod类
public class BindEventMethod {
 
    private ExecutableElement executableElement;
    private int type;

    public BindEventMethod(Element element) {
        if (element.getKind() != ElementKind.METHOD) {
            throw new IllegalArgumentException("not method");
        }
        executableElement = (ExecutableElement) element;
        if(getParameters().size() != 1){
            throw new IllegalArgumentException("parameter not equal to 1");
        }
        InjectMethodBind methodBind = executableElement.getAnnotation(InjectMethodBind.class);
        type = methodBind.type();
    }

    public int getType() {
        return type;
    }

    public Name getMethodName() {
        return executableElement.getSimpleName();//获取注解方法的方法名
    }
    public List<? extends VariableElement> getParameters(){
        return executableElement.getParameters();//获取注解方法上的参数
    }
}
AnnotatedClass类
AnnotatedClass 表示一个注解类,里面声明了 private ArrayList<BindEventMethod> bindEventMethods;存放注解类所生有被注解的方法
  public JavaFile generateFile() {
        //生成类名
        TypeSpec.Builder classBuilder = TypeSpec.classBuilder(typeElement.getSimpleName() + "_BindInject")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Common.RXBUS_INJECT_PACK_NAME, "Inject"), TypeName.get(typeElement.asType())));
        //声明inject方法
        MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("inject")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.get(typeElement.asType()), "host", Modifier.FINAL);
        //声明unInject方法
        MethodSpec.Builder unInjectBuilder = MethodSpec.methodBuilder("unInject")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.get(typeElement.asType()), "host", Modifier.FINAL);
//        FieldSpec.Builder host = FieldSpec.builder(TypeName.get(typeElement.asType()),
//                "m"+typeElement.getSimpleName().toString(), Modifier.PUBLIC);
//        classBuilder.addField(host.build());
//        injectBuilder.addStatement("m$N = host",typeElement.getSimpleName().toString());
        //解析注解方法
        for (BindEventMethod bindEventMethod : bindEventMethods) {
            //声明匿名内部类OnEventListener
            TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("")
                    .addSuperinterface(ClassName.get(Common.RXBUS_PACK_NAME, "RxBus", "OnEventListener"))
                    .addMethod(MethodSpec.methodBuilder("onEvent")
                            .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC)
                            .returns(TypeName.VOID)
                            .addParameter(TypeName.OBJECT, "object", Modifier.FINAL)
                            .addStatement("$T o = ($T)object", TypeName.get(bindEventMethod.getParameters().get(0).asType()),
                                    TypeName.get(bindEventMethod.getParameters().get(0).asType()))
                            .addStatement("host.$N($L)", /**typeElement.getSimpleName().toString(),**/ bindEventMethod.getMethodName(), "o").build()
//                                    .addStatement("m$N.$N($L)", typeElement.getSimpleName().toString(), bindEventMethod.getMethodName(), "o").build()
                    ).build();
            //声明类成员变量(匿名内部类)
            FieldSpec.Builder fieldBuilder = FieldSpec.builder(ClassName.get(Common.RXBUS_PACK_NAME, "RxBus", "OnEventListener"),
                    bindEventMethod.getMethodName().toString() + "_bind", Modifier.PUBLIC);
            classBuilder.addField(fieldBuilder.build());
            //inject方法添加代码(成员变量赋值)
            injectBuilder.addStatement(bindEventMethod.getMethodName().toString() + "_bind = $L", typeSpec);
            //inject方法添加代码(RxBus注册)
            injectBuilder.addStatement(" RxBus.getDefault().register($L,$L,$T.class)", bindEventMethod.getMethodName().toString() + "_bind",
                    bindEventMethod.getType(), TypeName.get(bindEventMethod.getParameters().get(0).asType()));
            //UnInject方法添加(RxBus解绑)
            unInjectBuilder.addStatement("RxBus.getDefault().unregister($L)", bindEventMethod.getMethodName().toString() + "_bind");
        }
        //类增加inject方法
        classBuilder.addMethod(injectBuilder.build());
        //类增加unInject方法
        classBuilder.addMethod(unInjectBuilder.build());
        return JavaFile.builder(getPackName(), classBuilder.build()).build();
    }
在generateFile() 方法中,按照前边设计的要生成的代码模板,利用 JavaPoet 的 API 生成代码。这部分没啥特别的姿势,照着 JavaPoet 文档 来就好了,文档写得很细致。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值