前言
ButterKnife
通过使用@BindView
注解就可以完成findViewById
工作,它的实现原理其实也很简单,通过APT(Annotation Processing Too,注解解析器)
技术,在编译期为我们生成了一个绑定类,而从完成了View的绑定。
// ButterKnife编译时生成的绑定类
package com.crystal.essayjoker.activity;
import android.view.View;
import android.widget.Button;
import androidx.annotation.UiThread;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import com.crystal.essayjoker.R;
import java.lang.IllegalStateException;
import java.lang.Override;
public final class CheckNetActivity_ViewBinding implements Unbinder {
private CheckNetActivity target;
@UiThread
public CheckNetActivity_ViewBinding(CheckNetActivity target) {
this(target, target.getWindow().getDecorView());
}
@UiThread
public CheckNetActivity_ViewBinding(CheckNetActivity target, View source) {
this.target = target;
target.btn2 = Utils.findRequiredViewAsType(source, R.id.btn2, "field 'btn2'", Button.class);
}
@Override
public void unbind() {
CheckNetActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.btn2 = null;
}
}
那我们是不是可以模仿ButterKnife
通过APT
去自己实现一套@BindView
呢?
具体实现
- 首先,模仿
ButterKnife
定义三个模块:butterknife-annotations
:Java模块,用于定义@BindView
注解;butterkinfe-compiler
:Java模块,用于解析@BindView
注解生成Activity_ViewBinding
类;butterknife
:Android模块,用于定义UnBinder
接口以及实例化Activity_ViewBinding
类;
他们的依赖关系如下:
- 在
butterknife-annotations
模块中,定义@BindView
注解`
@Retention(AnnotationRetention.BINARY) //保留到编译期
@Target(AnnotationTarget.FIELD) //作用于属性
annotation class BindView(val resId: Int)
- 在
butterknife
模块中,添加FindViewById
工具类、ButterKnife.bind()
工具类以及Unbinder
接口
Utils
类
object Utils {
@JvmStatic
fun <T : View> findViewById(target: Activity, viewId: Int): T {
return target.findViewById(viewId)
}
}
Unbinder
接口
public interface Unbinder {
void unbind();
Unbinder EMPTY = () -> {
};
}
ButterKnife
类
object ButterKnife {
@JvmStatic
fun bind(activity: Activity): Unbinder {
val activityClass = Class.forName(activity::class.java.canonicalName + "_ViewBinding")
val constructor = activityClass.getDeclaredConstructor(activity::class.java)
return try {
constructor.newInstance(activity) as Unbinder
} catch (e: Exception) {
e.printStackTrace()
Unbinder.EMPTY
}
}
}
- 在
butterknife-compiler
模块中,定义ButterKnifeProcessor
类,处理@BindView
注解;- 使用
JavaPoet
来完成Java文件生成; - 使用
Kotlin
,无法用AutoService
来定义注解解析器,会导致编译不执行,使用的最原始定义javax.annotation.processing.Processor
的方式;
- 使用
package com.crystal.butterkinfe_compiler
import com.crystal.butterknife_annotations.BindView
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements
/**
* 用于处理BindView注解
* on 2022/12/15
*/
class ButterKnifeProcessor : AbstractProcessor() {
private lateinit var filer: Filer
private lateinit var elementUtils: Elements
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
filer = processingEnv.filer
elementUtils = processingEnv.elementUtils
}
override fun getSupportedAnnotationTypes(): MutableSet<String> {
val set = mutableSetOf<String>()
for (supportAnnotation in getSupportAnnotations()) {
set.add(supportAnnotation.canonicalName)
}
return set
}
private fun getSupportAnnotations(): Set<Class<out Annotation>> {
val annotations = linkedSetOf<Class<out Annotation>>()
annotations.add(BindView::class.java)
return annotations
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latest()
}
override fun process(typeElement: MutableSet<out TypeElement>, env: RoundEnvironment): Boolean {
// 调试打印
System.out.println("------------------------------------>");
System.out.println("------------------------------------>");
val elements = env.getElementsAnnotatedWith(BindView::class.java)
//解析属性对应规则 1个activity -> List<View>
val elementsHashMap = linkedMapOf<Element, ArrayList<Element>>()
for (element in elements) {
val enclosingElement = element.enclosingElement //返回该元素对应的父元素,对应就是Activity
var viewElementsList = elementsHashMap[enclosingElement]
if (viewElementsList == null) {
viewElementsList = arrayListOf()
elementsHashMap[enclosingElement] = viewElementsList
}
viewElementsList.add(element)
}
//生成代码
for (mutableEntry in elementsHashMap) {
val enclosingElement = mutableEntry.key //对应Activity
val viewElements = mutableEntry.value //对应List<View>
//1.生成类名 public class MainActivity_ViewBinding implements Unbinder
val activityClassNameStr = enclosingElement.simpleName.toString()
val activityClassName = ClassName.bestGuess(activityClassNameStr)
//获取Unbinder ClassName
val unBinderClassName = ClassName.get("com.crystal.butterknife", "Unbinder")
val classBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addSuperinterface(unBinderClassName)
.addField(
activityClassName,
"target",
Modifier.PRIVATE
) //2.添加属性 private MainActivity target
val uiThreadClassName = ClassName.get("androidx.annotation", "UiThread")
//3.添加构造方法
val constructorBuilder =
MethodSpec.constructorBuilder().addParameter(activityClassName, "target")
.addStatement("this.target = target")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(uiThreadClassName)
//4.实现unbind方法
val callSuperClassName = ClassName.get("androidx.annotation", "CallSuper")
val unBindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override::class.java)
.addAnnotation(callSuperClassName)
.addModifiers(Modifier.PUBLIC)
//添加具体语句
//MainActivity target = this.target;
//if (target == null) throw new IllegalStateException("Bindings already cleared.");
// this.target = null;
unBindMethodBuilder.addStatement("\$T target = this.target", activityClassName)
unBindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")")
unBindMethodBuilder.addStatement("this.target = null")
//5.构造方法中添加findViewById
// unbind方法中添加 target.btn1 = null; target.btn2 = null;
for (viewElement in viewElements) {
val filedName = viewElement.simpleName.toString()
val utilsClassName = ClassName.get("com.crystal.butterknife", "Utils")
val viewId = viewElement.getAnnotation(BindView::class.java).resId
constructorBuilder.addStatement(
"target.\$L = \$T.findViewById(target,\$L)",
filedName,
utilsClassName,
viewId
)
unBindMethodBuilder.addStatement("target.\$L = null", filedName)
}
classBuilder.addMethod(constructorBuilder.build())
classBuilder.addMethod(unBindMethodBuilder.build())
//生成类
val packageName = elementUtils.getPackageOf(enclosingElement).qualifiedName.toString()
JavaFile.builder(packageName, classBuilder.build())
.addFileComment("Generated code from Butter Knife. Do not modify!")
.build().writeTo(filer)
}
return false
}
}
测试验证
- 在Activity中使用自定义的@BindView注解并绑定Activity
class CheckNetActivity : AppCompatActivity() {
@BindView(R.id.btn1)
lateinit var btn1: Button
@BindView(R.id.btn2)
lateinit var btn2: Button
private lateinit var unBind: Unbinder
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_check_net)
unBind = ButterKnife.bind(this)
btn1.setOnClickListener {
}
}
override fun onDestroy() {
super.onDestroy()
unBind.unbind()
}
}
对应生成的CheckNetActivity_ViewBinding
类;
总结
通过手写ButterKnife
,不仅对ButterKnife
的原理有了进一步的理解,同时也学习了APT
技术,虽然ButterKnife
已经被淘汰,但它其中的思想却是对我们开发有很大的帮助!
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )