编译时注解

来源:互联网 发布:淘宝联盟无法登陆 编辑:程序博客网 时间: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. 源代码

0 0