写在前面
由于文章和代码写的比较久了
再次翻看阅读,打开工程运行的时候,发现注解处理器不生效
浪费了很多时间才搞清楚问题所在,所以先记录一下
之前使用的是
classpath 'com.android.tools.build:gradle:3.0.0'
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
由于AndroidStudio更新,再次clone代码后,工程自动更新配置
gradle 5.4.1版本,android tools3.5版本,发现注解处理器不生效
对比了ButterKnife 源码的配置,发现还是使用的gradle4.10.3,android tools 3.4
索性降低版本试了下,发现是可以的
最终定位问题原因:在Gradle 5.0将忽略compile classpath中的annotation processor,
需要手动添加到annotation processor path
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
ButterKnife优势
Android之神Jake Wharton写的ButterKnife
ButterKnife是一个应用于Android系统的View注入框架
可以减少大量的findViewById以及setOnClickListener代码
优势:
- 代码清晰,可读性强
- 简化Adapter中ViewHolder绑定
- 运行时不会影响APP效率,使用配置方便
- View绑定和Click事件处理功能,简化代码,提高开发效率
具体用法就不做介绍了,参考官方使用即可
可能有人会对第三点有疑问,觉得使用了反射,影响了效率
其实看了ButterKnife的源码就知道
反射只用在了创建XXXActivity_ViewBinding对象时
ButterKnife注解是编译时注解并非运行时注解
ButterKnife框架使用到的技术
- 自定义注解
- APT(Annotation Process Tool) 注解处理器
- javapoet自动生成代码
自定义注解
参考链接:https://blog.csdn.net/kaifa1321/article/details/79622715
APT
APT(Annotation Process Tool),是一种在代码编译时处理注解,按照一定的规则,生成相应的java文件
多用于对自定义注解的处理,目前比较流行的Dagger2, EventBus3,包括 ButterKnife,都是采用APT技术
对运行时的性能影响很小
Androidstudio配置 APT
android module : app, butterknife
java module : butterknife-compiler, butterknife-annotations
app 依赖butterknife
butterknife 依赖 java工程 butterknife-compiler
butterknife-annotations 里面就是放了自定义注解
工程的 build.gradle
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
//新版本的Androidstudio 已经不用这么配置了
//classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
java-library butterknife-compiler的build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.auto.service:auto-service:1.0-rc3'
compile 'com.squareup:javapoet:1.9.0'
compile project(path: ':butterknife-annotations')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
自定义注解 BindView 标记属性
/**
* 编译时注解 作用在属性上 BindView
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
MainActivity 使用注解 BindView
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv1)
TextView mTextView1;
@BindView(R.id.tv2)
TextView mTextView2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mTextView1.setText("hello android");
mTextView2.setText("hello Python");
}
}
ButterKnife的bind方法
public class ButterKnife {
public static UnBinder bind(Activity activity) {
//获取activity的Class
Class<?> activityClass = activity.getClass();
//获取activity的全路径名
String activityName = activityClass.getName();
//拼接className
String className = activityName + "_ViewBinding";
try {
//获取生成类的Class
Class clazz = Class.forName(className);
//获取构造函数
Constructor constructor = clazz.getDeclaredConstructor(activityClass);
//创建生成类的对象
UnBinder unBinder = (UnBinder) constructor.newInstance(activity);
return unBinder;
} catch (Exception e) {
e.printStackTrace();
}
//如果出错返回EMPTY
return UnBinder.EMPTY;
}
}
activityName + "_ViewBinding这个类长什么样呢?
在app的build目录下\build\generated\source\apt\debug\com\qingguoguo\followbutterknife
public final class MainActivity_ViewBinding implements UnBinder {
private MainActivity target;
public MainActivity_ViewBinding(MainActivity target) {
this.target = target;
this.target.mTextView1 = Utils.findViewById(target,2131165315);
this.target.mTextView2 = Utils.findViewById(target,2131165316);
}
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target.mTextView1 = null;
this.target.mTextView2 = null;
this.target = null;
}
}
MainActivity_ViewBinding 怎么生成的
封装 findViewById方法
public class Utils {
//封装 findViewById方法
public static <T extends View> T findViewById(Activity activity, int id) {
return (T) activity.findViewById(id);
}
}
生成代码
//@AutoService(Processor.class)这个注解一定要
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private Filer mFile;
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//初始化 java文件生成的位置
mFile = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
}
/**
* 用来指定支持的 SourceVersion
*
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 用来指定支持的 AnnotationTypes
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
/**
* 参考 ButterKnife 的写法
*
* @return
*/
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
//BindView目前只写了这个注解
annotations.add(BindView.class);
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 解析注解
HashMap<Element, List<Element>> elementMap = new HashMap<>(16);
//包含BindView注解的Set集合
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elementSet) {
System.out.println("----" + element.getSimpleName() + "-----" + element.getEnclosingElement());
/**
*比较乱 并没有给我们包裹好,需要自己组合,把一个类的所有属性集合到一起
*----mTextView1-----com.qingguoguo.followbutterknife.LoginActivity
*----mTextView2-----com.qingguoguo.followbutterknife.LoginActivity
*----mTextView1-----com.qingguoguo.followbutterknife.MainActivity
*----mTextView2-----com.qingguoguo.followbutterknife.MainActivity
*/
//拿到类名
Element enclosingElement = element.getEnclosingElement();
//Map中查找是否已经存了该类
List<Element> elements = elementMap.get(enclosingElement);
if (elements == null) {
//没有找到,创建集合
elements = new ArrayList<>();
elementMap.put(enclosingElement, elements);
}
//把该属性加到集合
elements.add(element);
}
System.out.println("----" + elementMap + "-----");
//生成代码,遍历Map集合
Set<Map.Entry<Element, List<Element>>> entries = elementMap.entrySet();
for (Map.Entry<Element, List<Element>> entry : entries) {
Element element = entry.getKey();
//父类
ClassName unBinderClassName = ClassName.get("com.qingguoguo.butterknife", "UnBinder");
//生成类
//activity Class Name
String activityClassNameStr = element.getSimpleName().toString();
ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
TypeSpec.Builder activityBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
.addModifiers(Modifier.FINAL, Modifier.PUBLIC);
//---------添加父接口-----------------//
activityBuilder.addSuperinterface(unBinderClassName);
//---------添加属性 private XXXActivity target ;-------//
activityBuilder.addField(activityClassName, "target", Modifier.PRIVATE);
//---------添加构造方法 public xxx_ViewBinding(xxx target) -------------------//
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder();
constructorMethodBuilder.addParameter(activityClassName, "target").addModifiers(Modifier.PUBLIC);
constructorMethodBuilder.addStatement("this.target = target");
//-------------------添加实现接口UnBinder 要重写的unbind方法----------//
ClassName callSuperClassName = ClassName.get("android.support.annotation",
"CallSuper");
MethodSpec.Builder unbindMethodBuilder = MethodSpec
.methodBuilder("unbind")
.addAnnotation(Override.class)
.addAnnotation(callSuperClassName).addModifiers(Modifier.PUBLIC);
unbindMethodBuilder.addStatement("$T target = this.target", activityClassName);
unbindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")");
//-------------------给类中带有注解的属性赋值----------------------//
List<Element> elementList = entry.getValue();
for (Element viewBindIdElement : elementList) {
String filedName = viewBindIdElement.getSimpleName().toString();
int id = viewBindIdElement.getAnnotation(BindView.class).value();
ClassName utilsClassName = ClassName.get("com.qingguoguo.butterknife",
"Utils");
constructorMethodBuilder.addStatement("this.target.$L = $T.findViewById(target,$L)", filedName, utilsClassName, id);
unbindMethodBuilder.addStatement("this.target.$L = null", filedName);
}
unbindMethodBuilder.addStatement("this.target = null");
activityBuilder.addMethod(unbindMethodBuilder.build());
activityBuilder.addMethod(constructorMethodBuilder.build());
//-----------------包名----------------------//
String packageName = mElementUtils.getPackageOf(element.getEnclosingElement()).getQualifiedName().toString();
//----------------生成Java文件----------------//
try {
JavaFile.builder(packageName,
activityBuilder.build()).addFileComment("仿ButterKnife自动生成").build().writeTo(mFile);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
}
断点调试
刚开始接触APT不太熟悉的时候,肯定写出来有很多问题
如果你希望断点调试,按照以往的经验发现不起作用
因为编译时在单独的JVM里面执行的,所以需要建立远程调试
- 在项目的根目录下gradle.properties 文件中加入如下两条语句
org.gradle.jvmargs= -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
org.gradle.parallel=true
-
run->Edit Config
编辑配置 -
创建远程Remote调试配置参数
Remote调试.png -
点击刚刚创建的配置,debug运行
debug运行 -
rebuild
rebuild
参考链接:https://blog.csdn.net/hongxue8888/article/details/99710884