写一个ButterKnife
来源:互联网 发布:女朋友 文艺青年 知乎 编辑:程序博客网 时间:2024/06/05 00:22
上篇博客写的是源码分析,但是并没有涉及到apt;这次就来看看使用apt如何写一个ButterKnife;
Part1 注解基础
如果没用过注解或者用的少,推荐下看下http://blog.csdn.net/briblue/article/details/73824058
Part2 apt实现ButterKnife
使用Annotation Processing Tool实现的好处就是没有性能损耗,因为没有用反射,只是在编译时解析注解生成需要的class文件。
步骤:
1.新建名为annotation的Java Library,
annotation的build.gradle:
apply plugin: 'java'dependencies { compile fileTree(dir: 'libs', include: ['*.jar'])}sourceCompatibility = "1.7"targetCompatibility = "1.7"
定义两个注解:
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Bind { int id() default -1;}
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface OnClick { /** View IDs to which the method will be bound. */ int[] value() default {-1};}
2.新建名为compiler的Java Library
compiler的build.gradle:
apply plugin: 'java'dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.google.auto.service:auto-service:1.0-rc2' compile 'com.squareup:javapoet:1.7.0' compile project(':annotation')}sourceCompatibility = "1.7"targetCompatibility = "1.7"
配置项目根目录的build.gradle
dependencies { classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'}
定义一个ButterKnifeProcessor 继承AbstractProcessor;这里参考了最新版本的ButterKnife源码,不过里面的逻辑很复杂容易把人绕晕。所以我根据自己的理解,写了个更简单易懂的。
重写process方法,依据bindingMap 结果遍历生成多个以_ViewBinding为结尾的类文件:
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) { Map<ClassName, BindingSet> bindingMap = findAndParseTargets(env); for (Map.Entry<ClassName, BindingSet> entry : bindingMap.entrySet()) {//根据ClassName取得BindingSet,生成多个文件; ClassName className = entry.getKey(); BindingSet binding = entry.getValue(); TypeSpec.Builder result = TypeSpec.classBuilder(binding.bindingClassName.simpleName() + "_ViewBinding") .addModifiers(PUBLIC) .addSuperinterface(UNBINDER) .addField(binding.targetTypeName, "target", PRIVATE) .addMethod(createBindingConstructorForActivity(binding)) .addMethod(createBindingConstructorForActivity2(binding)) .addMethod(createBindingUnbindMethod(binding)); for (Element element : binding.elementClicks) {//添加点击view的成员变量; int[] ss = element.getAnnotation(OnClick.class).value(); for (int s : ss) { result.addField(VIEW, "view" + s, PRIVATE); } } JavaFile javaFile = JavaFile.builder(binding.bindingClassName.packageName(), result.build()) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } return false; }
BindingSet :依据类名,保存需要绑定的view和click集合。
public class BindingSet { public TypeName targetTypeName; public ClassName bindingClassName; public Set<Element> elements = new HashSet<>(); public Set<Element> elementClicks = new HashSet<>();}
取出所有的注解,依据所在的类名保存到bindingMap :
private Map<ClassName, BindingSet> findAndParseTargets(RoundEnvironment env) {//以ClassName为key,BindingSet为value保存结果 LinkedHashMap<ClassName, BindingSet> map = new LinkedHashMap<ClassName, BindingSet>(); Set<? extends Element> elements = env.getElementsAnnotatedWith(Bind.class); Set<? extends Element> elementClicks = env.getElementsAnnotatedWith(OnClick.class); for (Element element : elements) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); TypeMirror typeMirror = enclosingElement.asType(); TypeName targetTypeName = TypeName.get(typeMirror); if (targetTypeName instanceof ParameterizedTypeName) { targetTypeName = ((ParameterizedTypeName) targetTypeName).rawType; } String packageName = getPackage(enclosingElement).getQualifiedName().toString(); String className = enclosingElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$'); ClassName bindingClassName = ClassName.get(packageName, className); if (map.keySet().contains(bindingClassName)) { BindingSet bindingSet = map.get(bindingClassName); bindingSet.elements.add(element); bindingSet.targetTypeName = targetTypeName; bindingSet.bindingClassName = bindingClassName; } else { BindingSet bindingSet = new BindingSet(); bindingSet.elements.add(element); bindingSet.targetTypeName = targetTypeName; bindingSet.bindingClassName = bindingClassName; map.put(bindingClassName, bindingSet); } } for (Element click : elementClicks) { TypeElement enclosingElement = (TypeElement) click.getEnclosingElement(); TypeMirror typeMirror = enclosingElement.asType(); TypeName targetTypeName = TypeName.get(typeMirror); if (targetTypeName instanceof ParameterizedTypeName) { targetTypeName = ((ParameterizedTypeName) targetTypeName).rawType; } String packageName = getPackage(enclosingElement).getQualifiedName().toString(); String className = enclosingElement.getQualifiedName().toString().substring(packageName.length() + 1).replace('.', '$'); ClassName bindingClassName = ClassName.get(packageName, className); if (map.keySet().contains(bindingClassName)) { BindingSet bindingSet = map.get(bindingClassName); bindingSet.elementClicks.add(click); bindingSet.targetTypeName = targetTypeName; bindingSet.bindingClassName = bindingClassName; } else { BindingSet bindingSet = new BindingSet(); bindingSet.elementClicks.add(click); bindingSet.targetTypeName = targetTypeName; bindingSet.bindingClassName = bindingClassName; map.put(bindingClassName, bindingSet); } } return map; }
使用javapoet生成代码:findView和设置onclick等;
取值替换需要用到以下字符,有点类似于String.format:
$L for Literals
$S for Strings
$T for Types 类型,用了会自动import;
$N for Names(我们自己生成的方法名或者变量名等等)
private MethodSpec createBindingConstructorForActivity2(BindingSet binding) { MethodSpec.Builder builderConstructor2 = MethodSpec.constructorBuilder() .addModifiers(PUBLIC) .addParameter(binding.targetTypeName, "target", Modifier.FINAL) .addParameter(VIEW, "source") .addStatement("this.target = target") .addStatement("View view"); for (Element element : binding.elements) { builderConstructor2.addStatement("target.$L = $T.findRequiredViewAsType(source, R.id.$L, \"field '$L'\", $T.class)", element.getSimpleName(), UTILS, element.getSimpleName(), element.getSimpleName(), getRawType(element)); } for (Element element : binding.elementClicks) { int[] ss = element.getAnnotation(OnClick.class).value(); for (int s : ss) { builderConstructor2 .addStatement("view = Utils.findRequiredView(source, $L)", s) .addStatement("view$L = view", s) .addStatement("view.setOnClickListener(new View.OnClickListener() {\n" + " @Override\n" + " public void onClick(View v) {\n" + " target.onClick(v);\n" + " }\n" + " })"); } } return builderConstructor2.build(); }
主要的apt相关的代码就是上面这些,完成这些后再project build后就会自动生成绑定用到的java代码;
public class ButterKnife { public static final String TAG = "ButterKnife"; public static Unbinder bind(Activity target) { View sourceView = target.getWindow().getDecorView(); return createBinding(target, sourceView); } private static Unbinder createBinding(Object target, View source) { Class<?> targetClass = target.getClass(); Constructor<? extends Unbinder> constructor = findViewBinder(targetClass); try { return constructor.newInstance(target, source);//这里调用了2个参数的构造方法。生成实例并且绑定了对象; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } private static Constructor<? extends Unbinder> findViewBinder(Class<?> cls) { String clsName = cls.getName(); try { Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");//查找到apt生成的java文件,加载; return (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { Log.e(TAG, "ClassNotFoundException Not found."); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } return null; }}
源码地址:https://github.com/Ulez/AnnotationDemo
- 写一个 ButterKnife
- 写一个ButterKnife
- 一个懒人庫(findViewById)-ButterKnife
- ButterKnife的使用,再也不用写findviewById
- as自写插件及butterknife
- Android-自己动手写ButterKnife与原理解析
- ButterKnife
- ButterKnife
- ButterKnife
- ButterKnife
- ButterKnife
- ButterKnife
- ButterKnife
- butterknife
- ButterKnife
- ButterKnife
- Butterknife
- butterknife
- 提高客户管理系统应用价值的五种方法
- 最小点权覆盖集&最大点权独立集
- poj2965 The Pilots Brothers' refrigerator 枚举 暴力 递归
- KNN分类算法java实现
- Equivalent Strings
- 写一个ButterKnife
- 使用控制台执行MySQL命令时,出现1366错误的原因与解决方案
- sax、dom和dom4j、jdom区别
- IOS逆向笔记之重新签名(非越狱)
- Perl中的alarm、eval、die的联合使用
- HDU
- 大牛们的好习惯(一)
- PCL:遇到的一些问题及解决方案
- SOC-FPGA交叉编译环境搭建