【Android】APT

1,概述

平时使用的一些框架如dagger2、butterknife等,通过注解动态生成代码的技术,就是apt,本文将写一个简单demo,来演示apt基本使用;

2,实例

(1)创建java-libray

在android-studio4.0版本,创建一个java-libray module; 

创建两个,分别命名为apt-annotation、apt-processor

再创建一个android module,取名apt-library

项目结构如下,

 (2)在apt-annotation下创建注解,这儿选择class,即编译时;

@Retention(RetentionPolicy.CLASS)
@Target(value = {ElementType.FIELD})
public @interface DemoFindViewById {
    int value();
}

(3)在apt-processor模块导入依赖,

dependencies {
    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    implementation project(':apt-annotation')
}

(4)创建DemoProcessor类,在process中写入即将生成的java代码

@AutoService(Processor.class)
public class DemoProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }
}

(5)完整DemoProcessor如下,

//此注解,表示会执行此类
@AutoService(Processor.class)
public class DemoProcessor extends AbstractProcessor {

    private ProcessingEnvironment mProcessingEnvironment;

    private Map<String, CreateClassProxy> mCache = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.mProcessingEnvironment = processingEnv;
    }

     @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 写入支持编译的注解
        Set<String> supportTypes = new HashSet<>();
        supportTypes.add(DemoFindViewById.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        // 兼容到JDK最新版本
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //动态生成java代码
        mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE, "processing...");
        //获取所有@Demo的Element
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(DemoFindViewById.class);
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            String className = typeElement.getQualifiedName().toString();

            CreateClassProxy proxy = mCache.get(className);
            if (proxy == null) {
                proxy = new CreateClassProxy(mProcessingEnvironment.getElementUtils(), typeElement);
                mCache.put(className, proxy);
            }
            DemoFindViewById findViewById = variableElement.getAnnotation(DemoFindViewById.class);
            int id = findViewById.value();
            proxy.putElement(id,variableElement);
        }

        //创建java文件
        for (CreateClassProxy proxy : mCache.values()) {
            mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE,"create code :" + proxy.getClassName());
            try {
                JavaFileObject javaFileObject = super.processingEnv.getFiler().createSourceFile(proxy.getClassName(), proxy.getTypeElement());
                Writer writer = javaFileObject.openWriter();
                writer.write(proxy.generateJavaCode().toString());
                writer.flush();
                writer.close();
            } catch (IOException e) {
                mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE,"fail code :" + proxy.getClassName());
            }
            mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.NOTE,"finish code :" + proxy.getClassName());
        }
        return true;
    }
}

(6)生成代码proxy类,

CreateClassProxy,采用JavaPoet辅助生成代码;

public class CreateClassProxy {

    private String mClassName;
    private String mPackageName;
    private TypeElement mTypeElement;
    private final Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public CreateClassProxy(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.mClassName = className + "_DemoBinding";
    }

    public void putElement(int id, VariableElement element) {
        mVariableElementMap.put(id, element);
    }

    public String getClassName() {
        return mClassName;
    }

    public String getPackageName() {
        return mPackageName;
    }

    public TypeElement getTypeElement() {
        return mTypeElement;
    }

    public TypeSpec generateJavaCode(){
        TypeSpec typeSpec = TypeSpec.classBuilder(mClassName)
                .addMethod(generateJavaMethod())
                .build();

        StringBuilder builder = new StringBuilder();
        // 不知为何,生成代码少package行,导致找不到,这儿暂时这么写
        return builder.append("package ")
                .append(mPackageName)
                .append(";\n")
                .append(typeSpec.toString);
    }

    public MethodSpec generateJavaMethod(){
        //类名
        ClassName className = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());

        MethodSpec.Builder builder = MethodSpec.methodBuilder("inject")//方法名
                .addModifiers(Modifier.PUBLIC)//限定符
                .returns(void.class)//返回值
                .addParameter(className, "className");//参数类型和参数名

        //迭代,加入代码行
        for (Integer id : mVariableElementMap.keySet()) {
            VariableElement element = mVariableElementMap.get(id);
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            //代码块
            builder.addCode("className." + name + " = " + "(" + type + ")(((android.app.Activity)className).findViewById( " + id + "));");
        }

        return builder.build();
    }
}

(7)在apt-library写工具类,即通过反射调用已经生成的代码,

public class AptDemoInjector {

    public static void inject(Activity activity){
        try {
            Class<?> target = Class.forName(activity.getClass().getName() + "_DemoBinding");
            Method inject = target.getMethod("inject", activity.getClass());
            inject.invoke(target.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();
        }
    }
}

(8)在app模块写入测试代码,

public class AptDemoActivity extends AppCompatActivity {

    private static final String TAG = "AptDemoActivity";

    @DemoFindViewById(R.id.demo_id)
    public TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_apt_demo);

        // 注入,自动生成代码
        AptDemoInjector.inject(this);

        Log.d(TAG, "inject: TextView ->" + textView);
    }
}

(9)点击rebuild,自动生成代码如下

import android.widget.TextView;
import com.zjw.demoapp.apt.AptDemoActivity;

class AptDemoActivity_DemoBinding {
    AptDemoActivity_DemoBinding() {
    }

    public void inject(AptDemoActivity className) {
        className.textView = (TextView)className.findViewById(2131230864);
    }
}

运行app,打印日志,看到已经成功注入

2021-12-27 09:51:35.106 25978-25978/com.zjw.framework_self D/MainActivity: inject: TextView ->com.google.android.material.textview.MaterialTextView{81f68cb V.ED..... ......ID 0,0-0,0 #7f08008e app:id/demo_id}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值