编译时和运行时注解实现,案例ButterKnife

运行时注解,反射+注解的形式实现

/**
 * 定义了一个用在属性上的运行时注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value();
}

@Retention定义了该注解被保留的时间长短,有三种值可以选择
1、SOURCE,表示注解保留在源码层面,编译的后就会被擦除
2、CLASS,表示注解只保留到编译阶段
3、RUNTIME,表示注解保留到运行时,可以反射获取注解信息
@Target定义了注解用在什么地方
1、FIELD,属性
2、METHOD,方法函数
3、TYPE,类/接口
4、PARAMETER,参数
5、CONSTRUCTOR,构造方法

public class ButterKnife {
    /**
     * 调用bind方法时,使用反射的手段,设置view的值
     */
    public static void bind(Activity activity) {
        Class cls = activity.getClass();
        // 获取Activity下的所有属性
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            // 获取所有带BindView注解的属性
            BindView bindView = field.getAnnotation(BindView.class);
            if (bindView == null) continue;
            field.setAccessible(true);
            int viewId = bindView.value();
            if (viewId != -1) {
                View view = activity.findViewById(viewId);
                try {
                    // 重新设置属性值
                    field.set(activity, view);
                } catch (IllegalAccessException e) {
                }
            }
        }
    }
}
/**
 * 使用就非常简单了
 */
  @BindView(R.id.tv)
   TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        mTextView.setText("测试运行时注解");
    }

运行时注解实现起来非常简单,缺点也比较明显,因为在运行的时候,通过反射的形式,获取所有属性,所以非常损耗性能。
编译时注解就是为了解决这个问题而诞生。

基于APT的编译时注解

创建一个java lib,命名为butterknife_anaotation

/**
 * 定义一个用于属性上的编译时注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}

继续创建一个java lib(不是Android lib),命名为butterknife_compiler
定义一个ButterKnifeProcess类,继承于AbstractProcessor

/**
 * 自定义注解处理器
 */
public class ButterKnifeProcess extends AbstractProcessor {
}

由于是自定义的注解处理器,所以需要注册到系统里,可以用Google提供的AutoService,但是发现有时候不太稳定,所以我
习惯手写注册。其实也很简单,就是在butterknife_compiler这个项目下的main下面新建一个resources/META-INF/services目录,目录下放置一个命名为javax.annotation.processing.Processor的文件,这些都是固定写法。
文件里,写上ButterKnifeProcess这个文件对应的路径即可。

com.zbh.butterknife_compiler.ButterKnifeProcess

接下来重点关注ButterKnifeProcess类里的内容

/**
 * 自定义注解处理器
 */
public class ButterKnifeProcess extends AbstractProcessor {

    private Filer filer;

    private Elements mElements;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 生成文件时需要的Filer对象
        filer = processingEnv.getFiler();
        mElements = processingEnv.getElementUtils();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        // 告知处理器,需要获取哪些注解
        supportTypes.add(BindView.class.getCanonicalName());
        return supportTypes;
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 当我们编译时,注解处理器在处理时,这个方法就会被执行
        Map<TypeElement, List<BindField>> map = collectInfo(roundEnvironment);
        writeToFile(map);
        return false;
    }

    /**
     * 收集注解信息
     */
    private Map<TypeElement, List<BindField>> collectInfo(RoundEnvironment roundEnvironment) {
        // 获取所有带BindView注解的元素,不是只找某一个类的,而是声明了该注解处理器的项目里的所有类
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        Map<TypeElement, List<BindField>> map = new HashMap<>();
        for (Element element : elements) {
            // 判断这个注解是否是属性注解
            if (element.getKind() == ElementKind.FIELD) {
                // TypeElement表示注解属性所处的类文件元素
                TypeElement typeElement = (TypeElement) element.getEnclosingElement();
                List<BindField> list = map.get(typeElement);
                if (list == null) {
                    list = new ArrayList<>();
                    map.put(typeElement, list);
                }
                // 获取注解的值
                int viewId = element.getAnnotation(BindView.class).value();
                // 获取属性的名字
                String fieldName = element.getSimpleName().toString();
                // 获取属性类型
                TypeMirror type = element.asType();
                list.add(new BindField(fieldName, viewId, type));
            }
        }
        return map;
    }

    /**
     * 借助javapoet生成文件
     * 当然自己使用FileWrite去写入文件也是没问题的
     */
    private void writeToFile(Map<TypeElement, List<BindField>> map) {
        for (TypeElement typeElement : map.keySet()) {
            // 包名
            String packetName = mElements.getPackageOf(typeElement).getQualifiedName().toString();
            // 类名
            String className = typeElement.getSimpleName().toString();
            // 定义一个构造方法,私有属性,有一个参数是final XXActivity target
            MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
                    .addParameter(ClassName.bestGuess(className), "target", Modifier.FINAL)
                    .addModifiers(Modifier.PRIVATE);

            // 一个类里的所有BindView注解都放在list里了
            List<BindField> list = map.get(typeElement);
            for (BindField field : list) {
                ClassName typeName = ClassName.bestGuess(field.mType.toString());
                // 构造方法里添加代码
                constructorBuilder.addStatement("target.$L=($T)target.findViewById($L)", field.mFieldName, typeName, field.mViewId);
            }
            // 定义一个public的类,名字是className + "_$_ViewBinder"
            TypeSpec typeSpec = TypeSpec.classBuilder(className + "_$_ViewBinder").addModifiers(Modifier.PUBLIC)
//                    .addField(fieldSpec)
                    .addMethod(constructorBuilder.build())
//                    .addMethod(methodBuilder.build())
                    .build();

            try {
                /**
                 * 生成文件,在各自module的build/generated/source/
                 * apt/debug(release)/使用BindView注解的类所处的包/className_$_ViewBinder
                 */
                JavaFile javaFile = JavaFile.builder(packetName, typeSpec).build();
                javaFile.writeTo(filer);
            } catch (Exception e) {
            }
        }
    }

    static class BindField {
        String mFieldName;
        int mViewId;
        TypeMirror mType;

        BindField(String fieldName, int viewId, TypeMirror type) {
            this.mFieldName = fieldName;
            this.mViewId = viewId;
            this.mType = type;
        }
    }

}

我们再来看看通过上面javapoet生成的代码

public class MainActivity_$_ViewBinder {
  private MainActivity_$_ViewBinder(final MainActivity target) {
    target.mTv3=(TextView)target.findViewById(2131165325);
    target.mTv4=(TextView)target.findViewById(2131165326);
  }
}

最后一步,我们怎么调用上面javapoet生成的代码类呢,从而节省findViewById操作呢?答案是反射。

public class ButterKnife {
    /**
     * 静态bind方法,通过反射获取XXActivity_$_ViewBinder类的对象,因为findViewById都在其构造方法里
     * 因此,只要创建了javapoet生成的代码类对象,就是相当于执行了findViewById操作。
     */
    public static void bind(Activity target) {
        try {
            // 反射需要全类名
            String binderName = target.getPackageName() + "." + target.getLocalClassName() + "_$_ViewBinder";
            Class binderCls = Class.forName(binderName);
            Constructor constructor = binderCls.getDeclaredConstructor(target.getClass());
            constructor.setAccessible(true);
            constructor.newInstance(target);
        } catch (Exception e) {
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值