使用编译时注解方式实现View注入(Android Studio)
2016-05-24 00:00
555 查看
ButterKnife是一个强大的View注入,事件注入的框架,现模仿ButterKnife的方式,使用编译时注解实现View的注入的Demo。
基本的原理在上一篇文章中(https://www.zhangningning.com.cn/blog/Android/android_rentention.html)已经做了说明,这篇主要是实现一个在Activity中实现Bind View的注解。
先整体说明一下:
实例一共分为四个部分:
injectview-annotations: 这是个Java Library,,主要来定义注解。
injectview-compiler: 这个也是一个Java Library,一定不能为Android Library ,也不能被Android模块的dependencies中使用compile引用,不然会找不到javax相关的类。主要用来处理注解,并生成相关的代码。
injectview: 这个是Android Library,被android模块调用实现View的Bind.
app: Android Application模块,应用的主模块。
使用的库有:
auto-common:注解处理辅助类
auto-service:辅助Processor自动发现
javapoet:生成代码服务类
这三个库用在injectview-compiler中,不会存在于最终的apk包中。
android-apt:用来编译injectview-compiler生成需要的代码。
四个模块的关系:
首先定义一个注解:
修改build.gradle:
新建一个继承AbstractProcessor的BindViewProcessor。并且使用AutoService进行注解,这样系统就能够找到这个 Processor ,并在编译时对注解进行预处理。
重写
在process函数中对注解进行处理,并生成对应辅助class文件,这个class名是当前Activity名字拼接上$$ViewBinder。相应的代码如下:
BindViewProcessor.java
生成的文件在app->build->generated->source->apt->debug下,如果没有实时显示出来 ,可以尝试clean,然后菜单栏->Build0->Make Project就可以了
在InjectView的bind函数在Activity中调用,这里会先找到该Activity对应的ViewBinder类,并执行它的bind方法,来对该Activity中添加注解的View进行”注入”。
InjectView.java
OK,完成
全部代码 http://git.oschina.net/zhangningning/AndroidRentation
源地址:https://www.zhangningning.com.cn/blog/Android/android_rentention_sample.html
坑太多了 搞了两晚上才搞出来。。。
基本的原理在上一篇文章中(https://www.zhangningning.com.cn/blog/Android/android_rentention.html)已经做了说明,这篇主要是实现一个在Activity中实现Bind View的注解。
先整体说明一下:
实例一共分为四个部分:
injectview-annotations: 这是个Java Library,,主要来定义注解。
injectview-compiler: 这个也是一个Java Library,一定不能为Android Library ,也不能被Android模块的dependencies中使用compile引用,不然会找不到javax相关的类。主要用来处理注解,并生成相关的代码。
injectview: 这个是Android Library,被android模块调用实现View的Bind.
app: Android Application模块,应用的主模块。
使用的库有:
auto-common:注解处理辅助类
auto-service:辅助Processor自动发现
javapoet:生成代码服务类
这三个库用在injectview-compiler中,不会存在于最终的apk包中。
android-apt:用来编译injectview-compiler生成需要的代码。
四个模块的关系:
app引用
injectview,
injectview引用
injectview-annotations,
injectview-compiler引用
injectview-annotations,
app中使用APT执行
injectview-compiler
injectview-annotations
新建一个Java Library(File->New->New Module 选择Java Library)首先定义一个注解:
package com.znn.injectview.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value(); }
injectview-compiler
再新建一个名为injectview-compiler的Java Library。修改build.gradle:
apply plugin: 'java' dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.google.auto:auto-common:0.6' compile 'com.google.auto.service:auto-service:1.0-rc2' compile 'com.squareup:javapoet:1.7.0' compile project(':injectview-annotations') sourceCompatibility = 1.7 targetCompatibility = 1.7 }
新建一个继承AbstractProcessor的BindViewProcessor。并且使用AutoService进行注解,这样系统就能够找到这个 Processor ,并在编译时对注解进行预处理。
重写
getSupportedAnnotationTypes方法,将
BindView添加到支持处理的注解中。
在process函数中对注解进行处理,并生成对应辅助class文件,这个class名是当前Activity名字拼接上$$ViewBinder。相应的代码如下:
BindViewProcessor.java
@AutoService(Processor.class) public final class BindViewProcessor extends AbstractProcessor{ private Elements elementUtils; private Types typeUtils; private Filer filer; private static final ClassName VIEW_BINDER = ClassName.get("com.znn.injectview", "ViewBinder"); private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";//生成类的后缀 以后会用反射去取 @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); elementUtils = env.getElementUtils(); typeUtils = env.getTypeUtils(); filer = env.getFiler(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(BindView.class.getCanonicalName()); return types; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Map<TypeElement, List<FieldViewBinding>> targetClassMap = new LinkedHashMap<>(); for (Element element: roundEnv.getElementsAnnotatedWith(BindView.class)){ if (!SuperficialValidation.validateElement(element)) continue; // Start by verifying common generated code restrictions. TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // Verify that the target type extends from View. TypeMirror elementType = element.asType(); if (elementType.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } if (!isSubtypeOfType(elementType, "android.view.View") && !isInterface(elementType)) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName())); hasError = true; } if (hasError) { continue; } // Assemble information on the field. List<FieldViewBinding> fieldViewBindingList = targetClassMap.get(enclosingElement); if (fieldViewBindingList == null) { fieldViewBindingList = new ArrayList<>(); targetClassMap.put(enclosingElement, fieldViewBindingList); } String packageName = getPackageName(enclosingElement); TypeName targetType = TypeName.get(enclosingElement.asType()); int id = element.getAnnotation(BindView.class).value(); String fieldName = element.getSimpleName().toString(); TypeMirror fieldType = element.asType(); FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, fieldType, id); fieldViewBindingList.add(fieldViewBinding); } for (Map.Entry<TypeElement, List<FieldViewBinding>> item : targetClassMap.entrySet()){ List<FieldViewBinding> list = item.getValue(); if (list == null || list.size() == 0){ continue; } TypeElement enclosingElement = item.getKey(); String packageName = getPackageName(enclosingElement); ClassName typeClassName = ClassName.bestGuess(getClassName(enclosingElement, packageName)); TypeSpec.Builder result = TypeSpec.classBuilder(getClassName(enclosingElement, packageName) + BINDING_CLASS_SUFFIX) .addModifiers(Modifier.PUBLIC) .addTypeVariable(TypeVariableName.get("T", typeClassName)) .addSuperinterface(ParameterizedTypeName.get(VIEW_BINDER, typeClassName)); result.addMethod(createBindMethod(list, typeClassName)); try { JavaFile.builder(packageName, result.build()) .addFileComment(" This codes are generated automatically. Do not modify!") .build().writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } return true; } private MethodSpec createBindMethod(List<FieldViewBinding> list, ClassName typeClassName) { MethodSpec.Builder result = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC) .returns(TypeName.VOID) .addAnnotation(Override.class) .addParameter(typeClassName, "target", Modifier.FINAL); for (int i = 0; i < list.size(); i++) { FieldViewBinding fieldViewBinding = list.get(i); String packageString = fieldViewBinding.getType().toString(); // String className = fieldViewBinding.getType().getClass().getSimpleName(); ClassName viewClass = bestGuess(packageString); result.addStatement("target.$L=($T)target.findViewById($L)", fieldViewBinding.getName(), viewClass, fieldViewBinding.getResId()); } return result.build(); } private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass, String targetThing, Element element) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify method modifiers. Set<Modifier> modifiers = element.getModifiers(); if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.STATIC)) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName())); hasError = true; } // Verify containing type. if (enclosingElement.getKind() != ElementKind.CLASS) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName())); hasError = true; } // Verify containing class visibility is not private. if (enclosingElement.getModifiers().contains(Modifier.PRIVATE)) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName())); hasError = true; } return hasError; } private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass, Element element) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String qualifiedName = enclosingElement.getQualifiedName().toString(); if (qualifiedName.startsWith("android.")) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s-annotated class incorrectly in Android framework package. (%s)", annotationClass.getSimpleName(), qualifiedName)); return true; } if (qualifiedName.startsWith("java.")) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("@%s-annotated class incorrectly in Java framework package. (%s)", annotationClass.getSimpleName(), qualifiedName)); return true; } return false; } private boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) { if (otherType.equals(typeMirror.toString())) { return true; } if (typeMirror.getKind() != TypeKind.DECLARED) { return false; } DeclaredType declaredType = (DeclaredType) typeMirror; List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments(); if (typeArguments.size() > 0) { StringBuilder typeString = new StringBuilder(declaredType.asElement().toString()); typeString.append('<'); for (int i = 0; i < typeArguments.size(); i++) { if (i > 0) { typeString.append(','); } typeString.append('?'); } typeString.append('>'); if (typeString.toString().equals(otherType)) { return true; } } Element element = declaredType.asElement(); if (!(element instanceof TypeElement)) { return false; } TypeElement typeElement = (TypeElement) element; TypeMirror superType = typeElement.getSuperclass(); if (isSubtypeOfType(superType, otherType)) { return true; } for (TypeMirror interfaceType : typeElement.getInterfaces()) { if (isSubtypeOfType(interfaceType, otherType)) { return true; } } return false; } private boolean isInterface(TypeMirror typeMirror) { return typeMirror instanceof DeclaredType && ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.INTERFACE; } private String getPackageName(TypeElement type) { return elementUtils.getPackageOf(type).getQualifiedName().toString(); } private static String getClassName(TypeElement type, String packageName) { int packageLen = packageName.length() + 1; return type.getQualifiedName().toString().substring(packageLen).replace('.', '$'); } }
生成的文件在app->build->generated->source->apt->debug下,如果没有实时显示出来 ,可以尝试clean,然后菜单栏->Build0->Make Project就可以了
injectview
生成的class类会继承ViewBinder:
package com.znn.injectview; public interface ViewBinder<T> { void bind(T target); }
在InjectView的bind函数在Activity中调用,这里会先找到该Activity对应的ViewBinder类,并执行它的bind方法,来对该Activity中添加注解的View进行”注入”。
InjectView.java
public class InjectView { public static void bind(Activity activity){ String clsName = activity.getClass().getName(); try { Class<?> viewBindingClass = Class.forName(clsName + "$$ViewBinder"); ViewBinder viewBinder = (ViewBinder)viewBindingClass.newInstance(); viewBinder.bind(activity); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
app
在Activity中对View变量添加BindView注解,在
setContentView后,使用View前添加
InjectView.bind(this);进行注解。
public class MainActivity extends FragmentActivity { @BindView(R.id.text) TextView textView; @BindView(R.id.text2) TextView textView2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); InjectView.bind(this); textView.setText("text changged!"); } }
OK,完成
全部代码 http://git.oschina.net/zhangningning/AndroidRentation
源地址:https://www.zhangningning.com.cn/blog/Android/android_rentention_sample.html
坑太多了 搞了两晚上才搞出来。。。
相关文章推荐
- Android DrawerLayout 侧滑菜单栏
- Android RxJava使用介绍(二) RxJava的操作符
- Java+Android实现DES与四种模式、AES、MD5算法三合一
- ViewDragHelper源码分析
- android开发笔记之Json解析
- Android三级图片缓存
- [Android]Android开源工具项目集合20160523
- 关于handler机制(结合源码及方法的调用去总结)
- android源码解析(二十七)-->HOME事件流程
- 第一个Android crackme(2016-05)
- Android-architecture之MVC、MVP、MVVM、Data-Binding
- Android RxJava使用介绍(一)概念
- android studio 2.0 导入工程
- android中的各类权限
- viewHolder工具类
- 详解-Android各种提示框
- Android必需了解的东西
- android插入SQLite中文乱码问题
- Android Architecture(Is Activity God?)
- Android点击两次back退出程序的两种方法