这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战
前言
在上一篇中,就已经开始了对Gradle实战进行演练。在这一篇中,将会实现ButterKnife
里面的BindView
来讲解Gradle在APT自动化代码处理的实战。
1. APT介绍
APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。
简单来说就是在编译期,通过注解生成.java文件。
那么注解处理器怎么使用?
1.1 如何使用注解处理器
- annotationProcessor是在依赖管理中通implementation一样重要的设置
- 注解处理器主要用来在编译时期,对指定的注解类进行扫描并进行相关的处理
- 在dependencies{}中通过annotationProcessor添加进来的library,是不会打包进文件的,而是在编译时期,运行其中的指定的Processor类,这些Processor都继承了AbstractProcessor,并实现其中的process()方法来进行处理注解
2. APT配置
这里提到通过annotationProcessor
添加进来的library
,那么就创建对应的library
,并且用annotationProcessor
引入进来,因为它是不会打包进入文件的,所以创建java-library。
如图所示
在项目工程创建了Java-Library
工程,进入项目bulid.gradle
,通过annotationProcessor
将该Java-Library
工程引入进来
项目bulid.gradle
android{
...略
dependencies {
...略
annotationProcessor project(':compiler')
...略
}
...略
}
刚刚说到这个注解处理器只是用来处理注解的,而且这个处理器是不会打包进入APK的,但是我们在使用APT自动化处理代码的时候,又用到对应的注解。也就是说,对应使用的注解肯定不在这个注解处理器里面。所以在这要单独定义一个存放注解的library。
对应的BindView注解
@Retention(RetentionPolicy.RUNTIME)//运行时
@Target({ElementType.FIELD})//修饰属性
public @interface BindView {
int value();
}
对应注解处理器的build.gradle
plugins {
id 'java-library'
id 'kotlin'
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
//自动生成代码
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
//这里会用到注解,所以依赖注解对应的library
implementation project(':annotation2')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.squareup:javapoet:1.13.0'
}
最新项目build.gradle
android{
...略
dependencies {
...略
implementation project(':annotation2')
annotationProcessor project(':compiler')
...略
}
...略
}
到这里准备Gradle相关的配置工作已经完成了,这里意思就是compiler
这里面的代码(注解处理器)不会打包进入APK,而annotation2
这里面的注解会打包进入APK。
接下来就是实现APT了:(后面与Gradle知识点没关系了)
3. APT实现
刚刚说到:这些Processor都继承了AbstractProcessor。那么就按照上面所说的来。
ButterKnifeProcessor.java
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private Messager mMessager;
private Elements mElementUtils;
private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();
// 去做初始化操纵
// 获取一些工具类的实例
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mProxyMap.clear();
//得到所有的注解
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
mMessager.printMessage(Diagnostic.Kind.NOTE, "fullClassName " + fullClassName);
//elements的信息保存到mProxyMap中
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
//避免重复创建
proxy = new ClassCreatorProxy(mElementUtils, classElement);
//fullClassName 表示对应 class类名
mProxyMap.put(fullClassName, proxy);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int id = bindAnnotation.value();
//拿到注解标识控件ID,将对应控件ID以及Element以键值对存储在Map中
proxy.putElement(id, variableElement);
}
//通过javapoet生成
for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
mMessager.printMessage(Diagnostic.Kind.NOTE, "getPackageName " + proxyInfo.getPackageName().toLowerCase());
mMessager.printMessage(Diagnostic.Kind.NOTE, "generateJavaCode " + proxyInfo.generateJavaCode());
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode()).build();
try {
// 生成文件
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}
刚刚说到这个注解处理器将会自动运行其中的指定的Processor类里面的process()方法。
这方法里面就是获取对应的注解,拿到对应注解所在的包名以及类名,然后通过 proxyInfo.generateJavaCode()
创建对应的逻辑代码,最后通过javaFile.writeTo
生成对应的MainActivity_ViewBinding
文件。
注意:这里一定要用@AutoService(Processor.class)
注解标识该类
辅助类:ClassCreatorProxy.java
public class ClassCreatorProxy {
private String mBindingClassName;
private String mPackageName;
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
this.mTypeElement = classElement;
PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
String packageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mPackageName = packageName;
this.mBindingClassName = className + "_ViewBinding";
}
public void putElement(int id, VariableElement element) {
mVariableElementMap.put(id, element);
}
/**
* 创建Java代码
* javapoet
*
* @return
*/
public TypeSpec generateJavaCode() {
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethods2())
.build();
return bindingClass;
}
/**
* 加入Method
* javapoet
*/
private MethodSpec generateMethods2() {
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
}
return methodBuilder.build();
}
public String getPackageName() {
return mPackageName;
}
}
到这里注解处理器已经写好了,现在我们尝试在对应的Activity
使用看看效果:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text_content)
TextView textContent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//TestUtil.hello() 这是aar里面方法
textContent.setText(TestUtil.hello());
}
}
运行看看效果:
如图所示
APT已经成功的帮我们生成了对应的代码,但是呢在setText的时候,报了个空指针异常。
仔细想下,虽然APT吧代码给我们生成好了,但是好像并没有调用对应的bind方法的嘛。
要不手动在逻辑代码通过MainActivity_ViewBinding
来调用bind
方法?
但APT生成的代码并不会打包进入文件的嘛,在代码里强制使用MainActivity_ViewBinding
直接提醒找不到该类,所以这肯定是不行的!那该如何获取MainActivity_ViewBinding
里面的bind
方法呢?
既然直接用提醒不存在,那么用动态代理试试看哇!
进入昨天生成aar对应的library里创建BindViewTools
public class BindViewTools {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
这个没啥好说的,就是通过动态代理获取对应创建好的类以及对应方法,然后再通过method.invoke
调用对应的方法来实现调用bind
方法的作用。
关于动态代理的知识点可以看下这篇文章,我在这里详细讲解了对应的知识点。
这里修改了aar对应的library里面的代码,那么昨天的引用不能用了,重新按照上一篇的方式重新打包aar发布到maven里最后再重新依赖。
最后的activity代码:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.text_content)
TextView textContent;
@Override
protected void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindViewTools.bind(this);
textContent.setText( TestUtil.hello());
}
}
在这里调用了刚刚我们动态代理的方法,现在运行看看效果:
完美运行!
结束语
到这里相信你对APT以及Gradle有了更深刻的认识。在下一篇中,将会开启对Gradle在组件化实战操作。