-
ButterKnife介绍
主要是解决掉 findViewById 和 setOnclick ,还包括资源的注入 , IOC ,运行时注解(上次)和编译时注解(ButterKnife注解) -
ButterKnife原理分析
主要采用编译时注解,说白了就是用 apt 生成代码
ButterKnife使用
github地址:
https://github.com/JakeWharton/butterknife
配置步骤:
说明:最初我按照最新的配置步骤来配置项目的时候,发现报错。
error: resource android:attr/fontVariationSettings resource android:attr/ttcIndex not found.
dependencies {
implementation 'com.jakewharton:butterknife:9.0.0-rc1'
annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0-rc1'
}
//To use Butter Knife in a library, add the plugin to your buildscript:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.jakewharton:butterknife-gradle-plugin:9.0.0-rc1'
}
}
// and then apply it in your module:
apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'
网上查找解决办法:https://blog.csdn.net/wumama123/article/details/79493190,但是还没有解决。然后之后改用ButterKnife8.1.0的版本。
对应配置地址:https://www.jianshu.com/p/0392199a682b
然后发现还是会报错:
Error:android-apt plugin is incompatible with the Android Gradle plugin. Please use 'annotationProc
解决方案:
解决方案.png
一切配置后之后使用:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.user)
TextView username;
Unbinder mUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = ButterKnife.bind(this);
username.setText("Spring");
}
@Override
protected void onDestroy() {
super.onDestroy();
mUnbinder.unbind();
}
}
ButterKnife原理分析:
通过搜索会发现有这样一个自动生成的类MainActivity$$ViewBinder:
public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
@Override
public Unbinder bind(Finder finder, T target, Object source) {
return new InnerUnbinder<>(target, finder, source);
}
protected static class InnerUnbinder<T extends MainActivity> implements Unbinder {
protected T target;
protected InnerUnbinder(T target, Finder finder, Object source) {
this.target = target;
target.username = finder.findRequiredViewAsType(source, 2131230891, "field 'username'", TextView.class);
}
@Override
public void unbind() {
T target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
target.username = null;
this.target = null;
}
}
}
ButterKnife自动生成.png
通过分析这个代码,我们可以简单抽象的理解成,我们自己写了一个类MainActivity_ViewBinder:
public class MainActivity_ViewBinder {
MainActivity target;
public MainActivity_ViewBinder(MainActivity target) {
this.target = target;
target.username = target.findViewById(R.id.user);
}
}
使用的时候其实是在自动绑定控件:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.user)
TextView username;
Unbinder mUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// mUnbinder = ButterKnife.bind(this);
new MainActivity_ViewBinder(this);
username.setText("Spring MainActivity_ViewBinder");
}
@Override
protected void onDestroy() {
super.onDestroy();
mUnbinder.unbind();
}
}
MainActivity$$ViewBinder 这个类会随着每次编译运行的时候,自动生成。那么如何手写一个ButterKnife呢?
用到的原理是apt,是java知识,在《Thinking in java》这本书里面有讲解。
手写一个ButterKnife
生成代码的环境是java代码,因此是一个库。
ButterKnife的第一个版本架构图:
ButterKnife的第一个版本架构图.png
但是我们需要拆分,因为生成代码的compiler这部分代码,不应该打包在apk包里面,因此我们对上面的架构图改版。
ButterKnife的第二个版本架构图:
ButterKnife的第二个版本架构图.png
新建各个Lib依赖
lib依赖关系.pngMyButterKnife-annotation:用来写注解,这里我们只写一个MyBindView注解用作演示,后面可以添加onClick事件等。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface MyBindView {
int value();
}
MyButterKnife-compiler :为生成代码的依赖包,这里需要在这个包下面的build.gradle拷入两个依赖以及设置编码,完整build.gradle如下:
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation project(':MyButterKnife-annotations')
// 拷入
compile 'com.google.auto.service:auto-service:1.0-rc3'
compile 'com.squareup:javapoet:1.8.0'
}
//Error:(23, 35) 错误: 编码GBK的不可映射字符
tasks.withType(JavaCompile){
options.encoding ="UTF-8"
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
写一个代码生成的Processor类MyButterKnifeProcessor继承MyButterKnifeProcessor,完整代码如下:
@AutoService(Processor.class)
public class MyButterKnifeProcessor extends AbstractProcessor {
// 1. 指定处理的版本,这里返回最新版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//2. 给到需要处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(MyBindView.class);
// ... 其他注解,如onClick
return annotations;
}
//3. 生成代码
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("----------->代码生成部分");
return false;
}
}
为了演示MyButterKnifeProcessor的process方法是否能走到,我们在app里面引入compiler和annotations这两个依赖,然后在MainActivity里面绑定一个View,代码如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.user)
TextView username;
@BindView(R.id.user2)
TextView username2;
@MyBindView(R.id.user3)// 使用自己写的MyBindView注解
TextView username3;
Unbinder mUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = ButterKnife.bind(this);
//new MainActivity_ViewBinder(this);
username.setText("Spring MainActivity_ViewBinder");
username2.setText("username2");
//username3.setText("username3");
}
@Override
protected void onDestroy() {
super.onDestroy();
mUnbinder.unbind();
然后运行程序,在gradle console控制台输出:
gradle console控制台输出.png可以看到我们的代码执行了。
小结:以上的代码需要注意几点
- @AutoService(Processor.class) 这个注解别忘记加
- 解决编码GBK的不可映射字符的问题
tasks.withType(JavaCompile){
options.encoding ="UTF-8"
} - MainActivity需要改动之后,apt才会执行
- 错误信息提示
Annotation processors must be explicitly declared now
解决参考:https://blog.csdn.net/daihuimaozideren/article/details/78902079 ,推荐使用第二种
处理processor
这里直接贴出完整代码,后面会说几个注意事项:
@AutoService(Processor.class)
public class MyButterKnifeProcessor extends AbstractProcessor {
private Filer mFiler;//用来生成java文件
private Elements mElementsUtils;//用来获取生成java文件的路径
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
mElementsUtils = processingEnvironment.getElementUtils();
}
// 1. 指定处理的版本,这里返回最新版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//2. 给到需要处理的注解
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(MyBindView.class);
// ... 其他注解,如onClick
return annotations;
}
//3. 生成代码
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("----------->代码生成部分");
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(MyBindView.class);
for (Element e : elements) {
Element enclosingElement = e.getEnclosingElement();
System.out.println("--Element--" + e.getSimpleName().toString() + " in " + enclosingElement.getSimpleName().toString());
}
// 1. 解析 map: activity-->list<element>
Map<Element, List<Element>> map = new LinkedHashMap<>();
for (Element e : elements) {
Element enclosingElement = e.getEnclosingElement();
List<Element> viewElements = map.get(enclosingElement);
if (viewElements == null) {
viewElements = new ArrayList<>();
map.put(enclosingElement, viewElements);
}
viewElements.add(e);
}
// 2.生成 java类
for (Map.Entry<Element, List<Element>> entry : map.entrySet()) {
Element enclosingElement = entry.getKey();
List<Element> elementsValue = entry.getValue();
System.out.println(enclosingElement + "----------->" + elementsValue.size());//com.ivyzh.butterknifedemo.MainActivity----------->2
String activitySimpleName = enclosingElement.getSimpleName().toString();
ClassName unbinderClassSimpleName = ClassName.get("com.ivyzh.mybutterknife", "MyUnbinder");
System.out.println("unbinderClassSimpleName----------->" + unbinderClassSimpleName);//com.ivyzh.mybutterknife.MyUnbinder
// 2.1组装类: xxx_MyViewBinding implements Unbinder
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activitySimpleName + "_MyViewBinder")
.addModifiers(Modifier.FINAL, Modifier.PUBLIC).addSuperinterface(unbinderClassSimpleName)
.addField(ClassName.bestGuess(enclosingElement.toString()), "target");// 添加成员变量 MainActivity target;
;
// 2.2组装unbind 方法
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC);
// 2.3组装构造函数: public xxx_ViewBinding(xxx target)
// ClassName activityName = ClassName.bestGuess(activitySimpleName);// 这里不能用activitySimpleName
ClassName activityName = ClassName.bestGuess(enclosingElement.toString());// 用enclosingElement.toString()
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(activityName, "target");
// 2.3.1添加 target.textView1 = Utils.findViewById(target,R.id.tv1);
// target.username = finder.findRequiredViewAsType(source, 2131230905, "field 'username'", TextView.class);
// 2.3.2 unber里面的方法也在这里面实现
unbindMethodBuilder.addStatement("$L target= this.target", enclosingElement.getSimpleName().toString());
for (Element view : elementsValue) {
String viewName = view.getSimpleName().toString();
ClassName utilsName = ClassName.get("com.ivyzh.mybutterknife", "FindViewUtils");
int resId = view.getAnnotation(MyBindView.class).value();
constructorBuilder.addStatement("this.target = target");
constructorBuilder.addStatement("target.$L = $L.findViewById(target,$L)", viewName, utilsName, resId);
unbindMethodBuilder.addStatement("target.$L = null", viewName);
}
// 2.4 将方法统一添加到classBuilder中
classBuilder.addMethod(constructorBuilder.build());// 添加构造函数
classBuilder.addMethod(unbindMethodBuilder.build());// 添加unbinder方法
// 2.5生成类
try {
String packageName = "";//如果是空的话,会生成在根目录
packageName = mElementsUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
JavaFile.builder(packageName, classBuilder.build())
.build()
.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
System.out.println("生成类失败:" + e.toString());
}
}
return false;
}
}
MyUnbinder:
public interface MyUnbinder {
void unbind();
MyUnbinder EMPTY = new MyUnbinder() {
@Override
public void unbind() {
}
};
}
FindViewUtils:这个工具类写在MyButterKnife依赖包中
public class FindViewUtils {
public static <T> T findViewById(Activity activity, int viewId) {
return (T) activity.findViewById(viewId);
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
// 使用MainActivity_ViewBinder
TextView username;
// 使用ButterKnife
@BindView(R.id.user2)
TextView username2;
// 使用自己写的MyBindView注解
@MyBindView(R.id.user3)
TextView username3;
@MyBindView(R.id.user4)
TextView tvUserName4;
Unbinder mUnbinder;
MyUnbinder mMyUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mUnbinder = ButterKnife.bind(this);
new MainActivity_ViewBinder(this);
mMyUnbinder = MyButterKnife.bind(this);
username.setText("use MainActivity_ViewBinder");
username2.setText("use butterknife.");
username3.setText("use MyBindView");
tvUserName4.setText("use by MyBindView");
}
@Override
protected void onDestroy() {
super.onDestroy();
mUnbinder.unbind();
mMyUnbinder.unbind();
}
}
MyButterKnife:
public class MyButterKnife {
public static MyUnbinder bind(Activity activity) {
try {
// MainActivity_MyViewBinder
//ClassNotFoundException: com.ivyzh.butterknifedemo.MainActivity_MyViewBinder
Class<? extends MyUnbinder> clazz = (Class<? extends MyUnbinder>) Class.forName(activity.getClass().getName() + "_MyViewBinder");
Constructor<? extends MyUnbinder> constructor = clazz.getDeclaredConstructor(activity.getClass());
MyUnbinder unbinder = constructor.newInstance(activity);
return unbinder;
} catch (Exception e) {
e.printStackTrace();
}
return MyUnbinder.EMPTY;
}
}
运行效果:
运行效果.png
上面四个TextView分别是通过自定义简化的MainActivity_ViewBinder、JakeWharton的butterknife、自己写的MyBindView、自己写的MyBindView。
另:瑕疵 Onclick 注解没有 , id 好像不正常,private 属性没提示错误(属性加private修饰会报错, 错误: tvUserName4可以在MainActivity中访问private),等等 ,可以参考 ButterKnife 的源码。
源码的一些思考:
面试
开发中看看使用场景(明天)
拿出部分代码整合到自己的框架中 ,有的时候我们很多功能是没有用到的
END.
网友评论