深入理解编译注解(一)从实战理解什么是编译注解

阅读: 评论:0

深入理解编译注解(一)从实战理解什么是编译注解

深入理解编译注解(一)从实战理解什么是编译注解

前言

之前我们已经理解了什么是运行时注解,并且实现了一个含金量较高的数据库框架,同时我们也发现,使用反射会使运行的效率的变低,很多流行的注解框架已经考虑用编译注解来解决这个问题,今天我们用编译注解的形式来实现setContentView和findViewById。

正文

首先,我们来弄清楚使用编译注解的目的和优缺点:

编译注解主要是在编译过程中,生成必要的文件,这样在运行时调用,就不需要再通过大量的反射(低效)来进行操作。

这种形式大大提高了注解在运行时的效率,但同时也增加了编译的时间。

我们的最终目的就是让应用跑的更加流畅,所有编译时间的增加,我们还是可以接受的。

下面看一下具体的准备:

我画了一个简单的关系图,我们需要创建三个Library和一个demoModule,要注意的是:

注解库和编译库,必须为Java Library,且指定编译的Jdk为1.7。

创建四个库之后,按照上图进行依赖关系,其中编译的关系,请使用:

annotationProcessor project(':ioc-compiler')

这个插件提供了编译关系,与依赖关系不同,对应的库不会被打入到apk中,但是会编译库中的代码,会在之后的在具体的说明。

先来看看注解库:

/*** Created by li.zhipeng on 2017/3/17.**      绑定View的注解,实现findViewById的功能*/@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {int value();
}/*** Created by li.zhipeng on 2017/3/21.**      绑定ContentView的注解*/@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface ContentView {int value();
}// gradle文件
apply plugin: 'java'dependencies {compile fileTree(dir: 'libs', include: ['*.jar'])targetCompatibility = '1.7'sourceCompatibility = '1.7'
}

那么之后就是最重要的编译库了,我们最核心的内容都在这里,先看一下gradle文件:

apply plugin: 'java'dependencies {compile fileTree(dir: 'libs', include: ['*.jar']) // auto-service库可以帮我们去生成META-INF等信息。compile &#le.auto.service:auto-service:1.0-rc2'compile project (':ioc-annotation') // 如果找不到javax包,可以直接引入本地jdk的jar包compile files ('/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home/jre/lib/rt.jar')targetCompatibility = '1.7'sourceCompatibility = '1.7'
}

其中有注释写到,如果在使用javax包中的类的时,出现找不到指定的类的错误,需要自己手动的去导入jdk的jar包。

为了完整编译的操作,我在编译库中创建了三个类,分别提出他们的代码,里面已经有很详细的注释:

package com.lzp.ioc;import del.element.Element;
import del.element.Modifier;
import del.element.TypeElement;/*** Created by li.zhipeng on 2017/3/17.**      类的工具类*/public class ClassValidator {/*** 判断是否是private修饰* */static boolean isPrivate(Element annotatedClass){Modifiers().contains(Modifier.PRIVATE);}/*** 获取类的完整路径* */static String getClassName(TypeElement type, String packageName){int packageLen = packageName.length() + 1;QualifiedName().toString().substring(packageLen).replace(".", "$");}}
package com.lzp.ioc;import java.util.HashMap;
import java.util.Map;import del.element.PackageElement;
import del.element.TypeElement;
import del.element.VariableElement;
import del.util.Elements;/*** Created by li.zhipeng on 2017/3/17.**      含有注解信息的辅助类*/public class ProxyInfo {/*** 包名* */private String packageName;/*** 要编译生成的类名* */private String proxyClassName;/*** 注解修饰的元素* */private TypeElement typeElement;/*** 保存了所有的BindView 注解的信息* */public Map<Integer, VariableElement> injectVariables = new HashMap<>();/*** contentView的id* */public int contentViewId;public static final String PROXY = "ViewInject";public ProxyInfo(Elements elementUtils , TypeElement classElement){peElement = classElement;PackageElement packageElement = PackageOf(typeElement);String packageName = QualifiedName().toString();String className = ClassName(typeElement, packageName);this.packageName = packageName;this.proxyClassName = className + "$$" + PROXY;}/*** 生成的java文件的代码* */public String generateJavaCode(){StringBuilder builder = new StringBuilder();builder.append("// Generated code. Do not modify!n");builder.append("package ").append(packageName).append(";nn");builder.append("import com.lzp.ioc.*;n");builder.append('n');builder.append("public class ").append(proxyClassName).append(" implements ").append(ProxyInfo.PROXY).append("<").QualifiedName()).append(">");builder.append("{n");generateMethods(builder);builder.append('n');builder.append("}n");String();}/*** 生成inject方法的代码* */private void generateMethods(StringBuilder builder){builder.append("@Overriden");builder.append("public void inject(").QualifiedName()).append(" host, Object source) {n");StringBuilder ifStr = new StringBuilder();StringBuilder elseStr = new StringBuilder();//遍历所有的BindView注解的信息for (int id : injectVariables.keySet()){VariableElement variableElement = (id);String name = SimpleName().toString();String type = variableElement.asType().toString();ifStr.append("host.").append(name).append(" = ");ifStr.append("(").append(type).append(")(((android.app.Activity)source).findViewById(").append(id).append("));");elseStr.append("host.").append(name).append(" = ");elseStr.append("(").append(type).append(")(((android.view.View)source).findViewById(").append(id).append("));");}// ifbuilder.append(" if(source instanceof android.app.Activity) {n");// 设置ContentViewif (contentViewId != 0){builder.append("host.setContentView(").append(contentViewId).append(");n");}builder.append(ifStr);// else// 如果是View类型,不用设置ContentViewbuilder.append("n}nelse {n");builder.append(elseStr);builder.append("n}n");builder.append("};");}public String getProxyClassFullName(){return packageName + "." + proxyClassName;}public TypeElement getTypeElement(){return typeElement;}}
package com.lzp.ioc;le.auto.service.AutoService;
import com.lzp.io.BindView;
import com.lzp.io.ContentView;import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import del.SourceVersion;
import del.element.Element;
import del.element.ElementKind;
import del.element.TypeElement;
import del.element.VariableElement;
import del.util.Elements;
ls.Diagnostic;
ls.JavaFileObject;/*** Created by li.zhipeng on 2017/3/17.**      自定义编译、处理器*/@AutoService(Processor.class)
public class IocProcessor extends AbstractProcessor {/*** 日志打印类* */private Messager messager;/*** 元素工具类* */private Elements elementsUtils;/*** 保存所有的要生成的注解文件信息* */private Map<String, ProxyInfo> mProxyMap = new HashMap<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);messager = Messager();elementsUtils = ElementUtils();// 在这里打印gradle文件传进来的参数Map<String, String> map = Options();for (String key : map.keySet()) {messager.printMessage(Diagnostic.Kind.NOTE, "key" + ":" + (key));}}/*** 此方法用来设置支持的注解类型,没有设置的无效(获取不到)* */@Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> supportTypes = new LinkedHashSet<>();// 把支持的类型添加进去supportTypes.add(CanonicalName());supportTypes.add(CanonicalName());return supportTypes;}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {messager.printMessage(Diagnostic.Kind.NOTE, &#");/*** 防止处理多次,要清空* */mProxyMap.clear();// 获取全部的带有BindView注解的ElementSet<? extends Element> elesWidthBind = ElementsAnnotatedWith(BindView.class);// 对BindView进行循环,构建ProxyInfo信息// 对BindView进行循环,构建ProxyInfo信息for (Element element : elesWidthBind) {// 检查element的合法性checkSAnnotationValid(element, BindView.class);// 强转成属性元素VariableElement variableElement = (VariableElement) element;// 我们知道属性元素的外层一定是类元素TypeElement typeElement = (TypeElement) EnclosingElement();// 获取类元素的类名String fqClassName = QualifiedName().toString();// 以class名称为key,保存到mProxyMap中ProxyInfo proxyInfo = (fqClassName);if (proxyInfo == null) {proxyInfo = new ProxyInfo(elementsUtils, typeElement);mProxyMap.put(fqClassName, proxyInfo);}// 获取BindView注解,把信息放入proxyInfo中BindView bindAnnotation = Annotation(BindView.class);int id = bindAnnotation.value();proxyInfo.injectVariables.put(id, variableElement);}// 获取所有的ContentView注解,操作原理和上面的BindView一样Set<? extends Element> contentAnnotations = ElementsAnnotatedWith(ContentView.class);for (Element element : contentAnnotations) {TypeElement typeElement = (TypeElement) element;String fqClassName = QualifiedName().toString();ProxyInfo proxyInfo = (fqClassName);if (proxyInfo == null) {proxyInfo = new ProxyInfo(elementsUtils, typeElement);mProxyMap.put(fqClassName, proxyInfo);}ContentView contentViewAnnotation = Annotation(ContentView.class);tViewId =contentViewAnnotation.value();}// 循环生成源文件for (String key : mProxyMap.keySet()) {ProxyInfo proxyInfo = (key);try {JavaFileObject jfo = Filer().ProxyClassFullName(), TypeElement());Writer writer = jfo.openWriter();writer.ateJavaCode());writer.flush();writer.close();} catch (IOException e) {TypeElement(), "Unable to write injector for type %s: %s ", TypeElement(), e.getMessage());}}return true;}/*** 检查BindView修饰的元素的合法性* */private boolean checkSAnnotationValid(Element element, Class<?> clazz) {if (Kind() != ElementKind.FIELD) {error(element, "%s must be delared on field.", SimpleName());return false;}if (ClassValidator.isPrivate(element)) {error(element, "%s() must can not be private.", SimpleName());return false;}return true;}/*** 打印错误日志方法* */private void error(Element element, String message,  args) {if (args.length > 0) {message = String.format(message, args);}messager.printMessage(Diagnostic.Kind.NOTE, message, element);}
}

现在来挑重点的地方来说明一下:

1、gradle文件中 compile ‘le.auto.service:auto-service:1.0-rc2’,这是google提供的编译处理的库,继承AbstractProcessor自定义编译处理器,然后通过注解@AutoService(Processor.class),就会在编译时自动执行。

2、 在AbstractProcessor的核心方法 public boolean process(Set annotations, RoundEnvironment roundEnv) ,有两个参数,他们都可以获取注解信息,但其实他俩有根本的区别:

Set annotations
里面包含的是所有使用的注解的信息,例如BindView,ContentView

RoundEnvironment roundEnv
他返回的是所有被注解的元素,例如类,属性等

3、JavaFileObject是java文件对象,可以直接创建,通过流的形式,对文件进行编写。

现在看一下api库:

package com.lzp.ioc;/*** Created by li.zhipeng on 2017/3/17.**      注入接口*/public interface ViewInject<T> {void inject(T target, Object source);
}
package com.lzp.ioc;import android.app.Activity;
import android.view.View;/*** Created by li.zhipeng on 2017/3/17.**      提供注入的静态方法,间接调用了io-complier的编译生成的类方法*/public class ViewInjector {private static final String SUFFIX = "$$ViewInject";public static void injectView(Activity activity) {ViewInject proxyActivity = findProxyActivity(activity);proxyActivity.inject(activity, activity);}public static void injectView(Object object, View view) {ViewInject proxyActivity = findProxyActivity(object);proxyActivity.inject(object, view);}/*** 通过反射创建要使用的类的对象* */private static ViewInject findProxyActivity(Object activity) {try {Class<?> clazz = Class();Class<?> injectorClazz = Class.Name() + SUFFIX);return (ViewInject) wInstance();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}throw new RuntimeException(String.format("can not find %s , something when compiler.", Class().getSimpleName() + SUFFIX));}
}

在编译库中定义了命名规则,类名+SUFFIX,通过反射创建一个对象就得到了编译的文件,然后调用文件中的方法。

最后就是Demo的MainActivity:

package com.lzppileannotationstudy;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.TextView;import com.lzp.io.BindView;
import com.lzp.io.ContentView;
import com.lzp.ioc.ViewInjector;@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {@BindView(View)public TextView textView;@BindView(R.id.imageView)ImageView imageView;@Overrideprotected void onCreate(Bundle savedInstanceState) {Create(savedInstanceState);ViewInjector.injectView(this);textView.setText("hahahahaha");}
}

看一下运行效果:

再看一下编出来的文件,这就是通过JavaFileObject写出来的文件:

ok,我们的编译注解运行的很成功!!!

总结

虽然是看完了整体的大概流程,但是肯定还会有人觉得一头雾水,有种似懂非懂的感觉,那是因为我们对细节还不够了解,对新的api都刚刚接触,所以要拿出更多的精力,仔细的去体会感受,相信对看了几遍之后,一定会有大众焕然大悟的感觉。

由于每个人的思维不同,我也很难做到各个知识点面面俱到,如果你有什么问题,可以留言。

下一篇,来聊一聊annotationProcessor这个框架。

点击下载源码(包含之后讲的内容,可以暂时忽略)

github下载地址

本文发布于:2024-02-01 08:01:42,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170674570235064.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:注解   实战
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23