学习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>