精华内容
下载资源
问答
  • 编译时注解

    2016-09-02 21:46:28
    编译时注解有关的类和方法### 相关的类 这些方法会在接下来的工程中有所运用代码编写 android studio 下开发该案例是来源博客使用编译时注解方式实现View注入(Android Studio),该案例是实现类似...

    前言

    最近在看有关运行时注解的相关内容,在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. 源代码

    展开全文
  • 该系列将通过5篇博客一步步教你打造一个属于自己的编译时注解框架,并在之后开源出基于APT的编译时注解框架。 提到注解,普遍都会有两种态度:黑科技、低性能。使用注解往往可以实现用非常少的代码作出匪夷所思的...
    2016-07-17 | 暴打小女孩| Android

    概述

    Android编译时注解框架从入门到项目实践。该系列将通过5篇博客一步步教你打造一个属于自己的编译时注解框架,并在之后开源出基于APT的编译时注解框架。

    提到注解,普遍都会有两种态度:黑科技、低性能。使用注解往往可以实现用非常少的代码作出匪夷所思的事情,比如这些框架:ButterKnife、Retrofit。但一直被人诟病的是,运行时注解会因为java反射而引起较为严重的性能问题...

    今天我们要讲的是,不会对性能有任何影响的黑科技:编译时注解。也有人叫它代码生成,其实他们还是有些区别的,在编译时对注解做处理,通过注解,获取必要信息,在项目中生成代码,运行时调用,和直接运行手写代码没有任何区别。而更准确的叫法:APT - Annotation Processing Tool

    得当的使用编译时注解,可以极大的提高开发效率,避免编写重复、易错的代码。大部分时候编译时注解都可以代替java反射,利用可以直接调用的代码代替反射,极大的提升运行效率。

    本章作为《Android编译时注解框架》系列的第一章,将分三个部分让你简单认识注解框架。之后我们会一步步的创建属于自己的编译时注解框架。

    • 什么是注解

    • 运行时注解的简单使用

    • 编译时注解框架ButterKnife源码初探

    什么是注解

    注解你一定不会陌生,这就是我们最常见的注解:

    首先注解分为三类:

    • 标准 Annotation

      包括 Override, Deprecated, SuppressWarnings,是java自带的几个注解,他们由编译器来识别,不会进行编译, 不影响代码运行,至于他们的含义不是这篇博客的重点,这里不再讲述。

    • 元 Annotation

      @Retention, @Target, @Inherited, @Documented,它们是用来定义 Annotation 的 Annotation。也就是当我们要自定义注解时,需要使用它们。

    • 自定义 Annotation

      根据需要,自定义的Annotation。而自定义的方式,下面我们会讲到。

    同样,自定义的注解也分为三类,通过元Annotation - @Retention 定义:

    • @Retention(RetentionPolicy.SOURCE)

      源码时注解,一般用来作为编译器标记。如Override, Deprecated, SuppressWarnings。

    • @Retention(RetentionPolicy.RUNTIME)

      运行时注解,在运行时通过反射去识别的注解。

    • @Retention(RetentionPolicy.CLASS)

      编译时注解,在编译时被识别并处理的注解,这是本章重点。

    运行时注解的简单使用

    运行时注解的实质是,在代码中通过注解进行标记,运行时通过反射寻找标记进行某种处理。而运行时注解一直以来被呕病的原因便是反射的低效。

    下面展示一个Demo。其功能是通过注解实现布局文件的设置。

    之前我们是这样设置布局文件的:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
    }
    复制代码

    如果使用注解,我们就可以这样设置布局了

    @ContentView(R.layout.activity_home)
    public class HomeActivity extends BaseActivity {
    	。。。
    }
    复制代码

    我们先不讲这两种方式哪个好哪个坏,我们只谈技术不谈需求。

    那么这样的注解是怎么实现的呢?很简单,往下看。

    创建一个注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    public @interface ContentView {
    	int value();
    }
    复制代码

    第一行:@Retention(RetentionPolicy.RUNTIME)

    @Retention用来修饰这是一个什么类型的注解。这里表示该注解是一个运行时注解。这样APT就知道啥时候处理这个注解了。

    第二行:@Target({ElementType.TYPE})

    @Target用来表示这个注解可以使用在哪些地方。比如:类、方法、属性、接口等等。这里ElementType.TYPE 表示这个注解可以用来修饰:Class, interface or enum declaration。当你用ContentView修饰一个方法时,编译器会提示错误。

    第三行:public @interface ContentView

    这里的interface并不是说ContentView是一个接口。就像申明类用关键字class。申明枚举用enum。申明注解用的就是@interface。(值得注意的是:在ElementType的分类中,class、interface、Annotation、enum同属一类为Type,并且从官方注解来看,似乎interface是包含@interface的)

    /** Class, interface (including annotation type), or enum declaration */
    TYPE,
    复制代码

    第四行:int value();

    返回值表示这个注解里可以存放什么类型值。比如我们是这样使用的

    @ContentView(R.layout.activity_home)
    复制代码

    R.layout.activity_home实质是一个int型id,如果这样用就会报错:

    @ContentView(“string”)
    复制代码

    关于注解的具体语法,这篇不在详述,统一放到《Android编译时注解框架-语法讲解》中

    注解解析

    注解申明好了,但具体是怎么识别这个注解并使用的呢?

    @ContentView(R.layout.activity_home)
    public class HomeActivity extends BaseActivity {
    	。。。
    }
    复制代码

    注解的解析就在BaseActivity中。我们看一下BaseActivity代码

    public class BaseActivity extends AppCompatActivity {
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //注解解析
    	for (Class c = this.getClass(); c != Context.class; c = c.getSuperclass()) {
            ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
            if (annotation != null) {
                try {
                    this.setContentView(annotation.value());
                } catch (RuntimeException e) {
                    e.printStackTrace();
                }
                return;
            }
        }
    }
    复制代码

    第一步:遍历所有的子类

    第二步:找到修饰了注解ContentView的类

    第三步:获取ContentView的属性值。

    第四步:为Activity设置布局。

    总结

    相信你现在对运行时注解的使用一定有了一些理解了。也知道了运行时注解被人呕病的地方在哪了。

    你可能会觉得*setContentView(R.layout.activity_home)@ContentView(R.layout.activity_home)*没什么区别,用了注解反而还增加了性能问题。

    但你要知道,这只是注解最简单的应用方式。举一个例子:AndroidEventBus的注解是运行时注解,虽然会有一点性能问题,但是在开发效率上是有提高的。

    因为这篇博客的重点不是运行时注解,所以我们不对其源码进行解析。有兴趣的可以去github搜一下看看哦~话说AndroidEventBus还是我一个学长写得,haha~。

    编译时注解框架ButterKnife源码初探

    ButterKnife大家应该都很熟悉的吧,9000多颗start,让我们彻底告别了枯燥的findViewbyId。它的使用方式是这样的:

    你难道就没有好奇过,它是怎么实现的吗?嘿嘿,这就是编译时注解-代码生成的黑科技所在了。

    秘密在这里,编译工程后,打开你的项目app目录下的build目录:

    你可以看到一些带有*$$ViewBinder*后缀的类文件。这个就是ButterKnife生成的代码我们打开它:

    上面有一条注释: // Generated code from Butter Knife. Do not modify!

    1.ForgetActivity$$ViewBinder 和 我们的 ForgetActivity同在一个包下:

    package com.zhaoxuan.wehome.view.activity;
    复制代码

    同在一个包下的意义是什么呢?ForgetActivity$$ViewBinder 可以直接使用 ForgetActivity protected级别以上的属性方法。就像这样:

    	//accountEdit是ForgetActivity当中定义的控件
        target.accountEdit = finder.castView(view, 2131558541, "field 'accountEdit'");
    复制代码

    所以你也应该知道了为什么当使用private时会报错了吧?

    2.我们不去管细节,只是大概看一下这段生成的代码是什么意思。我把解析写在注释里。

    @Override
    public void bind(final Finder finder, final T target, Object source) {
        //定义了一个View对象引用,这个对象引用被重复使用了(这可是一个偷懒的写法哦~)
        View view;
        
        //暂时不管Finder是个什么东西,反正就是一种类似于findViewById的操作。
        view = finder.findRequiredView(source, 2131558541, "field 'accountEdit'");
        
        //target就是我们的ForgetActivity,为ForgetActivity中的accountEdit赋值
        target.accountEdit = finder.castView(view, 2131558541, "field 'accountEdit'");
        
        view = finder.findRequiredView(source, 2131558543, "field 'forgetBtn' and method 'forgetOnClick'");
        target.forgetBtn = finder.castView(view, 2131558543, "field 'forgetBtn'");
        
        //给view设置一个点击事件
        view.setOnClickListener(
                new butterknife.internal.DebouncingOnClickListener() {
                    @Override
                    public void doClick(android.view.View p0) {
                    
                    	//forgetOnClick()就是我们在ForgetActivity中写得事件方法。
                       target.forgetOnClick();
                       
                    }
                });
    }
    复制代码

    OK,现在你大致明白了ButterKnife的秘密了吧?通过自动生成代码的方式来代替我们去写findViewById这样繁琐的代码。现在你一定在疑惑两个问题:

    1.这个bind方法什么时候被调用?我们的代码里并没有ForgetActivity$$ViewBinder 这种奇怪的类引用呀。

    2.Finder到底是个什么东西?凭什么它可以找到view。

    不着急不着急,慢慢看。

    注解: @Bind的定义

    我们可以解读的信息如下:

    1. Bind是编译时注解

    2. 只能修饰属性

    3. 属性值是一个int型的数组。

    创建好自定义注解,之后我们就可以通过APT去识别解析到这些注解,并且可以通过这些注解得到注解的值、注解所修饰的类的类型、名称。注解所在类的名称等等信息。

    Finder类

    通过上面生成的代码,你一定奇怪,Finder到底是个什么东西。Finder实际是一个枚举。

    根据不同类型的,提供了不同实现的findView和getContext方法。这里你终于看到了熟悉的findViewById了吧,哈哈,秘密就在这里。

    另外Finder还有两个重要的方法,也是刚才没有介绍清楚的: finder.findRequiredViewfinder.castView

    findRequiredView 方法调用了 findOptionalView 方法

    findOptionalView调用了不同枚举类实现的findView方法(实际上就是findViewById啦~)

    findView取得view后,又交给了castView做一些容错处理。

    castView上来啥都不干直接强转并return。如果发生异常,就执行catch方法,只是抛出异常而已,我们就不看了。

    ButterKnife.bind(this)方法

    *ButterKnife.bind(this)*这个方法我们通常都在BaseActivity的onCreate方法中调用,似乎所有的findViewById方法,都被这一个bind方法化解了~

    bind有几个重载方法,但最终调的都是下面这个方法。

    参数target一般是我们的Activity,source是用来获取Context查找资源的。当target是activity时,Finder是Finder.ACTIVITY。

    首先取得target,(也就是Activity)的Class对象,根据Class对象找到生成的类,例如:ForgetActivity$$ViewBinder

    然后调用ForgetActivity$$ViewBinder的bind方法。

    然后就没有啦~看到这里你就大致明白了在程序运行过程中ButterKnife的实现原理了。下面上重头戏,ButterKnife编译时所做的工作。

    ButterKnifeProcessor

    你可能在疑惑,ButterKnife是如何识别注解的,又是如何生成代码的。

    AbstractProcessor是APT的核心类,所有的黑科技,都产生在这里。AbstractProcessor只有两个最重要的方法process 和 getSupportedAnnotationTypes。

    重写getSupportedAnnotationTypes方法,用来表示该AbstractProcessor类处理哪些注解。

    第一个最明显的就是Bind注解啦。

    而所有的注解处理,都是在process中执行的:

    通过findAndParseTargets方法拿到所有需要被处理的注解集合。然后对其进行遍历。

    JavaFileObject是我们代码生成的关键对象,它的作用是写java文件。ForgetActivity$$ViewBinder这种奇怪的类文件,就是用JavaFileObject来生成的。

    这里我们只关注最重要的一句话

    writer.write(bindingClass.brewJava());
    复制代码

    ForgetActivity$$ViewBinder中所有代码,都是通过bindingClass.brewJava方法拼出来的。

    bindingClass.brewJava方法

    哎,我不知道你看到这个代码时候,是什么感觉。反正我看到这个时候脑袋里只有一句话:好low啊……

    我根本没想到这么黑科技高大上的东西居然是这么写出来的。一行代码一行代码往出拼啊……

    既然知道是字符串拼接起来的,就没有看下去的心思了,这里就不放完整代码了。

    由此,你也知道了之前看生成的代码,为什么是用了偷懒的方法写了吧~

    总结

    当你揭开一个不熟悉领域的面纱后,黑科技好像也不过如此,甚至用字符串拼接出来的代码感觉lowlow的。

    但这不正是学习的魅力么?

    好了,总结一下。

    1. 编译时注解的魅力在于:编译时按照一定策略生成代码,避免编写重复代码,提高开发效率,且不影响性能。

    2. 代码生成与代码插入(Aspectj)是有区别的。代码插入面向切面,是在代码运行前后插入代码,新产生的代码是由原有代码触发的。而代码生成只是自动产生一套独立的代码,代码的执行还是需要主动调用才可以。

    3. APT是一套非常强大的机制,它唯一的限制在于你天马行空的设计~

    4. ButterKnife的原理其实很简单,可是为什么这么简单的功能,却写了那么多代码呢?因为ButterKnife作为一个外部依赖框架,做了大量的容错和效验来保证运行稳定。所以:写一个框架最难的不是技术实现,而是稳定!

    5. ButterKnife有一个非常值得借鉴的地方,就是如何用生成的代码对已有的代码进行代理执行。这个如果你在研究有代理功能的APT框架的话,应该好好研究一下。

    APT就好像一块蛋糕摆在你面前,就看你如何优雅的吃了。

    后续篇章我将会陆续推出几款以Cake命名的APT框架。


    原地址

    展开全文
  • java 编译时注解 Java 编译时注解 实际意义 注解处理器 实例 maven 的配置 录结构 定义 测试 AutoService 代码地址 引 Java 编译时注解 实际意义 本例仅于展现简单的编译时注解使 编译时注解可以再编译时成代码等如 ...
  • 一个基于运行时注解与编译时注解的Android Ioc Demo
  • Android编译时注解

    2018-02-23 15:07:14
    ...注解分为源码级注解、编译时注解与运行时注解,编译时注解和运行时注解时被经常用到的,但是运行时注解因为使用了反射机制,所以在性能上会有所降低,编译时注解就成了最受追捧的注解方式。...

    这个寒假用空闲时间做了一个基于编译时注解的RxBus,目前还没有做工程化处理,但是基本的功能已经写好,欢迎大家star和fork

    RxEventBus

    趁着寒假最后一天来写一下开发过程。

    编译时注解总结

    注解分为源码级注解、编译时注解与运行时注解,编译时注解和运行时注解时被经常用到的,但是运行时注解因为使用了反射机制,所以在性能上会有所降低,编译时注解就成了最受追捧的注解方式。

    annotation

    既然说是编译时注解,第一步当然是写注解的模块了,步骤如下:

    1. 新建java library(右键新建module)
    2. 编写注解,可以参考下面的
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.CLASS)
    public @interface Subscribe {
        /**
         * 订阅事件的线程
         * @return 订阅线程
         */
        ThreadMode threadMode() default ThreadMode.MAIN;
    }

    因为我们的注解是用在函数上的,所以Target就是ElementType.METHOD,还有下面几种值可以选择

    XEVS(FO[`F]I67G1ZB}@ZBI.png

    根据需要注解的元素的类型来选择值。

    注解处理器

    准备

    顾名思义,注解处理器就是用来处理注解的,而且是仅用来处理编译时注解的,要实现一个编译处理器,我们也要新建一个Java library来存放注解处理器,具体步骤如下:

    1. 新建Java library
    2. 在注解助力器的build.gradle中添加第一步所写的注解库,即
    compile project(':rxeventbusannotation')
    1. 除了导入注解库外,我们还需要导入auto-service和javapoet这两个库,如下:
    implementation 'com.squareup:javapoet:1.10.0'
    implementation 'com.google.auto.service:auto-service:1.0-rc4'

    编写注解处理器

    在配置好依赖后,接下来就可以正式地来编写注解处理器了。

    1. 新建一个processor.java文件,继承AbstractProcessor,如下:
    public class RxEventBusProcessor extends AbstractProcessor{
    
    }
    1. 添加注解,标志该注解处理器所处理的注解和版本
    @SupportedAnnotationTypes("site.gemus.rxeventbusannotation.Subscribe")
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class RxEventBusProcessor extends AbstractProcessor{
    
    }
    1. 还有注解auto-service为我们提供的服务,完成后如下:
    @AutoService(Processor.class)
    @SupportedAnnotationTypes("site.gemus.rxeventbusannotation.Subscribe")
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class RxEventBusProcessor extends AbstractProcessor{
    
    }
    1. 重写函数

    我们一般只重写最主要的几个函数,如init和process,当然process是必须要重写的,而在init中我们可以获得必要的工具

    像这样:

        private Messager mMessager;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            mMessager = processingEnvironment.getMessager();
        }

    Messager可以帮助我们打印信息,因为在注解处理器库中,我们无法使用log来进行打印

    可以这样使用:

    mMessager.printMessage(Diagnostic.Kind.WARNING, "Elements is empty!");

    注:不要轻易使用ERROR,会导致编译错误

    Element

    Element可以用来获得我们所注解的元素的信息,可以这样获得相应注解所标注的元素的信息

    Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Subscribe.class);

    Element的子类有TypeElement、PackageElement和VariableElement等,我们在获取信息时可以将它转换成这些子类来获得更详细的信息,但是需要注意,并不是Element能转换到任意一个子类,譬如说我使用的注解是用来标记函数的,就能转换为ExecutableElement,而不能转换为TypeElement、PackageElement等。

    Element的相关函数
    • getEnclosingElement()

    getEnclosingElement()这个函数的作用是获取上一个闭包元素,譬如说我的注解是在site.gemus.app.firstFragment这个类当中注解函数的,它的上一个闭包元素就是site.gemus.app.firstFragment,使用

    element.getEnclosingElement().toString();

    获取到的就是site.gemus.app.firstFragment这串字符

    • asType()

    asType()函数的作用是获得元素的类型,而不是变量的名字

    javapoet的使用

    javapoet最终的作用是生成一个.java文件,我们需要先设想好生成后的代码,然后再进行使用,网上关于javapoet的资料有很多,就不一一介绍了,可以参考

    javapoet基本使用

    特别要注意的是,我们使用javapoet导入包(import)的时候,javapoet只提供了静态导入,如果想正常导入的话,就要有意识地使用 $T 来导入包,下面就是后来改正的例子。

    ClassName eventMethodMessage = ClassName.get("site.gemus.rxeventbusannotation","EventMethodMessage");
    ClassName proxyMessageMethod = ClassName.get("site.gemus.rxeventbusannotation","ProxyMessageMethod");
    ClassName threadModeName = ClassName.get("site.gemus.rxeventbusannotation","ThreadMode");
    MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("addEventMethodMessage")
                    .addStatement("$T import1", eventMethodMessage)
                    .addStatement("$T import2", proxyMessageMethod)
                    .addStatement("$T import3", threadModeName);

    这样javapoet才会正常地导入我们自己所写的类。

    展开全文
  • 编译时注解

    2018-08-17 10:08:35
    编译时注解 上篇文章说了一下编译时注解的用法和使用步骤,并写了一个Demo编译时生成本地文件,这篇文章会通过编译时注解生成一个Java类看一下编译时注解在安卓中的具体使用. 1、新建Android工程名为 ...

    编译注解

    上篇文章说了一下编译时注解的用法和使用步骤,并写了一个Demo编译时生成本地文件,这篇文章会通过编译时注解生成一个Java类看一下编译时注解在安卓中的具体使用.

    1、新建Android工程名为 AnnotationPractice,完成后,新建一个Module选择Java Library 名为 testlib.
    2、在Java工程中定义注解类GenerateCode.
    @Documented()
    @Retention(RetentionPolicy.CLASS)
    @Target({ElementType.TYPE})
    public @interface GenerateCode {
    }
    
    3、在Java工程中定义注解处理器GenerateProcessor
    public class GenerateProcessor extends AbstractProcessor {
        private Messager messager; // 输入日志
        private Filer filer; // 生成文件
        private Elements elementsUtils; // Element工具
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return Collections.singleton(GenerateCode.class.getCanonicalName());
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            messager = processingEnvironment.getMessager();
            filer = processingEnvironment.getFiler();
            elementsUtils = processingEnvironment.getElementUtils();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);
    
            if (elements.iterator().hasNext()) {
                Element element = elements.iterator().next(); // 获取set集合的第一个元素
                if (ElementKind.CLASS == element.getKind()) {
                    TypeElement typeElement = (TypeElement) element;
                    final String pkgName = getPackageName(typeElement); // 获取包名
                    messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义
    
                    final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名
    
                    String code = jointCode(pkgName, clsName);
    
                    Writer writer = null;
                    try {
                        JavaFileObject file = filer.createSourceFile(pkgName + "." + clsName);
                        writer = file.openWriter();
                        writer.write(code);
    
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            if (writer != null)
                                writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
    
    
                }
            }
    
            return true;
        }
    
        /**
         * 拼接代码
         *
         * @param pkgName 包名
         * @param clsName 类名
         * @return  拼接效果下面这样
         *
          package pkgName;
    
          public class clsName {
              public void print() {
                 System.out.println("Hello");
              }
          }
         */
        private String jointCode(String pkgName, String clsName) {
            StringBuilder builder = new StringBuilder();
            builder.append("package ");
            builder.append(pkgName);
            builder.append(";");
            builder.append("\n\n");
            builder.append("public class ");
            builder.append(clsName);
            builder.append(" {");
            builder.append("\n");
            builder.append("\tpublic void print() {");
            builder.append("\n");
            builder.append("\t\tSystem.out.println(\"Hello\");");
            builder.append("\n");
            builder.append("\t}");
            builder.append("\n");
            builder.append("}");
    
            return builder.toString();
        }
    
        private String getPackageName(TypeElement type) {
            return elementsUtils.getPackageOf(type).getQualifiedName().toString();
        }
    
    }
    
    4、在main下新建resources文件夹,在resources下新建META-INF文件夹,在META-INF下新建services文件夹,在services下新建文件javax.annotation.processing.Processor,在javax.annotation.processing.Processor文件中写如文件注解器全限定名
    com.example.bill.testlib.GenerateProcessor
    
    5、编译。 如果配置好gradle环境变量,在Java Module根目录下,即testlib下使用 gradle build 命令编译,编译成功后会在testlib的build下生成jar包(AnnotationPractice/testlib/build/libs/testlib.jar).
    6、在app中使用
    • 将编译好的testlib.jar拷贝到app的libs下,在app的build.gradle下dependencies中引入jar包
    annotationProcessor files('libs/testlib.jar')
    
    • 在app中使用注解,在MainActivity上使用注解GenerateCode
    package com.bill.annotationpractice;
    
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    
    import com.example.bill.testlib.GenerateCode;
    
    @GenerateCode
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
        }
    
    }
    
    
    7、编译。 在app目录下使用 gradle build 命令编译,编译成功后会在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成我们编写的 MainActivity $GA 类,MainActivity $GA (注意MainActivity和 $GA是整个的,csdn上转意有问题,我写的加了个空格) 就可以在app中使用了。生成的类如下
    package com.bill.annotationpractice;
    
    public class MainActivity$GA {
    	public void print() {
    		System.out.println("Hello");
    	}
    }
    

    eg:AnnotationPractice-branch4

    上面代码通过编译时生成了Java文件,但是需要手动拼接生成的代码和注解处理器的配置信息.下面介绍两个开源库开简化这两步,Google和Square开源的两个库分别用来生成注解处理器的配置信息和生成Java类的

    具体使用如下

    • 在testlib的build.gradle中的dependencies下引入
    dependencies {
        compile 'com.google.auto.service:auto-service:1.0-rc2'
        compile 'com.squareup:javapoet:1.7.0'
    }
    
    • 注解处理器类上使用@AutoService(Processor.class),他是Google提供的作用是生成注解处理器的配置信息,代替上面的第4步的

    • 在注解处理器中使用javapoet中的api生成代码

        MethodSpec methodSpec = MethodSpec.methodBuilder("print")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) // 方法修饰符
                .returns(void.class) // 方法返回值
                .addStatement("$T.out.println($S)", // 参数 System.class, "Hello") // 内容
                .build();
        TypeSpec typeSpec = TypeSpec.classBuilder(clsName) // 类名
                .addModifiers(Modifier.PUBLIC) // 类修饰符
                .addMethod(methodSpec) //类中的方法
                .build();
        JavaFile javaFile = JavaFile.builder(pkgName, typeSpec) // 包名和类
                .build();
        try {
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
    

    下面通过这两个库来替换上面的Demo

    上面第1步和第2步不变,将第3步和第4步去掉,注解处理器改为下面

    @AutoService(Processor.class)
    public class GenerateProcessor extends AbstractProcessor {
    
        private Messager messager; // 输入日志
        private Filer filer; // 生成文件
        private Elements elementsUtils; // Element工具
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            return Collections.singleton(GenerateCode.class.getCanonicalName());
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            messager = processingEnvironment.getMessager();
            filer = processingEnvironment.getFiler();
            elementsUtils = processingEnvironment.getElementUtils();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);
    
            if (elements.iterator().hasNext()) {
                Element element = elements.iterator().next(); // 获取set集合的第一个元素
                if (ElementKind.CLASS == element.getKind()) {
                    TypeElement typeElement = (TypeElement) element;
                    final String pkgName = getPackageName(typeElement); // 获取包名
                    messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义
    
                    final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名
    
    
                    MethodSpec methodSpec = MethodSpec.methodBuilder("print")
                            .addModifiers(Modifier.PUBLIC)
                            .returns(void.class)
                            .addStatement("$T.out.println($S)", System.class, "Hello")
                            .build();
                    TypeSpec typeSpec = TypeSpec.classBuilder(clsName)
                            .addModifiers(Modifier.PUBLIC)
                            .addMethod(methodSpec)
                            .build();
                    JavaFile javaFile = JavaFile.builder(pkgName, typeSpec)
                            .build();
                    try {
                        javaFile.writeTo(filer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
                }
            }
    
            return true;
        }
    
        private String getPackageName(TypeElement type) {
            return elementsUtils.getPackageOf(type).getQualifiedName().toString();
        }
    
    
    }
    

    还是上面第5步编译生成jar包,上面第6步在app中引入使用,第7步中编译,然后会报下面错误,就是找不到javapoet包,因为上面编译生成jar并没有将javapoet的代码打到包里,所以找不到
    s1

    下面通过引用module的方式引入testlib,在app的build.gradle的dependencies中加入

    implementation project(path: ':testlib')
    annotationProcessor project(path: ':testlib')
    

    然后在app下编译报错
    s2
    按照错误提示在app的build.gradle的android下加入下面代码

    lintOptions {
        abortOnError false
    }
    

    编译成功,在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成MainActivity$GA类

    package com.bill.annotationpractice;
    
    public class MainActivity$GA {
    	public void print() {
    		System.out.println("Hello");
    	}
    }
    

    eg:AnnotationPractice-branch5

    下篇文章 自定义注解-实现findViewById和onClick

    展开全文
  • 编译时注解解析

    千次阅读 2017-06-28 17:57:43
    【参考资料】 ...   当注解的@Retention为CLASS时,...如果一个元素上面使用了编译时注解,那么我们可以在编译阶段,解析该元素上的编译时注解,生成自己想要的.java源代码文件。 解析编译时注解需要用到注解处理器(A
  • Java编译时注解处理器

    2020-04-05 23:10:14
    Java注解处理器 Android 如何编写基于编译时注解的项目 Java编译时注解处理器(APT)详解
  • Android 编译时注解

    千次阅读 2019-01-18 18:22:23
    Android 编译时注解-初认识, 和 Android 编译时注解-提升后写的,因为原博主的例子是基于Android Studio 2,而我使用的是3,所以在实践的时候遇到了一些问题,现在我在这里做一点记录: 一、Annotation processors ...
  • 编译时注解

    2018-08-17 09:37:03
    编译时注解 编译时注解使用 RetentionPolicy.CLASS 修饰,注解会保留在java和class文件里,在执行的时候,会被虚拟机丢弃,不会加载到虚拟机中。由于使用 RetentionPolicy.CLASS 注解代码的生成发生在编译...
  • lombok-ex lombok-ex 是一款类似于 lombok 的编译时注解框架 编译时注解拥有运行时注解的便利性和无任何损失的性能 主要补充一些 lombok 没有实现且自己会用到的常见工具 创作目的 补充 lombok 缺失的注解便于日常...
  • 关于编译时注解

    2017-11-24 10:33:46
    编译时注解对比运行时注解的优势在于。它是在对程序进行编译时期。根据你的注解处理器的逻辑。处理该注解。并生成相应的java文件。提供给你调用。所以。使用编译时注解。你程序运行时。是使用的生成的普通java类来...
  • 前面阅读了ARouter的源码, 他主要是通过编译时注解来实现主要的逻辑的, 我们仿照他来实现下编译时注解 1. module结构 如上, 我们新建两个Java-library my-annotator 用于声明注解 my-annotator-compiler 用于在...
  • Java 编译时注解 实际意义 注解处理器 实例 maven 的配置 目录结构 定义 测试 AutoService 代码地址 引用 系列导航 Java 编译时注解 实际意义 本例子仅用于展现简单的编译时注解使用。 编译时注解,...
  • android编译时注解

    2020-03-31 16:31:01
    android编译时注解前言第一步 创建模块第二步 导入依赖第三步 编写代码第四步 自动生成的结果最后总结下遇到的坑 前言 总结一下仿写的ButterKnife,以及所遇到的坑. 第一步 创建模块 创建两个Java Library 和一个...
  • 编译时注解参考文献

    2017-07-24 14:32:48
    自定义注解之编译时注解(RetentionPolicy.CLASS)(一)Java注解处理器鸿洋的利用处理器完成ioc
  • android 编译时注解

    2019-10-09 01:39:48
    android 编译时注解 前言 我们经常使用的一些第三方框架,比如:butterknife,通过一行注解就可以实现View 的“自动赋值”。 那么,这其中的原理是什么呢? 为了带大家更好的深入了解,本文将打造一...
  • 我们知道,在日常开发中我们常用的两种注解是运行时注解和编译时注解,运行时注解是通过反射来实现注解处理器的,对性能稍微有一点损耗,而编译时注解是在程序编译期间生成相应的代理类,替我们完成某些功能。...
  • 安卓中很多有名的开源框架都运用了编译时注解,如ButterKnife,EventBus , Retrofit , GreenDao等。所以作为一个合格的安卓开发者,学会运用编译时注解是非常有必要的。 下面就仿照ButterKnife的view的注入写一个...
  • java 编译时注解框架 lombok-ex

    万次阅读 2019-10-03 13:30:23
    lombok-ex 是一款类似于 lombok 的编译时注解框架。 编译时注,拥有运行时注解的便利性,和无任何损失的性能。 主要补充一些 lombok 没有实现,且自己会用到的常见工具。 创作目的 补充 lombok 缺失的注解,便于...
  • 今天看一下编译时注解的相关知识,相信手动实践后你会更容易理解像 Dagger、ARouter、ButterKnife 等这种使用了编译时注解的框架,也更容易理解其内部源码实现,内容如下: 编译时和运行时注解 注解处理器APT ...
  • 要定义编译时注解只需在定义注解时使用@Retention(RetentionPolicy.CLASS)修饰即可,编译时注解结合注解处理器(APT)在编译期完成操作,对性能影响很小;像@Nullable@NonNull这类的注解就是编译时注解;一些开源...
  • Android 编译时注解生成代码

    千次阅读 2017-08-05 16:29:11
    Android 编译时注解生成代码* 本项目 只是学习使用,项目中推荐ButterKnife*1 简介  在现阶段的Android开发中,注解越来越流行起来,比如ButterKnife,Retrofit,Dragger,EventBus等等都选择使用注解来配置。...
  • 自定义注解之编译时注解(RetentionPolicy.CLASS)(一) 自定义注解之编译时注解(RetentionPolicy.CLASS)(二)——JavaPoet 自定义注解之编译时注解(RetentionPolicy.CLASS)(三)—— 常用接口介绍 说到编译时...
  • 编译时注解之APT

    2016-12-11 12:49:23
    编译时注解之APT 0x00 概述 注解系列 注解基础 JavaPoet 编译期注解处理之APT 前一篇介绍了注解的基本知识以及常见用法,由于运行期(RunTime)利用反射去获取信息还是比较损耗性能的,本篇将介绍一种使用注解更加...
  • 概述 ...此篇文章主要讲一下编译时注解的使用,同时也是以”Activity路由“的Demo为例子。 本篇的Demo主要是演示了使用编译时注解来创建文件的功能。 主要模块 anotationrouter:创建注解 pro...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,788
精华内容 1,515
关键字:

编译时注解