注解的使用
2022年10月4日
注解的使用
注解+APT(注解处理器) (ButterKnife实现原理)
APT实现原理
在java编译阶段(java->class),通过注解处理器(APT)解析注解,生成新的代码,把新的代码编译成字节码。
APT使用
- 引入
annotationProcessor,在编译的时候使用注解处理器,需要用@AutoService注解标识
dependencies {
//......
implementation project(path: ':annotations')
annotationProcessor project(path: ':annotation_compiler')
}
- 定义注解
BindView,用于使用注解的地方
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
- 定义接口,用于绑定Activity
public interface IBind<T> {
void bind(T target);
}
- 创建绑定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();
}
}
}
- 创建注解处理器,继承
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;
}
}
注解+反射+动态代理
实现运行时通过反射解析注解,通过动态代理代理注解的接口,回调到注解的地方。
以点击事件做为一个例子:
- 定义运行时注解
/**
* 提供其他注解使用,例如:OnClick注解, OnLongClick注解
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
//需要代理的方法->setOnClickListener
String function();
//需要代理的接口类->View.OnClickListener()
Class<?> interfaceClass();
//需要代理的具体接口->onClick
String callbackMethod();
}
- 定义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;
}
- 使用注解
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;
}
}
- 实现
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);
}
}
- 处理解析注解,动态代理接口,执行回调事件
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();
}
}
}
}
}
总结
- 注解+APT的使用流程
- 引入
annotationProcessor - 定义注解
- 创建注解处理器
- 写入自定义的代码到自定义的文件
- 使用注解
- 引入
- ButterKnife的实现原理
在编译时找到项目中所有注解的元素→根据类名进行分类→
遍历对每个类创建对应的文件→在文件中根据注解使用Writer 我们的代码→
最后写入文件→在运行的时候,通过bind()接口传入activity→反射获取当前类对应的文件→
获取对应的构造器创建对象→执行响应的代码逻辑
- 使用APT的框架
- ButterKnife
- databinding
- dagger2
- hilt
- arouter
- 注解+反射+动态代理