前言
ButterKnife 通过使用@BindView 注解就可以完成findViewById 工作,它的实现原理其实也很简单,通过APT(Annotation Processing Too,注解解析器) 技术,在编译期为我们生成了一个绑定类,而从完成了View的绑定。
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
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)
val elementsHashMap = linkedMapOf<Element, ArrayList<Element>>()
for (element in elements) {
val enclosingElement = element.enclosingElement
var viewElementsList = elementsHashMap[enclosingElement]
if (viewElementsList == null) {
viewElementsList = arrayListOf()
elementsHashMap[enclosingElement] = viewElementsList
}
viewElementsList.add(element)
}
for (mutableEntry in elementsHashMap) {
val enclosingElement = mutableEntry.key
val viewElements = mutableEntry.value
val activityClassNameStr = enclosingElement.simpleName.toString()
val activityClassName = ClassName.bestGuess(activityClassNameStr)
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
)
val uiThreadClassName = ClassName.get("androidx.annotation", "UiThread")
val constructorBuilder =
MethodSpec.constructorBuilder().addParameter(activityClassName, "target")
.addStatement("this.target = target")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(uiThreadClassName)
val callSuperClassName = ClassName.get("androidx.annotation", "CallSuper")
val unBindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override::class.java)
.addAnnotation(callSuperClassName)
.addModifiers(Modifier.PUBLIC)
unBindMethodBuilder.addStatement("\$T target = this.target", activityClassName)
unBindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")")
unBindMethodBuilder.addStatement("this.target = 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 已经被淘汰,但它其中的思想却是对我们开发有很大的帮助!
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!?( ′・?・` )
|