ButterKnife我们都用过,很好用,今天我们自己实现一个ButterKnife。
博客全程参考
一,注解处理器简介
注解处理器是(Annotation Processor
)是Javac
的一个工具,用来在编译时扫描和编译和处理注解(Annotation
)。你可以自己定义注解和注解处理器去搞一些事情。一个注解处理器它以Java代码或者(编译过的字节码)作为输入,生成文件(通常是java
文件)。这些生成的java
文件不能修改,并且会同其手动编写的java代码一样会被javac
编译。大概的过程就是把标记了注解的类,变量等作为输入内容,经过注解处理器处理,生成想要生成的java代码。
注解处理器继承自AbstractProcessor
。固定写法如下:
public class BindViewProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return true;
}
}
主要函数 | 简介 |
---|---|
init(ProcessingEnvironment processingEnv) | 参数ProcessingEnvironment 提供了Element,Filer,Messager等工具 |
getSupportedAnnotationTypes() | 指定支持的注解 |
getSupportedSourceVersion | 指定支持Java版本 |
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) | 是最主要的函数,在这里扫描和处理你的注解并生成Java代码 |
注解处理器是需要注册的,为了使用方便,我们使用Google提供的开源库进行处理:
implementation 'com.google.auto.service:auto-service:1.0-rc2'
@AutoService(Process.class)
public class BindViewProcessor extends AbstractProcessor {
...
}
二,项目结构
app是android application
apt_annotation是JavaLibrary 存放自定义注解
apt_processor是JavaLibrary 存放注解处理器
app 依赖apt_annotation和apt_processor
apt_processor依赖apt_annotation
其中apt_processor 的 gradle如下
javapoet是用来生成Java代码的,我在前面的博客中有提到过。
auto-service则是注册注解处理器的。
三,自定义注解
在apt_annotation module中新建BindView.java
文件,生成我们的注解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
四,注解处理器的使用
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
}
//指定要处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> typeElements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> elementMap = new HashMap<>();
for (Element element : typeElements) {
VariableElement variableElement = (VariableElement) element;
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> map = elementMap.get(typeElement);
if (map == null) {
map = new HashMap<>();
elementMap.put(typeElement, map);
}
BindView bindView = variableElement.getAnnotation(BindView.class);
int value = bindView.value();
map.put(value, variableElement);
}
for (TypeElement typeElement : elementMap.keySet()) {
Map<Integer, VariableElement> map = elementMap.get(typeElement);
String packageName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
JavaFile javaFile = JavaFile.builder(packageName, generateClass(typeElement, map)).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
//生成一个类
private TypeSpec generateClass(TypeElement typeElement, Map<Integer, VariableElement> map) {
TypeSpec typeSpec = TypeSpec.classBuilder(typeElement.getSimpleName().toString() + "ViewBinding")
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethod(typeElement, map))
.build();
return typeSpec;
}
//生成bind()方法
private MethodSpec generateMethod(TypeElement typeElement, Map<Integer, VariableElement> map) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
String parameter = "_" + toLowFirstChar(className.simpleName());
MethodSpec.Builder builder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int valueId : map.keySet()) {
VariableElement element = map.get(valueId);
String text = "{0}.{1} = ({2})({3}.findViewById({4}))"; //_mainActivity.btn_remove = (android.widget.Button) (_mainActivity.findViewById(2131165219));
String name = element.getSimpleName().toString();
String type = element.asType().toString();
builder.addStatement(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(valueId)));
}
return builder.build();
}
//将首字母转变为小写
private String toLowFirstChar(String string) {
if (string == null || string.length() == 0) {
return "";
}
if (Character.isLowerCase(string.charAt(0))) {
return string;
}
return String.valueOf(Character.toLowerCase(string.charAt(0))) + string.substring(1);
}
ReBuild项目之后可以在generatedJava文件中看到自动生成的文件。
这个就是我们自动生成的java文件。
findViewById
的绑定文件已经生成了,接下来只需要通过反射调用这个bind()
方法就可以了。
先生成一个MyButterKnife.java
文件·,内容如下:
public class MyButterknife {
public static void bind(Activity activity) {
Class clazz = activity.getClass(); //MainActivity
try {
Class bindViewClass = Class.forName(clazz.getName() + "ViewBinding"); //MainActivityViewBinding
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后在MainActivity
里面bind
一下,使用和ButterKnife
一样,这样就完成了。
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_hint)
TextView hintTv;
@BindView(R.id.btn_content)
Button contentBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyButterknife.bind(this);
hintTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "You click hinTv", Toast.LENGTH_SHORT).show();
}
});
contentBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "You click contentBtn", Toast.LENGTH_SHORT).show();
}
});
}
}
五,一点问题
注解处理器是需要注册的,我们使用的是@AutoService(Processor.class)
注册的,如果把Processor
单词拼错了,会导致不能在generatedJava
下生成文件。