注解的使用

羊小咩2022年10月4日
大约 5 分钟

注解的使用

注解+APT(注解处理器) (ButterKnife实现原理)

APT实现原理

在java编译阶段(java->class),通过注解处理器(APT)解析注解,生成新的代码,把新的代码编译成字节码。

APT使用

  1. 引入annotationProcessor,在编译的时候使用注解处理器,需要用@AutoService注解标识
dependencies {
    //......
    implementation project(path: ':annotations')
    annotationProcessor  project(path: ':annotation_compiler')
}
  1. 定义注解BindView,用于使用注解的地方
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value();
}
  1. 定义接口,用于绑定Activity
public interface IBind<T> {
    void bind(T target);
}
  1. 创建绑定Activity工具类,运行的时候动态创建对应的类并执行相应的代码
public class MyButterKnife {
    public static void bind(Activity activity){
        String name=activity.getClass().getName()+"_ViewBinding";
        try{
            Class<?> aClass=Class.forName(name);
            IBind iBinder=(IBind)aClass.newInstance();
            iBinder.bind(activity);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}
  1. 创建注解处理器,继承AbstractProcessor,并在process的回调使用Filer把代码的写入到指定的文件。
@AutoService(Processor.class)
public class AnnotationsCompiler extends AbstractProcessor {
    //1.支持的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //2.能用来处理哪些注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    //3.定义一个用来生成APT目录下面的文件的对象
    Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnvironment.getFiler();
    }


    /**
     * 所有的坏事都在这个方法中实现
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        // 注意此方法会被回调3次,需要判断set不为空再执行
        if (set == null || set.size() == 0) {
            return false;
        }
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,"jett---------------"+set);
        //获取APP中所有用到了BindView注解的对象
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);

        //TypeElement//类
        //ExecutableElement//方法
        //VariableElement//属性
        //开始对elementsAnnotatedWith进行分类
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement) element;
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            Class aClass = variableElement.getEnclosingElement().getClass();
            List<VariableElement> variableElements = map.get(activityName);
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(activityName, variableElements);
            }
            variableElements.add(variableElement);
        }

        //需要生成文件内容
        /*
         * package com.example.butterknife_framework_demo;
         *
         * import com.example.butterknife_framework_demo.IBind;
         *
         * public class MainActivity_ViewBinding implements IBind<com.example.butterknife_framework_demo.MainActivity> {
         *     @Override
         *     public void bind(com.example.butterknife_framework_demo.MainActivity target) {
         *         target.textView = (android.widget.TextView) target.findViewById(2131165359);
         *
         *     }
         * }
         */
        if (map.size() > 0) {
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                List<VariableElement> variableElements = map.get(activityName);
                //得到包名
                TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                try {
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                    writer = sourceFile.openWriter();
                    //        package com.example.dn_butterknife;
                    writer.write("package " + packageName + ";\n");
                    //        import com.example.dn_butterknife.IBind;
                    writer.write("import " + packageName + ".IBind;\n");
                    //        public class MainActivity_ViewBinding implements IBind<
                    //        com.example.dn_butterknife.MainActivity>{
                    writer.write("public class " + activityName + "_ViewBinding implements IBind<" +
                            packageName + "." + activityName + ">{\n");
                    //            public void bind(com.example.dn_butterknife.MainActivity target) {
                    writer.write(" @Override\n" +
                            " public void bind(" + packageName + "." + activityName + " target){");
                    //target.tvText=(android.widget.TextView)target.findViewById(2131165325);
                    for (VariableElement variableElement : variableElements) {
                        //得到名字
                        String variableName = variableElement.getSimpleName().toString();
                        //得到ID
                        int id = variableElement.getAnnotation(BindView.class).value();
                        //得到类型
                        TypeMirror typeMirror = variableElement.asType();
                        writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
                    }

                    writer.write("\n}}");

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return false;
    }
}

注解+反射+动态代理

实现运行时通过反射解析注解,通过动态代理代理注解的接口,回调到注解的地方。

以点击事件做为一个例子:

  1. 定义运行时注解
/**
 * 提供其他注解使用,例如:OnClick注解, OnLongClick注解
 */
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
    //需要代理的方法->setOnClickListener
    String function();
    //需要代理的接口类->View.OnClickListener()
    Class<?> interfaceClass();
    //需要代理的具体接口->onClick
    String callbackMethod();
}
  1. 定义OnClick注解和OnLongClick注解
// OnClick
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Event(function = "setOnClickListener"
        , interfaceClass = View.OnClickListener.class
        ,callbackMethod = "onClick")
public @interface OnClick {
    int[] value() default -1;
}

// OnLongClick
@Event(function = "setOnLongClickListener"
        , interfaceClass = View.OnLongClickListener.class
        ,callbackMethod = "onLongClick")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnLongClick {
    int[] value() default -1;
}
  1. 使用注解
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InjectUtils.inject(this);
    }
    
    @OnClick({R.id.btn1,R.id.btn2})
    public void click(View view){
        Toast.makeText(this, "按下1", Toast.LENGTH_SHORT).show();
    }

    @OnLongClick({R.id.btn2})
    public boolean longClick(View view){
        Toast.makeText(this, "按下2", Toast.LENGTH_SHORT).show();
        return false;
    }
}
  1. 实现InvocationHandler,代理需要回调的接口
/**
 * 这个类用来代理new View.OnClickListener()对象
 * 并执行这个对象身上的onClick方法
 */
public class ListenerInvocationHandler implements InvocationHandler {

    private Object activity;
    private Method activityMethod;

    public ListenerInvocationHandler(Object activity, Method activityMethod) {
        this.activity = activity;
        this.activityMethod = activityMethod;
    }

    /**
     * 就表示onClick的执行
     * 程序执行onClick方法,就会转到这里来
     * 因为框架中不直接执行onClick
     * 所以在框架中必然有个地方让invoke和onClick关联上
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在这里去调用被注解了的click();
        return activityMethod.invoke(activity,args);
    }
}
  1. 处理解析注解,动态代理接口,执行回调事件
public class InjectUtils {
    public static void inject(Object context) {
        injectClick(context);
    }

    private static void injectClick(Object context) {
        Class<?> clazz = context.getClass();
        Method[] methods = clazz.getDeclaredMethods();

        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                Class<?> annotationClass = annotation.annotationType();
                Event eventBase = annotationClass.getAnnotation(Event.class);
                //判断是不是事件处理程序  onClick  onLongClink
                if (eventBase == null) {
                    continue;
                }
                
                // 获取注解的Event注解的参数
                String function = eventBase.function();
                Class<?> interfaceClass = eventBase.interfaceClass();
                String callBackMethod = eventBase.callbackMethod();

                Method valueMethod;
                try {
                    valueMethod = annotationClass.getDeclaredMethod("value");
                    int[] viewId = (int[]) valueMethod.invoke(annotation);
                    if (viewId == null) {
                        return;
                    }
                    for (int id : viewId) {
                        //执行 findViewById 获取view对象
                        Method findViewById = clazz.getMethod("findViewById", int.class);
                        View view = (View) findViewById.invoke(context, id);
                        if (view == null) {
                            continue;
                        }
                        // context->activity   method->click
                        ListenerInvocationHandler listenerInvocationHandler = new ListenerInvocationHandler(context, method);
                        // new View.OnClickListener()
                        Object proxy = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, listenerInvocationHandler);

                        // 执行 view.setOnClickListener(new View.OnClickListener())
                        Method onClickMethod = view.getClass().getMethod(function, interfaceClass);
                        // 当点击按钮的时候,就会执行ListenerInvocationHandler的invoke方法,从而回调注解的地方
                        onClickMethod.invoke(view, proxy);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

总结

  1. 注解+APT的使用流程
    • 引入annotationProcessor
    • 定义注解
    • 创建注解处理器
    • 写入自定义的代码到自定义的文件
    • 使用注解
  2. ButterKnife的实现原理
在编译时找到项目中所有注解的元素→根据类名进行分类→
  
遍历对每个类创建对应的文件→在文件中根据注解使用Writer 我们的代码→

最后写入文件→在运行的时候,通过bind()接口传入activity→反射获取当前类对应的文件→

获取对应的构造器创建对象→执行响应的代码逻辑
  1. 使用APT的框架
    • ButterKnife
    • databinding
    • dagger2
    • hilt
    • arouter
  2. 注解+反射+动态代理