编译时注解
来源:互联网 发布:淘宝联盟无法登陆 编辑:程序博客网 时间:2024/05/16 17:30
前言
最近在看有关运行时注解的相关内容,在android studio 上开发遇到了不少的坑,希望通过这篇博客来总结这几天来的成果。
与编译时注解有关的类和方法
### 相关的类
这些方法会在接下来的工程中有所运用
代码编写 android studio 下开发
该案例是来源博客使用编译时注解方式实现View注入(Android Studio),该案例是实现类似ButterKnife的View注入, 自己在android studio 上开发还是遇到了不少的坑
1. 新建工程
在本案例中只建立了三个工程,ioc-annotation ,ioc-compiler 和 annotationtTest工程
- ioc-compiler 是 java Library 这个是一个Java Library,一定不能为Android Library ,也不能被Android模块的dependencies中使用compile引用,不然会找不到javax相关的类。主要用来处理注解,并生成相关的代码。
- ioc-annotation 是 android Libraray 被android模块调用实现View的ViewInject
- annotationtTest 是普通android 工程 存放测试案例
2 代码编写
2.1 编写注解
@Target(ElementType.FIELD)@Retention(RetentionPolicy.CLASS)public @interface BindView { int viewId();}
2.2 编写ViewInjector 接口
public interface ViewInjector<T> { public void inject(T t, Object obj);}
2.3 编写 ViewInjectProcessor
2.3.1 什么是注解解释器 Processor
- 注解需要通过注解处理器进行处理,所有的注解处理器都实现了Processor接口,一般我们选择继承AbstractProcessor来创建自定义注解处理器。
继承AbstractProcessor,实现public boolean process(Set annotations, RoundEnvironment roundEnv)方法。方法参数中annotations包含了该处理器声明支持并已经在源码中使用了的注解,roundEnv则包含了注解处理的上下文环境。 此方法返回true时,表示此注解已经被处理完毕,返回false时将会交给其他处理器继续处理。 - 覆盖getSupportedSourceVersion方法,返回处理器支持的源码版本,一般直接返回SourceVersion.latestSupported()即可。
覆盖getSupportedAnnotationTypes方法,返回处理器想要处理的注解类型,此处需返回一个包含了所有注解完全限定名的集合。
在Java 7及以上,可以使用类注解@SupportedAnnotationTypes和@SupportedSourceVersion替代上面的方法进行声明。
2.3.2 代码编写
// JDK 7 以后使用//@SupportedSourceVersion(SourceVersion.RELEASE_7)//@SupportedAnnotationTypes({"com.zs.annotation.BindView"}) @AutoService(Processor.class)public class ViewInjectProcessor extends AbstractProcessor { private Elements elementsUtils; private Filer filer; //文件的写入 private Messager messager; //打印Log信息之类 @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); this.elementsUtils = processingEnv.getElementUtils(); this.filer = processingEnv.getFiler(); this.messager = processingEnv.getMessager(); } /** * @param annotations * @param roundEnv * @return */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { Map<String, ElementInfo> maps = new HashMap<>(); // 拿到所有具有该注解的Elemnet Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class); // 拿到该element的封装的TypeElement for (Element element : elements) { if (!element.getKind().isField()) { continue; } // VariableElement 表示一个字段,局部变量等 VariableElement variableElement = (VariableElement) element; TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement(); String typeEleName = typeElement.getQualifiedName().toString(); ElementInfo elementInfo = maps.get(typeEleName); if (elementInfo == null) { elementInfo = new ElementInfo(typeElement, elementsUtils); maps.put(typeEleName, elementInfo); } BindView bindView = variableElement.getAnnotation(BindView.class); int viewId = bindView.viewId(); elementInfo.elementMap.put(viewId, variableElement); } for (Map.Entry<String, ElementInfo> entry : maps.entrySet()) { try { ElementInfo ei = entry.getValue(); JavaFileObject javaFileObject = filer.createSourceFile(ei.getProxyClassName(), ei.getTypeElement()); Writer writer = javaFileObject.openWriter(); //TODO writer.write(ei.generateJavaCode()); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); } } return true; } /** * 返回支持的注解类型 * * @return * @SupportedAnnotationTypes({"com.zs.annotation.BindView"})代替 */ @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotationType = new LinkedHashSet<>(); annotationType.add(BindView.class.getCanonicalName()); return annotationType; } /** * 返回支持的源码版本 * 基本默认为下面的形式就行 * 也可以用注解@SupportedSourceVersion(SourceVersion.RELEASE_7)代替 * * @return */ @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_7; } class ElementInfo { private final static String SUFFIX = "ViewInjector"; private TypeElement typeElement; private Elements elementUtils; public Map<Integer, Element> elementMap = new HashMap<>(); public ElementInfo(TypeElement typeElement, Elements elementUtils) { this.elementUtils = elementUtils; this.typeElement = typeElement; } public String getProxyClassName() { return typeElement.getSimpleName() + "$$" + SUFFIX; } public String getProxyPackgeName() { String fullName = this.typeElement.getQualifiedName().toString(); return fullName.substring(0, fullName.lastIndexOf(".")); } public Element getTypeElement() { return this.typeElement; } /** * 生成代理类的代码 * * @return */ public String generateJavaCode() { StringBuilder builder = new StringBuilder(); builder.append("package " + this.getProxyPackgeName()).append(";\n\n"); builder.append("import com.zs.*;\n"); builder.append("public class ").append(this.getProxyClassName()).append(" implements " + SUFFIX + "<" + this.typeElement.getQualifiedName() + ">"); builder.append("\n{\n"); generateMethod(builder); builder.append("\n}\n"); return builder.toString(); } private void generateMethod(StringBuilder builder) { builder.append("public void inject("+this.typeElement.getQualifiedName()+" host , Object object )"); builder.append("\n{\n"); for(int id : elementMap.keySet()){ VariableElement variableElement =(VariableElement) elementMap.get(id); String name = variableElement.getSimpleName().toString(); String type = variableElement.asType().toString() ; // messager.printMessage(Diagnostic.Kind.ERROR,"id="+id); builder.append(" if(object instanceof android.app.Activity)"); builder.append("\n{\n"); builder.append("host."+name).append(" = "); builder.append("(" + type + ")(((android.app.Activity)object).findViewById(" + id + "));"); //messager.printMessage(Diagnostic.Kind.ERROR, "id2=" + id); builder.append("\n}\n").append("else").append("\n{\n"); builder.append("host."+name).append(" = "); builder.append("("+type+")(((android.view.View)object).findViewById("+id+"));"); builder.append("\n}\n"); } builder.append("\n}\n"); } }}
2.4 编写 Ioc 即通过反射来调用通过编译时注解自动生成的类
public class Ioc { private final static String SUFFIX = "ViewInjector"; public static void inject(Activity activity) { inject(activity, activity); } public static void inject(Object host, Object root) { try { String prefixName = host.getClass().getName(); //拿到动态生成类的全名称 String fullName = prefixName + "$$" + SUFFIX; Class proxyClass = Class.forName(fullName); ViewInjector viewInjector = (ViewInjector) proxyClass.newInstance(); viewInjector.inject(host, root); } catch (Exception e) { e.printStackTrace(); } }}
2.5 编写测试用例
public class MainActivity extends AppCompatActivity { @BindView(viewId = R.id.tv) public TextView tv; @BindView(viewId = R.id.btn) Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); Ioc.inject(this); // button = (Button) findViewById(R.id.btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { tv.setText("通过编译时注解改变"); } }); }}
3. 踩过的坑
3. 1 Java Library 和 android Library 以及 android module之间的依赖问题
在android studio1.5上,将android Library 添加为Java Library 依赖时可以添加成功但不能导入android Library中的类,不知道是为什么
3.2 non-zero exit value 2 错误
Error:Execution failed for task ':app:transformClassesWithDexForDebug'.> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'F:\Program Files (x86)\Java\jdk1.8.0_31\bin\java.exe'' finished withnon-zero exit value 2
解决
这个错误在app的build.gradle里面添加下面这句就好了。android { defaultConfig { ... multiDexEnabled true }}
3.3 bad class file magic (cafebabe) or version (0034.0000)
Error:com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) 和Error:Execution failed for task ':app:transformClassesWithDexForDebug'.> com.Android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: org.gradle.process.internal.ExecException: Process 'command 'F:\Program Files (x86)\Java\jdk1.8.0_31\bin\java.exe'' finished with non-zero exit value 1
解决
在新建的java Libraray 的 build.gradle中添加 下文中的红色内容apply plugin: 'java'dependencies { //compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.google.auto.service:auto-service:1.0-rc2' targetCompatibility = JavaVersion.VERSION_1_7 sourceCompatibility = JavaVersion.VERSION_1_7}
3.4 通过编译时注解生成的代码存放目录问题
在这篇博文 使用编译时注解方式实现View注入(Android Studio)中说动态生成的代码存放的目录为app->build->generated->source->apt->debug,如果没有显示该目录可以尝试clean,然后菜单栏->Build0->Make Project就可以了 ,但在我的android studio 1.5 上该方法并不起作用,但在目app\build\intermediates\classes\debug\下找到了动态生成的代码
3.5 新建工程问题
在android studio 中用来存放Processor,即要存放自定义的注解解释器的工程一定要是 java Library ,不然就找不到AbstractProcessor这个类
4. 参考资料
http://blog.csdn.net/qduningning/article/details/51485869
http://my.oschina.net/u/134491/blog/663124
http://blog.csdn.net/lmj623565791/article/details/51931859
https://moxun.me/archives/63?utm_source=tuicool&utm_medium=referral
5. 源代码
- 编译时注解
- 编译时注解解析
- Android 编译时注解
- 编译时注解参考文献
- Android 编译时注解
- 编译时注解
- 关于编译时注解
- Java注解处理器(编译时注解)
- android编译时注解框架
- Android 编译时解析注解
- 编译时的注解编写
- ButterKnife编译时注解探秘
- 注解:编译注解
- 自定义运行时注解、编译时注解[ButterKnife原理探析]
- 自定义注解之编译时注解(RetentionPolicy.CLASS)(一)
- 自定义注解之编译时注解(RetentionPolicy.CLASS)(一)
- android__编译时注解的尝试。
- Java编译时注解自动生成代码
- 创建可以分享内容的应用
- eclipse+cdt 配置mysql(附测试代码)
- POJ 3281 Dining 最大流
- 今日头条2017年实习生在线笔试题1
- VS2015配置EGE图形库
- 编译时注解
- Qt tips 如何给qlabel添加边框
- this的小练习
- 欢迎使用CSDN-markdown编辑器
- Java排序算法——冒泡排序
- 网教 8.a+b
- [PAT] B1047
- 数据结构一些知识点备忘
- 微软的十大变动