编译时注解

前言

最近在看有关运行时注解的相关内容,在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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值