简单回顾一下ButterKnife使用BindView注解初始化View过程:
其实最核心的是ButterKnife使用了APT技术,也就是Annotation Processing Tool的简称,翻译成中文就是"注解处理工具":
它的作用是在编译阶段处理注解,生成相应的java文件,然后在运行阶段,通过反射调用那些生成的java类中的方法来进行绑定等操作
生成的Java文件内容很简单,就是把当前Activity或者View传入进来,然后findViewById找到对应View并给带有注释的那些View赋值,所以使用BindView注解的变量都要声明为public
public final class BindProxy$MainActivity {
public BindProxy$MainActivity(MainActivity mainactivity) {
mainactivity.mTitleView = (android.widget.TextView) mainactivity.findViewById(2131165289);
}
}
其实我们也可以省去APT生成代码那一步,直接代码运行时解析那些注解,然后通过反射赋值,也不需要固定带注解的变量都是public的,比如下面这样:
/**
* 使用注解代替FindViewById()
*/
private void autoFindViews() {
List<Field> fields = ClassUtil.getAllField(getClass());
for (Field field : fields) {
if (field.isAnnotationPresent(BindView.class)) {
BindView bindView = field.getAnnotation(BindView.class);
int viewId = bindView.value();
if (viewId > 0) {
//反射访问私有变量需要设置这个
field.setAccessible(true);
try {
View view = findViewById(viewId);
if (view != null) {
if (field.isAnnotationPresent(BindClick.class)) {
view.setOnClickListener(this);
}
field.set(this, view);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
那么为什么还有那么多人用BufferKnife,而不是自己用几行代码反射搞定呢?
这主要还是取舍问题,通过上面我们知道BufferKnife也不是特别完美的:
但是如果简单一点使用反射,则会消耗更多的系统资源,比较适合项目还比较小,使用反射对整个项目性能影响不大的情况,而BufferKnife则适合较大项目,在编译时处理好,运行时减少使用反射节提高性能
下面我们使用简单的项目来模拟BufferKnife解析注解绑定View过程
build.gradle:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "7"
targetCompatibility = "7"
创建一个注解类型的java类:BindView.java
/**
* 通过配置控件id,替换findViewById
*
* @author zhujie
* @date 2019-09-13
* @time 08:32
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
创建一个常量类:Constant.java
/**
* 相关常量定义
*
* @author zhujie
* @date 2019-09-14
* @time 06:23
*/
public class Constant {
/**
* 生成类所在包名
*/
public static final String GENERATE_PACKAGE = "com.agilezhu.annotationkit.generate";
/**
* 生成类名固定前缀
*/
public static final String GENERATE_CLASS_NAME_HEAD = "BindProxy$";
/**
* 生成类完整类名开头:包名+类名固定前缀
*/
public static final String GENERATE_CLASS_FULL_NAME_HEAD = GENERATE_PACKAGE + "." + GENERATE_CLASS_NAME_HEAD;
}
build.gradle:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':annotation')
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'//auto-service本身也是个注解处理器
implementation 'com.google.auto.service:auto-service:1.0-rc4'//注解 processor 类,并对其生成 META-INF 的配置信息
implementation 'com.squareup:javapoet:1.8.0' //通过类调用的形式来生成java代码,避免手动拼接字符串
}
sourceCompatibility = "7"
targetCompatibility = "7"
接着创建自定义的注解处理器BindProcessor.java:这个类主要用于解析用户标注的注解,然后生成自定义java处理类
/**
* 处理绑定类型的注解
*
* @author zhujie
* @date 2019-09-13
* @time 08:29
*/
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
private Filer mFiler; //文件相关工具类:用于保存生成的java类文件
private Elements mElementUtils; //元素相关工具类:用于获取java类文件
private Messager mMessager;//用于打印日志
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnv.getFiler();
mElementUtils = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//返回该注解处理器能够处理哪些注解
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
//返回当前注解处理器支持的java版本号
return SourceVersion.latest();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取所有的源码文件
Set<? extends Element> elements = roundEnvironment.getRootElements();
for (Element element : elements) {
if (!(element instanceof TypeElement)) {//判断是否class类
continue;
}
//转换成class类型
TypeElement typeElement = (TypeElement) element;
//当前文件的类名
String classSimpleName = element.getSimpleName().toString();
//将要生成的java完整类名:BindProxy$+当前类名
String targetClassName = GENERATE_CLASS_NAME_HEAD + element.getSimpleName();
//创建方法(构造方法)
MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("" )
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.get(typeElement.asType()), classSimpleName.toLowerCase());
//获取当前类里所有元素
List<? extends Element> members = mElementUtils.getAllMembers(typeElement);
//当前类里所有添加了BindView注释的元素
List<Element> annotationMembers = new ArrayList<>();
for (Element member : members) {
BindView bindViewAnnotation = member.getAnnotation(BindView.class);
if (bindViewAnnotation != null) {
annotationMembers.add(member);
String paramName = classSimpleName.toLowerCase();
//构造方法中添加初始化代码:findViewById
bindMethodBuilder.addStatement(
String.format(
paramName + ".%s = (%s) " + paramName + ".findViewById(%s)"
, member.getSimpleName()
, ClassName.get(member.asType()).toString()
, bindViewAnnotation.value()));
}
}
//如果该类中没有我们自定义的注解,则不生成对应java处理类
if (annotationMembers.isEmpty()) {
continue;
}
//创建类
TypeSpec bindProxyClass = TypeSpec.classBuilder(targetClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(bindMethodBuilder.build())
.build();
//创建java文件
JavaFile bindProxyFile = JavaFile
.builder(GENERATE_PACKAGE, bindProxyClass)
.build();
try {
//保存java类文件
bindProxyFile.writeTo(mFiler);
} catch (Throwable e) {
e.printStackTrace();
}
}
return false;
}
}
build.gradle:
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
defaultConfig {
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//引入自定义的注解,这里使用api是为了引入当前library的项目可以也可以直接使用到自定义的注解
api project(':annotation')
}
AnnotationKit.kt:通过反射获取生成的类,并调用构造方法,从而绑定当前Activity中的View
/**
* 用于绑定组件
*
* @author zhujie
* @date 2019-09-13
* @time 18:32
*/
class AnnotationKit {
companion object {
/**
* 缓存构造方法
*/
private val mCacheConstructor = HashMap<Class<*>, Constructor<*>?>()
fun bind(activity: Activity) {
//从缓存中读取构造方法
var constructor = mCacheConstructor[activity.javaClass]
if (constructor == null) {
synchronized(mCacheConstructor) {
if (constructor == null) {
try {
val fullClassName =
GENERATE_CLASS_FULL_NAME_HEAD + activity::class.java.simpleName
//通过反射获取生成的类
val clazz = Class.forName(fullClassName)
//获取构造方法
constructor = clazz.getConstructor(activity.javaClass)
//存入缓存
mCacheConstructor[activity.javaClass] = constructor
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
//反射调用构造方法
constructor?.newInstance(activity)
}
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
//使用kapt处理注解处理器
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.agilezhu.annotationkit"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
//引入自定义注解library
implementation project(':library')
//使用kapt编译注解处理器
kapt project(':compiler')
}
class MainActivity : AppCompatActivity() {
@BindView(R.id.main_title_view)
lateinit var mTitleView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//调用生成的代码,初始化view
AnnotationKit.bind(this)
if (mTitleView != null) {
Toast.makeText(this, "BindView成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "BindView失败", Toast.LENGTH_SHORT).show()
}
}
}
package com.agilezhu.annotationkit.generate;
import com.agilezhu.annotationkit.MainActivity;
public final class BindProxy$MainActivity {
public BindProxy$MainActivity(MainActivity mainactivity) {
mainactivity.mTitleView = (android.widget.TextView) mainactivity.findViewById(2131165289);
}
}
[kapt] Incremental annotation processing requested, but support is disabled because the following processors are not incremental: com.agilezhu.compiler.BindProcessor (NON_INCREMENTAL), com.google.auto.service.processor.AutoServiceProcessor (NON_INCREMENTAL).
则在根目录下的gradle.properties文件中添加下面内容就就可以:#apt增量编译,加速编译效果
kapt.incremental.apt=true
Execution failed for task ':lib:common:kaptDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction
> java.lang.reflect.InvocationTargetException (no error message)
在gradle.properties文件中添加下面这段内容:kapt.incremental.apt = false
kapt.include.compile.classpath=false
kapt.use.worker.api=false
AbstractProcessor
等类的情况,请删除该Module,重新创建Java or Kotlin Library
类型项目,即使其他类型Module把build.gradle
配置改成一模一样也不行!Demo下载地址