学习APT以及简单应用( 注解实现 findViewById)

本文的参考资料、原代码都可以在享学课堂中获取
/** * 创建Java Library Module 名称为 APTModule *1、创建自定义注解 MQBindView *2、创建注解处理器 MQProcessor *3、定义一个使用入口,给使用者调用 MQButterKnife */ //标记注解可以用在什么地方 (ElementType.FIELD 属性字段上) @Target(ElementType.FIELD) //标记注解的保存策略 (RetentionPolicy.SOURCE 源文件,编译时丢弃,class字节码文件中不包含) @Retention(RetentionPolicy.SOURCE) public @interface MQBindView { int value(); }
/**
 * 配置注解处理器 自定义处理器代码编写好之后,还有一个非常重要的步骤,就是配置注解处理器
 * 在自定义注解处理器所在的 Module,APTModule 的src/main目录下新建一个 resources/METAINF/services 三级目录,
 * 然后在services目录下新建一个名字为 javax.annotation.processing.Processor 的文件.
 * 文件里面的内容,写上自定义处理器的全类名即可  com.example.packageName.MQProcessor
 */

public class MQProcessor extends AbstractProcessor {

    private static final String TAG = "MQProcessor";
    Filer mFiler;
    ProcessingEnvironment mProcessingEnv;
    Messager mMessage;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mMessage = processingEnv.getMessager();
        mFiler = processingEnv.getFiler();
        mProcessingEnv = processingEnv;
        mMessage.printMessage(Diagnostic.Kind.NOTE, "----------->init");
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        mMessage.printMessage(Diagnostic.Kind.NOTE, "----------->process");

        /**
         * Element代表程序的一个元素,这个元素可以是:包,类/接口,属性变量,方法/方法形参,泛型参 数,
         * Element是java-apt( 编译时注解处理器)技术的基础,在编译期间可以获取元素的各类信息,结合 APT技术动态生成代码实现相应的逻辑处理;
         */
        //我们的源代码中使用了@MBQinfView 的所有元素
        //这里,当我们拿到使用了 @MQBindView 这个注解的 Element(mTextView) ,然后通过 getEnclosingElement() 方法就能获取到父Element,这里父Element就是MainActivity
        //process 方法中,有一个参数 RoundEnvironment ,通过这个参数可以获取包含特定注解的被注解元素
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MQBindView.class);

        //一个Activity会有声明了多个@MBQinfView 的所有元素
        //定义一个Map集合以Activity类名为键,这个Activity使用@MQBindVie注解的元素列表为键值
        Map<String, List<VariableElement>> map = new HashMap<>();

        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement) element;

            //Activity的名字
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();

            List<VariableElement> variableElements;

            //把variableElement加入对应的Activity中
            if (map.get(activityName) != null) {
                variableElements = map.get(activityName);
                variableElements.add(variableElement);
            } else {
                variableElements = new ArrayList<>();
                variableElements.add(variableElement);
                map.put(activityName, variableElements);
            }
        }


        Writer writer = null;

        //给每一个使用了@MQVindView注解的类(Activity/Fragment等)都生成对应的Java源文件
        for (Map.Entry<String, List<VariableElement>> stringListEntry : map.entrySet()) {
            List<VariableElement> variableElementList = stringListEntry.getValue();
            TypeElement enclosingTypeElement = (TypeElement) variableElementList.get(0).getEnclosingElement();

            //init方法中有一个 ProcessingEnvironment 参数,通过这个参数可以获取元素所在的包
            String packageName = mProcessingEnv.getElementUtils().getPackageOf(enclosingTypeElement).toString();
            String activityName = variableElementList.get(0).getEnclosingElement().getSimpleName().toString();
            try {
                mMessage.printMessage(Diagnostic.Kind.NOTE, "----------- >process" + mFiler);
                /** * Java源文件里的内容为定义一个类,类名就为使用了@BindView的注解的 Activity的类名加_ViewBinding *
                 * 然后构造方法有一个Activity类型的target参数,构造方法里面给这个Activity中使用了@BindView注解 的View成员进行findViewById操作
                 *
                 * For example:这个Activity 只声明了一个  TextView
                 *
                 * package com.example.packageName;
                 * import android.view.View;
                 * public class MainActivity_ViewBinding {
                 *     public MainActivity_ViewBinding(MainActivity target) {
                 *         View decorView = target.getWindow().getDecorView();
                 *         target.textView = decorView.findViewById(2131231156);
                 *     }
                 * }
                 * */

                //Filer是文件生成器,可以用来生成Java源文件等
                JavaFileObject sourceFile = mFiler.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                writer = sourceFile.openWriter();
                writer.write("package " + packageName + ";\n");
                writer.write("import android.view.View;\n");
                writer.write("public class " + activityName + "_ViewBinding {\n");
                writer.write("public " + activityName + "_ViewBinding (" + activityName + " target){\n");
                writer.write("View decorView = target.getWindow().getDecorView();\n");

                //一个Activity会有声明了多个@MBQinfView 的所有元素
                for (VariableElement variableElement : variableElementList) {
                    String variableName = variableElement.getSimpleName().toString();
                    int value = variableElement.getAnnotation(MQBindView.class).value();
                    //需要Activity 成员变量 View声明为public 不然会报错
                    writer.write("target." + variableName + "=decorView.findViewById(" + value + ");\n");
                }
                writer.write("}\n");
                writer.write("}\n");
                try {
                    mMessage.printMessage(Diagnostic.Kind.NOTE, "----------- >process:" + writer);
                    //最终Writer这个写对象,要关闭,否则内容是不会写进创建的Java源文件中的
                    writer.close();
                } catch (IOException exception) {
                    exception.printStackTrace();
                }
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        }
        mMessage.printMessage(Diagnostic.Kind.NOTE, "----------->process: end");
        return false;
    }

    @Override
    public Set<String> getSupportedOptions() {
        return super.getSupportedOptions();
    }


    //重写getSupportedAnnotationTypes方法,添加支持处理的注解类型
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(MQBindView.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

}

定义一个使用入口,给使用者调用 我们这里定义了一个 MQButterKnife 类,提供了一个静态方法 bind(Activity), 
bind 方法里面就是 通过反射创建Activity对应生成的 Activity_ViewBinding
对象触发 findViewById 操作
public class MQButterKnife { private static final String TAG = "MQButterKnife"; public static void bind(Activity activity) { String viewBindingClassName = activity.getClass().getCanonicalName() + "_ViewBinding"; Log.e(TAG, "bind:" + viewBindingClassName); try { Class<?> aClass = Class.forName(viewBindingClassName); Log.e(TAG, "bind: " + aClass); Constructor<?> constructor = aClass.getConstructor(activity.getClass()); Object o = constructor.newInstance(activity); Log.e(TAG, "bind: " + o); }catch(Exception e){ e.printStackTrace(); } } }

 

我们的 app 需要导入依赖
dependencies {
implementation project(path: ':APTModule') annotationProcessor project(':APTModule') }
使用方式
public class MainActivity extends AppCompatActivity {
private ConstraintLayout constraintLayout;
@MQBindView(R.id.text_bind)
TextView textView;

@MQBindView(R.id.button)
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MQButterKnife.bind (this);
textView.setText ("testhhhhh");
button.setText("xaingbudaoba");
}
}

xml文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/container"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:id="@+id/text_bind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="550px"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />




<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
tools:layout_editor_absoluteX="246dp"
tools:layout_editor_absoluteY="122dp"
tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>
 

 

posted @ 2023-02-18 12:11  炸憨啪  阅读(42)  评论(0编辑  收藏  举报