前言
Butterknife的代码到目前为止还没有仔细去看,这里也是自己在网上找的一个资料,主要是针对注解学习理解,但是发现这个学习资料估计是在Butterknife里面扣的,因为如果单单实现一个ViewBinder绑定注解,没必要那样写,代码高逼格了,既然看了这个代码,我就又实现到扩展循序渐进搞一遍,也来个可扩展的高逼格代码。
所以,有些东西没必要上路就想太多(兼容这,兼容那),先实现,在想着扩展。
参考文献:Android 注解使用之通过 annotationProcessor 注解生成代码实现自己的 ButterKnife 框架
正文
先看下目錄
以上分为4各模块:
1.app:宿主,测试注解,
2.annotation:存放自定义注解,
3.api:(类似“用于申明UI注解框架的api”网上的说法,不敢苟同,我自己理解下:)对自定义注解一种辅助操作模块,注解实现注入(api模块核心,不理解没关系,往下看你会深入理解的)。例如activity和Fragment,View使用方法有区别,在这个模块中处理,又例如资源管理:绑定和清除(可通过下面代码自己理解下)
高质量代码都体现在api模块下,其他的都是很难动的代码
4.compiler:用于在编译期间通过反射机制自动生成代码
额?有人问?为啥不可以使用同一个模块一次性完成!!!
- compiler里面的代码只是在编译生成代码是有效,分开的这个compiler模块是不会被打包进入我们的apk的,从而减少apk体积;
- 非常清晰明了,annotation就用于自定义注解,其他来了都不行;api用于辅佐使用注解
- 据说butterknife就这么分的,还听说这种分发就是它传出来的,有些期待对它的学习了
总结:没啥好纠结的,这就好比你媳妇让你跪搓衣板,你偏偏犟“我就不,我就要跪键盘”一个道理
实现ViewBinder Demo V0.9版
先能实现就行,代码能不能直视,关上灯都一样!!!
1.)annotation 声明注解框架中要使用的注解
这里我声明了一个BindView注解,声明周期为RUNTIME,作用域为成员变量
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
因为这里仅仅想要实现绑定View控件,这里就自定义了一个BindView注解
注意啦:这里一定要写成RetentionPolicy.RUNTIME,而不是RetentionPolicy.CLASS,为啥?后面会介绍为啥不能用RetentionPolicy.CLASS
app.annotation module为java library,build.gradle配置如下
apply plugin: 'java'
sourceCompatibility =JavaVersion.VERSION_1_7
targetCompatibility =JavaVersion.VERSION_1_7
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
}
2.)api 一个帮助注解使用的类
package com.example.api;
import android.app.Activity;
import android.util.Log;
import android.view.View;
import com.example.annotation.BindView;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
/**
* Copyright (C), 2019-2020, 佛生
* FileName: ViewInject
* Author: 佛学徒
* Date: 2020/11/10 13:29
* Description:view绑定在activity上
* History:
*/
public class ViewBindWithActivity {
//运行时解析注解 BindView
public static void inject(Activity activity) {
//获取当前activity下的所有字段
Field[] fields = activity.getClass().getDeclaredFields();
//通过该方法设置所有的字段都可访问,否则即使是反射,也不能访问private修饰的字段
AccessibleObject.setAccessible(fields, true);
for (Field field : fields) {
//这里api需要依赖于annotation,注意!!!如果BindView采用RetentionPolicy.CLASS修饰这里返回的needInject是false,没错即使有BindView也是false,只有RetentionPolicy.RUNTIME修饰才表示在运行是BindView也有效
boolean needInject = field.isAnnotationPresent(BindView.class);
if (needInject) {
BindView anno = field.getAnnotation(BindView.class);
int id = anno.value();
if (id == -1) {
continue;
}
//以下相当于做了这个操作: textView = this.findViewById(R.id.tv_text);
View view = activity.findViewById(id);
Class fieldType = field.getType();
try {
//把View转换成field声明的类型
field.set(activity, fieldType.cast(view));
} catch (Exception e) {
Log.e(BindView.class.getSimpleName(), e.getMessage());
}
}
}
}
}
**为啥annotation 中BindView 只能使用RetentionPolicy.RUNTIME?**因为我以上代码采用的是java的反射机制,运行时注解及有效,那么必须RetentionPolicy.RUNTIME修饰
//这里api需要依赖于annotation,注意!!!如果BindView采用RetentionPolicy.CLASS修饰这里返回的needInject是false,没错即使有BindView也是false,只有RetentionPolicy.RUNTIME修饰才表示在运行是BindView也有效
boolean needInject = field.isAnnotationPresent(BindView.class);
api引入的gradle文件
apply plugin: 'com.android.library'
...
dependencies {
...
implementation project(path: ':annotation')
}
3.)app宿主:用于测试注解
package com.example.viewbinderdemo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import com.example.annotation.BindView;
import com.example.api.ViewBindWithActivity;
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_text)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// textView = this.findViewById(R.id.tv_text);
ViewBindWithActivity.inject(this);
textView.setText("我是一个IT搬运工!!");
}
}
gradle配置如下:
apply plugin: 'com.android.application'
...
dependencies {
...
implementation project(path: ':annotation')
implementation project(path: ':api')
}
以上即一个简单的通过反射实现的注解,啥都没有,你这么在项目里面写会被人揍成猪头!!!
**总结:**这个是通过反射机制完成的注解,很多原始的框架,例如EventBus3.0前,Dagger1等都是通过反射实现的,但是后来都没有这么去实现,因为效率问题,打个比方(希望没人叫比方),一个activity业务很重代码量多,这个时候用反射机制就很不友好了(去一个个找字段,方法,类,在代码量超多情况下执行效率非常低下,这里也让我们明白一个道理,没有啥事一蹴而就的,都是因为突出了问题或者一个新技术,再实现架构的迭代,这就是架构师的意思所在),那咋办,凉…当然有更好的方式去实现了
实现ViewBinder Demo V1.0版
android在编译时通过反射生成相应的代码
1.)annotation模块外甥打灯笼,但:
这里可以采用RetentionPolicy.CLASS修饰了
2.)complier根据注解在编译期间自动生成java代码
该类中主要2个类:1.一个是自定义AbstractProcessor类,android studio在编译时会自动扫描该文件;2.自定义类,主要用于编译执行AbstractProcessor时,生成我们所需要的java类。
注意,这里如果没有正确引入gradle,是不会执行自定义AbstractProcessor类,也可以说,如果在build中没有生成对应的类,那么除非build报错,否则必然是graldle引入问题:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
//用于自动为 JAVA Processor 生成 META-INF 信息。
implementation 'com.google.auto.service:auto-service:1.0-rc3'
annotationProcessor "com.google.auto.service:auto-service:1.0-rc3"
//快速生成.java文件的库
implementation 'com.squareup:javapoet:1.8.0'
implementation project(path: ':annotation')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
1.AnnotatedClass 类poet生成java代码
package com.example.compiler;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.util.HashMap;
import java.util.Map;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
/**
* Copyright (C), 2019-2020, 佛生
* FileName: AnnotatedClass
* Author: 佛学徒
* Date: 2020/11/10 16:48
* Description:生成代码的工具类
* History:
*/
class AnnotatedClass {
private String mBindingClassName;//生成新java类的名称
private String mPackageName;//生成新java类包名
private TypeElement mTypeElement;
private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
public AnnotatedClass(TypeElement classElement, Elements elementsUtils) {
this.mTypeElement = classElement;
PackageElement packageElement = elementsUtils.getPackageOf(mTypeElement);
this.mPackageName = packageElement.getQualifiedName().toString();
String className = mTypeElement.getSimpleName().toString();
this.mBindingClassName = className + "$$ViewBinder";
}
public void putElement(int id, VariableElement element) {
mVariableElementMap.put(id, element);
}
JavaFile generateFile() {
//生成java文件
JavaFile javaFile = JavaFile.builder(mPackageName, generateJavaCode()).build();
return javaFile;
}
private TypeSpec generateJavaCode() {
//生成java代码
TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethod())
.build();
return bindingClass;
}
private MethodSpec generateMethod() {
//生成方法,并且通过poet写入具体实现方法的代码
ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
//加入一个bind方法,public类型,传入参数为 host(当前注解所在的类对象),void类型所以没有返回类型
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(host, "host");
//bind方法的具体实现
for (int id : mVariableElementMap.keySet()) {
VariableElement element = mVariableElementMap.get(id);
String name = element.getSimpleName().toString();
String type = element.asType().toString();
//相当于加了host.view = (view类型)host.findViewById(id)
methodBuilder.addCode("host." + name + " = " + "(" + type + ")host.findViewById( " + id + " );\n");
}
return methodBuilder.build();
}
}
2.自定义AbstractProcessor类
package com.example.compiler;
import com.example.annotation.BindView;
import com.google.auto.service.AutoService;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Filer mFiler;//文件相关的辅助类
private Messager mMessager;//日志相关的辅助类
private Elements mElementUtils;//元素相关类
private Map<String, AnnotatedClass> mAnnotatedClassMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mElementUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
mAnnotatedClassMap.clear();
//获得被BindView注解标记的element
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//对不同的activity进行分类
for (Element element: elements){
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullClassName);
if(annotatedClass == null){
annotatedClass = new AnnotatedClass(classElement,mElementUtils);
mAnnotatedClassMap