技术标签: android jetpack 数据库 Android笔记
Butterknife现在在项目中基本没用到了,逐渐被ViewBinding所代替,而我们所熟知它的内部原理是通过自定义注解+自定义注解解析器来动态生成代码并为我们的view绑定id的。今天就通过重新手写ButterKinife来搞明白我们今天的主角–Anotation Processing(注解处理器)。
源码地址:APTDemo
在写注解处理器之前,先用运行时注解来操作下。这里我们先新建一个library取名lib-reflection
然后自定义注解,我们只实现了View与id的绑定功能,所以我们这里定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
Target的Type说明这是一个用于修饰类和接口的注解,它也就是成员变量,即需要绑定资源id的view成员。
同时这个注解Retention是RUNTIME,表示注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。
注解定义好后就可以直接在项目中使用了;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Binding.bind(this);
textView.setText("哈哈哈哈");
}
}
注意这里我们加了个Binding.bind(this)
,就是它来帮助我们做注解的解析,之后在内部调用MainActivity.textView=(TextView)MainActivity.findViewById()
来实现为view绑定id的。还是在刚才的目录lib-reflect
下创建了Binding
类,具体代码如下:
public class Binding {
public static void bind(Activity activity){
//反射获取注解注释
for (Field field: activity.getClass().getDeclaredFields()){
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null){
try {
//扩大范围
field.setAccessible(true);
field.set(activity, activity.findViewById(bindView.value()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
再运行就可以绑定了view的id了,不再需要我们手动的绑定id了。但是依靠反射始终是消耗性能的,这时候就用到我们的注解处理器了。
这里,我们新创建个Java Library
的项目,就叫lib-processor
吧。然后再这个目录下创建BindingProcessor
,继承自AbstractProcessor
。这个就是用于解析自定义注解的解析器了。不过要想让它生效还必须在resource
下新建如下目录(现在google给提供了一个注册处理器的库@AutoService(Processor.class)的注解来简化我们的操作。):
javax.annotation.processing.Processor
的文本文件里面内容就一行:
com.pince.lib_processor.BindingProcessor
接下来将之前定义的BindView
注解改为编译时注解,放进新创建的lin-annotations
目录下:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
还需要修改build.gradle
文件,加入:
app依赖:
implementation project(':lib')
annotationProcessor project(':lib-processor')
lib-processor依赖:
implementation project(':lib-annotations')
lib依赖:
//主项目需要的依赖
api project(':lib-annotations')
这么做就是为了让编译器使用我们的解析器用于解析注解。
后面的的工作都是在BindingProcessor
操作了。通过读取类中的自定义注解,生成相应的绑定视图的代码,还需要引入一个库。
compile 'com.squareup:javapoet:1.9.0'
先展示下最后自动生成的类:
public class MainActivityBinding {
public MainActivityBinding(MainActivity activity) {
activity.textView = activity.findViewById(2131231093);
}
}
上面的内容就是由javapoet
生成的,下面就按照上面这个最终效果来一步一步分析要怎么生成我们的代理类。
看下面我们需要创建一个构造函数<? extend Activity>作为参数传入:
String packageStr = element.getEnclosingElement().toString();
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(packageStr, classStr), "activity");
然后查找所有标注了BidView注解的成员变量:
for (Element enclosedElement: element.getEnclosedElements()){
if (enclosedElement.getKind() == ElementKind.FIELD){
//寻找BindView注释
BindView bindView = enclosedElement.getAnnotation(BindView.class);
if (bindView != null){
....
}
}
}
再然后是生成findViewById的具体语句代码:
for (Element enclosedElement: element.getEnclosedElements()){
if (enclosedElement.getKind() == ElementKind.FIELD){
//寻找BindView注释
BindView bindView = enclosedElement.getAnnotation(BindView.class);
if (bindView != null){
hasBinding = true;
constructorBuilder.addStatement("activity.$N = activity.findViewById($L)",
enclosedElement.getSimpleName(), bindView.value());
}
}
}
做好了上面步骤,主要的代码也写完了,最后就是生成这个MainActivityBinding
这个类:
String packageStr = element.getEnclosingElement().toString();
ClassName className = ClassName.get(packageStr, classStr + "Binding");
TypeSpec builtClass = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.addMethod(constructorBuilder.build())
.build();
try {
JavaFile.builder(packageStr, builtClass)
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
注意这里的包名,生成的类的包名尽量与需要绑定的Activity所在的包名一致,这样BindView修饰的成员变量只需是包内可见就行。到了这里我们再执行./gradlew :app:compileDebugJava
编译就可以自动生成我们所需要的类了。
到了这一步还没完,我们还需要在lib moodule
目录下创建新的Binding
帮助类:
public class Binding {
public static void bind(Activity activity){
try {
Class bindingClass = Class.forName(activity.getClass().getCanonicalName() + "Binding");
Constructor constructor = bindingClass.getDeclaredConstructor(activity.getClass());
constructor.newInstance(activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
在这里利用了一点反射new出了MainActivityBinding实体,传入相应的activity
在内部进行绑定操作。到这里简单的ButterKnife
版本就实现了。下面BindingProcessor
给出完整代码:
public class BindingProcessor extends AbstractProcessor {
Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//使用Filer你可以创建文件
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取到全部的类
//Element代表的是源代码
// Element可以是类、方法、变量等
for (Element element: roundEnv.getRootElements()){
String packageStr = element.getEnclosingElement().toString();
String classStr = element.getSimpleName().toString();
//构建新的类的名字:原类名 + Binding
ClassName className = ClassName.get(packageStr, classStr + "Binding");
构建新的类的构造方法
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(packageStr, classStr), "activity");
boolean hasBinding = false;
//还有个getEnclosingElement 单数 被包住的外层
//子类里的元素 字段 方法 内部类
for (Element enclosedElement: element.getEnclosedElements()){
//仅获取成员变量
if (enclosedElement.getKind() == ElementKind.FIELD){
//寻找BindView注解
BindView bindView = enclosedElement.getAnnotation(BindView.class);
if (bindView != null){
hasBinding = true;
//在构造方法中加入代码
constructorBuilder.addStatement("activity.$N = activity.findViewById($L)",
enclosedElement.getSimpleName(), bindView.value());
}
}
}
TypeSpec builtClass = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC)
.addMethod(constructorBuilder.build())
.build();
if (hasBinding){
try {
//生成 Java 文件
JavaFile.builder(packageStr, builtClass)
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//对这个注解进行注解处理
return Collections.singleton(BindView.class.getCanonicalName());
}
}
源码地址:APTDemo
元注解是用来定义其他注解的注解(在自定义注解的时候,需要使用到元注解来定义我们的注解)。java.lang.annotation提供了四种元注解:@Retention
、 @Target
、@Inherited
、@Documented
。
元注解 | 说明 |
---|---|
@Target | 表明我们注解可以出现的地方。是一个ElementType枚举 |
@Retention | 这个注解的的存活时间 |
@Document | 表明注解可以被javadoc此类的工具文档化 |
@Inherited | 是否允许子类继承该注解,默认为false |
@Target-ElementType类型 | 说明 |
---|---|
ElementType.TYPE | 接口、类、枚举、注解 |
ElementType.FIELD | 字段、枚举的常量 |
ElementType.METHOD | 方法 |
ElementType.PARAMETER | 方法参数 |
ElementType.CONSTRUCTOR | 构造函数 |
ElementType.LOCAL_VARIABLE | 局部变量 |
ElementType.ANNOTATION_TYPE | 注解 |
ElementType.PACKAGE | 包 |
表示需要在什么几倍保存该注释信息,用于描述注解的生命周期。
@Retention-RetentionPolicy类型 | 说明 |
---|---|
RetentionPolicy.SOURCE | 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃 |
RetentionPolicy.CLASS | 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 |
RetentionPolicy.RUNTIME | 解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 |
@Document表明我们标记的注解可以被javadoc此类的工具文档化
@Inherited表明我们标记的注解是被继承的。比如,如果一个父类使用了@Inherited修饰的注解,则允许子类继承该父类的注解
Class类里面常用方法如下:
/**
* 包名加类名
*/
public String getName();
/**
* 类名
*/
public String getSimpleName();
/**
* 返回当前类和父类层次的public构造方法
*/
public Constructor<?>[] getConstructors();
/**
* 返回当前类所有的构造方法(public、private和protected)
* 不包括父类
*/
public Constructor<?>[] getDeclaredConstructors();
/**
* 返回当前类所有public的字段,包括父类
*/
public Field[] getFields();
/**
* 返回当前类所有申明的字段,即包括public、private和protected,
* 不包括父类
*/
public native Field[] getDeclaredFields();
/**
* 返回当前类所有public的方法,包括父类
*/
public Method[] getMethods();
/**
* 返回当前类所有的方法,即包括public、private和protected,
* 不包括父类
*/
public Method[] getDeclaredMethods();
/**
* 获取局部或匿名内部类在定义时所在的方法
*/
public Method getEnclosingMethod();
/**
* 获取当前类的包
*/
public Package getPackage();
/**
* 获取当前类的包名
*/
public String getPackageName$();
/**
* 获取当前类的直接超类的 Type
*/
public Type getGenericSuperclass();
/**
* 返回当前类直接实现的接口.不包含泛型参数信息
*/
public Class<?>[] getInterfaces();
/**
* 返回当前类的修饰符,public,private,protected
*/
public int getModifiers();
Field和Method都实现了AnnotatedElement接口,常用方法如下:
/**
* 指定类型的注释是否存在于此元素上
*/
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}
/**
* 返回该元素上存在的指定类型的注解
*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
/**
* 返回该元素上存在的所有注解
*/
Annotation[] getAnnotations();
/**
* 返回该元素指定类型的注解
*/
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
}
/**
* 返回直接存在与该元素上的所有注释(父类里面的不算)
*/
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
Objects.requireNonNull(annotationClass);
// Loop over all directly-present annotations looking for a matching one
for (Annotation annotation : getDeclaredAnnotations()) {
if (annotationClass.equals(annotation.annotationType())) {
// More robust to do a dynamic cast at runtime instead
// of compile-time only.
return annotationClass.cast(annotation);
}
}
return null;
}
/**
* 返回直接存在该元素岸上某类型的注释
*/
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
return AnnotatedElements.getDirectOrIndirectAnnotationsByType(this, annotationClass);
}
/**
* 返回直接存在与该元素上的所有注释
*/
Annotation[] getDeclaredAnnotations();
注解处理器(Annotation Processor)是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册相应的注解处理器(自定义的注解处理器需继承自AbstractProcessor)。
定义一个注解处理器,需要继承自AbstractProcessor。如下所示:
public class MyProcessor extends AbstractProcessor {
/**
* 每个Annotation Processor必须有一个空的构造函数。
* 编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnvironment参数,
* 通过该参数可以获取到很多有用的工具类(Element,Filer,Messager等)
*/
@Override
public synchronized void init(ProcessingEnvironment env){
}
/**
* 用于指定自定义注解处理器(Annotation Processor)是注册给哪些注解的(Annotation),
* 注解(Annotation)指定必须是完整的包名+类名
*/
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
}
/**
* 用于指定你的java版本,一般返回:SourceVersion.latestSupported()
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
}
/**
* Annotation Processor扫描出的结果会存储进roundEnvironment中,可以在这里获取到注解内容,编写你的操作逻辑。
* 注意:process()函数中不能直接进行异常抛出,否则程序会异常崩溃
*/
@Override
public SourceVersion getSupportedSourceVersion() {
}
}
注处理器的核心是process()方法,而process方法的核心是Element元素。Element里的元素可以是类、方法或是变量。在操作注解的过程中,编译器会扫描所有的Java文件,并将每一个部分看做是特定类型的的Element。可以代表包、类、接口、方法、字段等多种元素种类。
Element子类 | 解释 |
---|---|
TypeElement | 类或接口元素 |
VariableElement | 字段、enum常量、方法或构造方法参数、局部变量或异常参数元素 |
ExecutableElement | 类或接口的方法、构造方法,或者注解类型元素 |
PackageElement | 包元素 |
TypeParameterElement | 类、接口、方法或构造方法元素的泛型参数 |
Element类常用方法如下:
/**
* 返回此元素定义的类型,int,long这些
*/
TypeMirror asType();
/**
* 返回此元素的种类:包、类、接口、方法、字段
*/
ElementKind getKind();
/**
* 返回此元素的修饰符:public、private、protected
*/
Set<Modifier> getModifiers();
/**
* 返回此元素的简单名称(类名)
*/
Name getSimpleName();
/**
* 返回封装此元素的最里层元素。
* 如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素;
* 如果此元素是顶层类型,则返回它的包;
* 如果此元素是一个包,则返回 null;
* 如果此元素是一个泛型参数,则返回 null.
*/
Element getEnclosingElement();
/**
* 返回此元素直接封装的子元素
*/
List<? extends Element> getEnclosedElements();
/**
* 返回直接存在于此元素上的注解
* 要获得继承的注解,可使用 getAllAnnotationMirrors
*/
List<? extends AnnotationMirror> getAnnotationMirrors();
/**
* 返回此元素上存在的指定类型的注解
*/
<A extends Annotation> A getAnnotation(Class<A> var1);
还有四个帮助类也是需要我们了解的:
参考
springboot项目打包jar,编写bat启动jar脚本,并解决bat控制台中文乱码问题
Hawk-and-ChickenTime Limit: 6000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2409 Accepted Submission(s): 712Problem DescriptionKids in kinder
就算说学Java的都是傻子,那也是拿着高薪,傲娇地鄙视着那些啥也不学或者没有深入的了解java的人。自己的问题,永远不要推给行业,再不好的行业,也会有牛的人,那个人为什么不是你呢?为何说学Java的都是傻子?第一点,问题的出处说出这个问题的人应该是一个刚入门的新手,估计是刚看完java相应的知识不久,我这里想说的是java只是一门语言,是一门基础性的语言,重点在基础两个字,现在来说应用的相对较广,知识点就那么多,但在应用上就会变得很多,有很多东西可能你还没学到,算法?框架?就像是高数,知识点就那.
[緣起]上次和 JBoss Ben Wang 閒聊時, 因為不知道 JBoss Rule Engine - Drools 而被嘲笑了一下, 可能從未實際接觸過 Rule Engine 的領域, 所以就只能馬上查閱資料惡補...[RuleEngine]當今技術前端應用應該就屬 ajax 最火紅, 加上 MVC Framework 的整合, 應該就可以快速處理許多系統.而後端技術應用就屬 Hibe
使用Mybatis中使用Mapper动态代理方式测试报以下错误:org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException: Error instantiating class ...
#1.输入数据# genome:# genome.fasta## rnaseq data:# brevis.rna.1.fastq.gz# brevis.rna.2.fastq.gz## homolog.fasta#2.bam文件准备hisat2-build -p 20 genome.fasta brevishisat2 -p ...
延迟函数调用(deferred function call)是golang中很有特点的一个功能,通过defer修饰的函数调用会在函数退出的时候才被真正调用,它可以用来进行资源释放等收尾工作。一个普通的函数调用被defer关键字修饰以后,就构成了一个延迟函数调用,和协程调用类似,被延迟的函数调用的所有返回值都会全部被舍弃。延迟函数调用的用法首先我们看看延迟函数调用的用法,它的用法其实很简单!...
点击文件–新建连接弹出如下图–选择IBM DB2 LUW然后点选择你自己要链接的服务器,双击即可输入用户名密码即可连接
之前使用 requests 模块的时候,是直接 requests.get() 或者 requests.post() 发送GET请求或POST请求;当然也是可以带上 cookies 和 headers 的,但这都是一次性请求,你这次带着cookies信息,后面的请求还得带。这时候 requests.session() 就派上用场了,它可以自动处理cookies,做状态保持。使用示例:# 先实例化一个对象session = requests.session()# 后面用法和直接使用request
有时候,我们在写C时,执行会遇到控制台窗口一闪而过的情况。首先,我们来看一段代码:#include&lt;stdio.h&gt;void main(){ printf("--------------hello world!---------");}这段代码运行就会出现上述情况。如何解决呢?解决办法:法一、getchar();在主函数的最后加上getchar()法二、system("pau...
但也不要过于担心 python 的速度,这并不一定是一个非此即彼的命题。经过适当优化,Python 应用程序可以以惊人的速度运行 —— 也许还不能达到 Java 或 C 语言的速度,但是对于 Web 应用程序、数据分析、管理和自动化工具以及大多数其他用途来说,速度已经足够快了。快到你可能会忘记了你是在用应用程序性能换取开发人员的生产力。优化 Python 性能不能单从一个角度上看。而是应用所有可用...
最近看到这个东西,一头雾水。看了一些资料了解了Webpack概念,大体是webpack 是一个模块绑定器,主要目的是在浏览器上绑定 JavaScript 文件。看到题叶写的一篇介绍,写的很好,转载连接http://segmentfault.com/a/1190000002551952转载于:https://www.cnblogs.com/kuailingmin/p/4420427.h...