Android—Gradle教程(七)

前言

在上一篇中,就已经开始了对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在组件化实战操作。

Guess you like

Origin blog.csdn.net/qq_30382601/article/details/121168184