Java学习之注解(五)Android循序渐进实现高逼格自定义ViewBinder
前言
Butterknife的代码到目前为止还没有仔细去看,这里也是自己在网上找的一个资料,主要是针对注解学习理解,但是发现这个学习资料估计是在Butterknife里面扣的,因为如果单单实现一个ViewBinder绑定注解,没必要那样写,代码高逼格了,既然看了这个代码,我就又实现到扩展循序渐进搞一遍,也来个可扩展的高逼格代码。
所以,有些东西没必要上路就想太多(兼容这,兼容那),先实现,在想着扩展。
参考文献:Android 注解使用之通过 annotationProcessor 注解生成代码实现自己的 ButterKnife 框架
正文
先看下目錄
以上分为4各模块:
1.app:宿主,测试注解,
2.annotation:存放自定义注解,
3.api:(类似“用于申明UI注解框架的api”网上的说法,不敢苟同,我自己理解下:)对自定义注解一种辅助操作模块,注解实现注入(api模块核心,不理解没关系,往下看你会深入理解的)。例如activity和Fragment,View使用方法有区别,在这个模块中处理,又例如资源管理:绑定和清除(可通过下面代码自己理解下)
高质量代码都体现在api模块下,其他的都是很难动的代码
4.compiler:用于在编译期间通过反射机制自动生成代码
额?有人问?为啥不可以使用同一个模块一次性完成!!!
- compiler里面的代码只是在编译生成代码是有效,分开的这个compiler模块是不会被打包进入我们的apk的,从而减少apk体积;
- 非常清晰明了,annotation就用于自定义注解,其他来了都不行;api用于辅佐使用注解
- 据说butterknife就这么分的,还听说这种分发就是它传出来的,有些期待对它的学习了
总结:没啥好纠结的,这就好比你媳妇让你跪搓衣板,你偏偏犟“我就不,我就要跪键盘”一个道理
实现ViewBinder Demo V0.9版
先能实现就行,代码能不能直视,关上灯都一样!!!
1.)annotation 声明注解框架中要使用的注解
这里我声明了一个BindView注解,声明周期为RUNTIME,作用域为成员变量
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {int value();
}
因为这里仅仅想要实现绑定View控件,这里就自定义了一个BindView注解
注意啦:这里一定要写成RetentionPolicy.RUNTIME,而不是RetentionPolicy.CLASS,为啥?后面会介绍为啥不能用RetentionPolicy.CLASS
app.annotation module为java library,build.gradle配置如下
apply plugin: 'java'
sourceCompatibility =JavaVersion.VERSION_1_7
targetCompatibility =JavaVersion.VERSION_1_7
dependencies {compile fileTree(dir: 'libs', include: ['*.jar'])
}
2.)api 一个帮助注解使用的类
package com.example.api;import android.app.Activity;
import android.util.Log;
import android.view.View;import com.example.annotation.BindView;import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;/*** Copyright (C), 2019-2020, 佛生* FileName: ViewInject* Author: 佛学徒* Date: 2020/11/10 13:29* Description:view绑定在activity上* History:*/
public class ViewBindWithActivity {//运行时解析注解 BindViewpublic static void inject(Activity activity) {//获取当前activity下的所有字段Field[] fields = activity.getClass().getDeclaredFields();//通过该方法设置所有的字段都可访问,否则即使是反射,也不能访问private修饰的字段AccessibleObject.setAccessible(fields, true);for (Field field : fields) {//这里api需要依赖于annotation,注意!!!如果BindView采用RetentionPolicy.CLASS修饰这里返回的needInject是false,没错即使有BindView也是false,只有RetentionPolicy.RUNTIME修饰才表示在运行是BindView也有效boolean needInject = field.isAnnotationPresent(BindView.class);if (needInject) {BindView anno = field.getAnnotation(BindView.class);int id = anno.value();if (id == -1) {continue;}//以下相当于做了这个操作: textView = this.findViewById(R.id.tv_text);View view = activity.findViewById(id);Class fieldType = field.getType();try {//把View转换成field声明的类型field.set(activity, fieldType.cast(view));} catch (Exception e) {Log.e(BindView.class.getSimpleName(), e.getMessage());}}}}
}
**为啥annotation 中BindView 只能使用RetentionPolicy.RUNTIME?**因为我以上代码采用的是java的反射机制,运行时注解及有效,那么必须RetentionPolicy.RUNTIME修饰
//这里api需要依赖于annotation,注意!!!如果BindView采用RetentionPolicy.CLASS修饰这里返回的needInject是false,没错即使有BindView也是false,只有RetentionPolicy.RUNTIME修饰才表示在运行是BindView也有效
boolean needInject = field.isAnnotationPresent(BindView.class);
api引入的gradle文件
apply plugin: 'com.android.library'...
dependencies {...implementation project(path: ':annotation')}
3.)app宿主:用于测试注解
package com.example.viewbinderdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.widget.TextView;import com.example.annotation.BindView;
import com.example.api.ViewBindWithActivity;public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_text)TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// textView = this.findViewById(R.id.tv_text);ViewBindWithActivity.inject(this);textView.setText("我是一个IT搬运工!!");}
}
gradle配置如下:
apply plugin: 'com.android.application'
...dependencies {...implementation project(path: ':annotation')implementation project(path: ':api')}
以上即一个简单的通过反射实现的注解,啥都没有,你这么在项目里面写会被人揍成猪头!!!
**总结:**这个是通过反射机制完成的注解,很多原始的框架,例如EventBus3.0前,Dagger1等都是通过反射实现的,但是后来都没有这么去实现,因为效率问题,打个比方(希望没人叫比方),一个activity业务很重代码量多,这个时候用反射机制就很不友好了(去一个个找字段,方法,类,在代码量超多情况下执行效率非常低下,这里也让我们明白一个道理,没有啥事一蹴而就的,都是因为突出了问题或者一个新技术,再实现架构的迭代,这就是架构师的意思所在),那咋办,凉…当然有更好的方式去实现了
实现ViewBinder Demo V1.0版
android在编译时通过反射生成相应的代码
1.)annotation模块外甥打灯笼,但:
这里可以采用RetentionPolicy.CLASS修饰了
2.)complier根据注解在编译期间自动生成java代码
该类中主要2个类:1.一个是自定义AbstractProcessor类,android studio在编译时会自动扫描该文件;2.自定义类,主要用于编译执行AbstractProcessor时,生成我们所需要的java类。
注意,这里如果没有正确引入gradle,是不会执行自定义AbstractProcessor类,也可以说,如果在build中没有生成对应的类,那么除非build报错,否则必然是graldle引入问题:
apply plugin: 'java-library'dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])//用于自动为 JAVA Processor 生成 META-INF 信息。implementation 'com.google.auto.service:auto-service:1.0-rc3'annotationProcessor "com.google.auto.service:auto-service:1.0-rc3"//快速生成.java文件的库implementation 'com.squareup:javapoet:1.8.0'implementation project(path: ':annotation')
}sourceCompatibility = "1.7"
targetCompatibility = "1.7"
1.AnnotatedClass 类poet生成java代码
package com.example.compiler;import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;import java.util.HashMap;
import java.util.Map;import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;/*** Copyright (C), 2019-2020, 佛生* FileName: AnnotatedClass* Author: 佛学徒* Date: 2020/11/10 16:48* Description:生成代码的工具类* History:*/
class AnnotatedClass {private String mBindingClassName;//生成新java类的名称private String mPackageName;//生成新java类包名private TypeElement mTypeElement;private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();public AnnotatedClass(TypeElement classElement, Elements elementsUtils) {this.mTypeElement = classElement;PackageElement packageElement = elementsUtils.getPackageOf(mTypeElement);this.mPackageName = packageElement.getQualifiedName().toString();String className = mTypeElement.getSimpleName().toString();this.mBindingClassName = className + "$$ViewBinder";}public void putElement(int id, VariableElement element) {mVariableElementMap.put(id, element);}JavaFile generateFile() {//生成java文件JavaFile javaFile = JavaFile.builder(mPackageName, generateJavaCode()).build();return javaFile;}private TypeSpec generateJavaCode() {//生成java代码TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName).addModifiers(Modifier.PUBLIC).addMethod(generateMethod()).build();return bindingClass;}private MethodSpec generateMethod() {//生成方法,并且通过poet写入具体实现方法的代码ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());//加入一个bind方法,public类型,传入参数为 host(当前注解所在的类对象),void类型所以没有返回类型MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind").addModifiers(Modifier.PUBLIC).returns(void.class).addParameter(host, "host");//bind方法的具体实现for (int id : mVariableElementMap.keySet()) {VariableElement element = mVariableElementMap.get(id);String name = element.getSimpleName().toString();String type = element.asType().toString();//相当于加了host.view = (view类型)host.findViewById(id)methodBuilder.addCode("host." + name + " = " + "(" + type + ")host.findViewById( " + id + " );\n");}return methodBuilder.build();}
}
2.自定义AbstractProcessor类
package com.example.compiler;import com.example.annotation.BindView;
import com.google.auto.service.AutoService;import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {private Filer mFiler;//文件相关的辅助类private Messager mMessager;//日志相关的辅助类private Elements mElementUtils;//元素相关类private Map<String, AnnotatedClass> mAnnotatedClassMap = new HashMap<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mMessager = processingEnv.getMessager();mElementUtils = processingEnv.getElementUtils();mFiler = processingEnv.getFiler();}@Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> supportTypes = new LinkedHashSet<>();supportTypes.add(BindView.class.getCanonicalName());return supportTypes;}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");mAnnotatedClassMap.clear();//获得被BindView注解标记的elementSet<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);//对不同的activity进行分类for (Element element: elements){VariableElement variableElement = (VariableElement) element;TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();String fullClassName = classElement.getQualifiedName().toString();AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullClassName);if(annotatedClass == null){annotatedClass = new AnnotatedClass(classElement,mElementUtils);mAnnotatedClassMap.put(fullClassName,annotatedClass);}BindView bindAnnotation = variableElement.getAnnotation(BindView.class);int id = bindAnnotation.value();annotatedClass.putElement(id,variableElement);}//通过javapoet生成java文件for(String key: mAnnotatedClassMap.keySet()){AnnotatedClass annotatedClass = mAnnotatedClassMap.get(key);try {annotatedClass.generateFile().writeTo(mFiler);} catch (IOException e) {e.printStackTrace();}}mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");return true;}
}
3.)app宿主
activity类:
package com.example.viewbinderdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.widget.TextView;import com.example.annotation.BindView;public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_text)TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// textView = this.findViewById(R.id.tv_text);// ViewBindWithActivity.inject(this);textView.setText("我是一个IT搬运工!!");}
}
gradle添加了代码:
annotationProcessor project(':compiler')
然后,我们build一下下查看我们生成的build文件:
代码非常之简单,就是因为MainActivity中有BindView注解,所以生成了一个MainActivity$$ViewBinder类,该类提供一个bind方法,该方法作用就是实现view控件的初始化
这就结束啦?当然没有,MainActivity$$ViewBinder.bind仅仅是提供了一个view控件初始化的方法,还没调用啊,这个时候就需要api模块了,辅助类,是不是很意外,很惊喜!!!
2.)api辅助模块
api首先在gradle里面干掉下面的代码
implementation project(path: ':annotation')
ViewBindWithActivity代码:
package com.example.api;import android.app.Activity;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** Copyright (C), 2019-2020, 佛生* FileName: ViewInject* Author: 佛学徒* Date: 2020/11/10 13:29* Description:view绑定在activity上* History:*/
public class ViewBindWithActivity {//编译时注解方式:具体的view控件实例化调用public static void bind(Activity activity) {Class clazz = activity.getClass();try {Class bindViewClass = Class.forName(clazz.getName() + "$$ViewBinder");Method method = bindViewClass.getMethod("bind", activity.getClass());method.invoke(bindViewClass.newInstance(), activity);} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}//运行时解析注解 BindView
// public static void inject(Activity activity) {// //获取当前activity下的所有字段
// Field[] fields = activity.getClass().getDeclaredFields();
//
// //通过该方法设置所有的字段都可访问,否则即使是反射,也不能访问private修饰的字段
// AccessibleObject.setAccessible(fields, true);
//
// for (Field field : fields) {// //这里api需要依赖于annotation
// boolean needInject = field.isAnnotationPresent(BindView.class);
//
// if (needInject) {// BindView anno = field.getAnnotation(BindView.class);
//
// int id = anno.value();
// if (id == -1) {// continue;
// }
//
// //以下相当于做了这个操作: textView = this.findViewById(R.id.tv_text);
// View view = activity.findViewById(id);
// Class fieldType = field.getType();
// try {// //把View转换成field声明的类型
// field.set(activity, fieldType.cast(view));
// } catch (Exception e) {// Log.e(BindView.class.getSimpleName(), e.getMessage());
// }
// }
// }
// }
}
别忘了MainActivity中需要添加:
package com.example.viewbinderdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.widget.TextView;import com.example.annotation.BindView;
import com.example.api.ViewBindWithActivity;public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_text)TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// textView = this.findViewById(R.id.tv_text);ViewBindWithActivity.bind(this);textView.setText("我是一个IT搬运工!!");}
}
完美!!!至此解决demo!
**注意啦??**这里在ViewBindWithActivity.bind(this)用的还是反射机制,通过反射找到MainActivity$$ViewBinder,所以我之前看了很多资料要么模棱两可要么说这个是注解,结束了反射提高了效率等等,其实这里用的还是反射,有句话叫 无反射不框架,只不过这里用到的反射大大的缩短了查找范围,所以效率相对来说影响不大而已。
实现ViewBinder Demo V1.1版
你这么写代码会被人拿42米长刀砍死的!!!我要是用Fragment呢,他还有自定义的View!!!那我们就进一步优化代码吧,蛋…咳咳,头疼!
篇幅有限,直接上代码这里的所有依赖关系沿用V1.0即可
1.)annotation模块保持不变
2.)api模块修改
首先提供一个ViewFinder 初始化View接口
package com.example.api;import android.view.View;/*** Copyright (C), 2019-2020, 佛生* FileName: ViewFinder* Author: 佛学徒* Date: 2020/11/11 13:53* Description: 所有初始化view接口* History:*/
public interface ViewFinder {/*** 最终实现类似于 View view = obj.findViewById(id)* @param obj 当前view所在类:fragment,activity,自定义view等* @param id 当前view的id值* @return*/View findView(Object obj,int id);
}
接口名字纠结了半天,最终还是和原demo保持一致,因为这里obj.findViewById使用的也是findView,如果是activity发现者又该如何实现,我们对activity实现view初始化:ActivityViewFinder类,
package com.example.api;import android.app.Activity;
import android.view.View;/*** Copyright (C), 2019-2020, 佛生* FileName: ActivityViewFinder* Author: 佛学徒* Date: 2020/11/11 14:04* Description: activity初始化view* History:*/
public class ActivityViewFinder implements ViewFinder {@Overridepublic View findView(Object obj, int id) {if (obj instanceof Activity) {return ((Activity) obj).findViewById(id);}return null;}
}
fragment呢,FragmentViewFinder类如下:
package com.example.api;import android.view.View;import androidx.fragment.app.Fragment;/*** Copyright (C), 2019-2020, 佛生* FileName: FragmentViewBinder* Author: 佛学徒* Date: 2020/11/11 14:08* Description: fragment中view初始化* History:*/
public class FragmentViewFinder implements ViewFinder{@Overridepublic View findView(Object obj, int id) {if(obj instanceof Fragment){return ((Fragment) obj).getView().findViewById(id);}return null;}
}
这样的代码非常值得回味,好的代码让人感觉很舒服
在提供一个view绑定的接口,ViewBidner,这里注意,不仅仅实现view的绑定,同样的,这里我们还可以实现view的解绑,ViewBinder类如下,
package com.example.api;/*** Copyright (C), 2019-2020, 佛生* FileName: ViewBinder* Author: 佛学徒* Date: 2020/11/11 13:48* Description: view捆绑者* History:*/
public interface ViewBinder<T> {//实现view和host(host表示view所在的类:activity,fragment等)绑定,啥意思:host.view = obj.findViewById(id)//obj.findViewById(id),因为有在activity初始化的,也有在fragment初始化的,还有...,因此我们在ViewFinder中实现初始化步骤//所以:最终理解host.view = ViewFinder.findViewvoid bind(T host, Object obj,ViewFinder viewFinder);void unBind(T host);
}
代码细品,回味,三浅一深用点力,自己试下,我以上代码以及贴了全部了,真的很爽!!!
好了,这里我们还缺两个类:一个在host中绑定(解绑)view的类,一个调用该绑定类
这里把compiler中的gradle注释掉一下代码,build就不会生成xx$$ViewBinder
//annotationProcessor "com.google.auto.service:auto-service:1.0-rc3"
1.host中绑定(解绑)view的类 MainActivity$ViewBinder(app模块中写)
package com.example.viewbinderdemo;import android.widget.TextView;import com.example.api.ViewBinder;
import com.example.api.ViewFinder;/*** Copyright (C), 2019-2020, 佛生* FileName: MainActivity$$ViewBinder* Author: 佛学徒* Date: 2020/11/11 15:29* Description: MainActivity绑定view* History:*/
class MainActivity$ViewBinder implements ViewBinder<MainActivity> {@Overridepublic void bind(MainActivity host, Object obj, ViewFinder viewFinder) {host.textView = (TextView) viewFinder.findView(obj, R.id.tv_text);}@Overridepublic void unBind(MainActivity host) {host.textView = null;}
}
2.以上确实实现了,但是和MainActivity其实没啥关系,因为这里的MainActivity和实际使用过程中的MainActivity没啥关系,没调用啊,需要将这里的代码注入到我们正在使用的MainActivity中,下面我们来实现注入(Inject)工作(app模块中写)
package com.example.viewbinderdemo;import android.app.Activity;import com.example.api.ActivityViewFinder;
import com.example.api.ViewFinder;/*** Copyright (C), 2019-2020, 佛生* FileName: PerformInject* Author: 佛学徒* Date: 2020/11/12 13:34* Description: 执行注入:即将view的初始化工作绑定到它对应的activity,fragment类中* History:*/
public class PerformInject {//一个activity完成view初始化代码private static ViewFinder viewFinder;private static MainActivity$ViewBinder mainActivity$ViewBinder;//表示一个activity中注入public static void inject(Activity activity) {if (viewFinder == null)viewFinder = new ActivityViewFinder();inject(activity, activity, viewFinder);}private static void inject(Object obj, Object source, ViewFinder viewFinder) {if (mainActivity$ViewBinder == null) {mainActivity$ViewBinder = new MainActivity$ViewBinder();}mainActivity$ViewBinder.bind((MainActivity) obj, source, viewFinder);}public static void unBind(Object obj) {mainActivity$ViewBinder.unBind((MainActivity) obj);}
}
然后我们看看MainActivity调用这个注入,才算真正完成使用,运行一遍,一切ok!!!
这里和注解啥关系,没用到@BindView吧?同样的,我们这里针对MainActivity使用了这样的写法,如果每个界面都这么写我估计…
好了,然后删了MainActivity$ViewBinder和PerformInject代码吧!
3.)compiler模块实现
首先定义一个BindViewField类,用于表示针对annotation模块下BindView这个注解的域,为啥要这么做?首先这个域是干啥的,主要用于存储它对应的注解相关信息;那么我们就可以确定有多少个注解类,就产生多少个相对应的域,例如我有定义了一个BindClick,那么我就生成一个BindClickFied
然后我们分析这里的BindView注解主要是干啥的,目的就是为了不需要初始化View,在View上加上@BindView(id)即可直接使用
@BindView(R.id.tv_text)
TextView textView;
初始化工作 **textView = (TextView)this.findViewById(id)**工作放在build生成的代码中完成,那么这里我们的BindViewField能否通过初始化代码来查看下需要存储哪些信息:1.TextView类型;2.id值;3.textView 名称,那就好办了,代码如下:
package com.example.compiler;import com.example.annotation.BindView;import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;/*** Copyright (C), 2019-2020, 佛生* FileName: ViewBinderField* Author: 佛学徒* Date: 2020/11/11 14:28* Description: 定义一个BindView域,主要通过element获取id,类型,名称* History:*/
class BindViewField {//表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数等private VariableElement mVariableElement;private int mResId;BindViewField(Element element) throws IllegalArgumentException {if (element.getKind() != ElementKind.FIELD) {//判断Element的类型,种类如下://PACKAGE 包//ENUM 枚举//CLASS 类//ANNOTATION_TYPE 注解//INTERFACE 接口//ENUM_CONSTANT 枚举常量//FIELD 字段//PARAMETER 方法参数//LOCAL_VARIABLE 局部变量//METHOD 方法//CONSTRUCTOR 构造方法//TYPE_PARAMETER 类型参数throw new IllegalArgumentException(String.format("Only fields can be annotated with @%s",BindView.class.getSimpleName()));}mVariableElement = (VariableElement) element;BindView bindView = mVariableElement.getAnnotation(BindView.class);mResId = bindView.value();if (mResId < 0) {//String.format()字符串的拼接throw new IllegalArgumentException(String.format("value() in %s for field %s is not valid!", BindView.class.getSimpleName(), mVariableElement.getSimpleName()));}}/*** 获取变量名称** @return*/Name getFieldName() {return mVariableElement.getSimpleName();}/*** 获取变量id** @return*/int getResId() {return mResId;}/*** 获取变量类型** @return*/TypeMirror getFiledType() {return mVariableElement.asType();}
}
你可能会说,你都看了,当然知道这么写了!!!说得对,给你口头嘉奖,我也是这么对该架构设计人员这么说的!!!
代码熟能生巧,我也是看了并且自己编了一遍才更深刻理解这种理念,也在想我也会有这么一天写代码信手拈来随心所欲,只要有恒心,肉…铁棒也能磨成针
3.1.)延伸
这样我们就可以考虑BindClick注解如何实现
我们先看下view点击监听demo写法:
textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});textView1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});
由此我们在annotation模块写一个BindClick注解
package com.example.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Copyright (C), 2019-2020, 佛生* FileName: BindClick* Author: 佛学徒* Date: 2020/11/11 14:59* Description:点击监听注解* History:*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface BindClick {int[] value();
}
BindClickField域如何编写呢???
然后我们先继续完成BindView注解,然后在回来补充BindClick,意思就是前人栽树后人乘凉,然后顺便在树上搭个窝常驻!!!
继续!!!compiler模块还有两个类,一个是自定义AbstractProcessor类,该类用户当找到注解时(这里目前就一个BindView注解);再找到Annotated类生成java类,并且通过执行以下方法在buid中生成对应的java文件(别忘了放开下面的代码,之前上面注释了)
annotationProcessor "com.google.auto.service:auto-service:1.0-rc3"
Annotated类干了啥事,上面的MainActivity$ViewBinder就是它造的,而且干的比我们写的好很多,因为这块自动机械式生成java文件,运用反射,poet代码生成器技术——价格公道,童叟无欺啊!
package com.example.compiler;import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;import java.util.ArrayList;import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;/*** Copyright (C), 2019-2020, 佛生* FileName: AnnotatedClass* Author: 佛学徒* Date: 2020/11/10 16:48* Description:生成代码的工具类* History:*/
class AnnotatedClass {private static class TypeUtil {static final ClassName BINDER = ClassName.get("com.example.api", "ViewBinder");static final ClassName PROVIDER = ClassName.get("com.example.api", "ViewFinder");}private TypeElement mTypeElement;//表示一個class類private Elements mElements;private ArrayList<BindViewField> mFields;public AnnotatedClass(TypeElement classElement, Elements elements) {this.mTypeElement = classElement;this.mElements = elements;mFields = new ArrayList<>();}void addField(BindViewField field) {mFields.add(field);}JavaFile generateFile() {//生成java文件JavaFile javaFile = null;//如下代码,添加bindView方法,// public类型,// 加入Override元注解,三// 个参数T(view所在的类,activity或fragment等) host, Object source,ViewFinder finderMethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bind").addModifiers(Modifier.PUBLIC).addAnnotation(Override.class).addParameter(TypeName.get(mTypeElement.asType()), "host").addParameter(TypeName.OBJECT, "source").addParameter(TypeUtil.PROVIDER, "finder");for (BindViewField field : mFields) {//这里的意思就是添加了代码,host.xx = (Obj)finder.findView(source,resId);//有多少添加多少//find views// statemenet 中的类型占位符,$N 是名称替换,$T 是类型替换,$L 是字面量替换bindViewMethod.addStatement("host.$N = ($T)(finder.findView(source,$L))",field.getFieldName(),ClassName.get(field.getFiledType()),field.getResId());}//生成unBindView解绑方法MethodSpec.Builder unBindViewMethod = MethodSpec.methodBuilder("unBind").addModifiers(Modifier.PUBLIC).addParameter(TypeName.get(mTypeElement.asType()), "host").addAnnotation(Override.class);for (BindViewField field : mFields) {unBindViewMethod.addStatement("host.$N = null", field.getFieldName());}//动态生成class类TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$ViewBinder").addModifiers(Modifier.PUBLIC)//addSuperinterface:继承父类接口ViewBinder<T>,并且将当前view所在类作为 T.addSuperinterface(ParameterizedTypeName.get(TypeUtil.BINDER,TypeName.get(mTypeElement.asType()))).addMethod(bindViewMethod.build()).addMethod(unBindViewMethod.build()).build();String packageName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();javaFile = JavaFile.builder(packageName,injectClass).build();return javaFile;}}
再来看自定义AbstractProcessor类
package com.example.compiler;import com.example.annotation.BindView;
import com.google.auto.service.AutoService;import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;@AutoService(Processor.class)
public class MyProcessor extends AbstractProcessor {private Filer mFiler;//文件相关的辅助类private Messager mMessager;//日志相关的辅助类private Elements mElementUtils;//元素相关类private Map<String, AnnotatedClass> mAnnotatedClassMap = new HashMap<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);mMessager = processingEnv.getMessager();mElementUtils = processingEnv.getElementUtils();mFiler = processingEnv.getFiler();}@Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> supportTypes = new LinkedHashSet<>();supportTypes.add(BindView.class.getCanonicalName());return supportTypes;}@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}private void error(String msg, Object... args) {mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {mAnnotatedClassMap.clear();try {processBindView(roundEnvironment);} catch (IllegalArgumentException e) {e.printStackTrace();error(e.getMessage());}for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {try {annotatedClass.generateFile().writeTo(mFiler);} catch (IOException e) {e.printStackTrace();error("Generate file failed reason: %s", e.getMessage());}}return false;}//查找到所有的BindView注解private void processBindView(RoundEnvironment roundEnvironment) throws IllegalArgumentException {//非常消耗效率的代码,这里在build构建非常完美,如果程序运行执行非常不妥for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {AnnotatedClass annotatedClass = getAnnotatedClass(element);BindViewField bindViewField = new BindViewField(element);annotatedClass.addField(bindViewField);}}//针对拥有注解的所在类,新建一个AnnotatedClass类,目的当然是生成对应的java代码//这里为什么单独拎出来写???是的,那如果我还会添加一个BindClick呢private AnnotatedClass getAnnotatedClass(Element element) {TypeElement typeElement = (TypeElement) element.getEnclosingElement();String fullName = typeElement.getQualifiedName().toString();AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullName);if (annotatedClass == null) {annotatedClass = new AnnotatedClass(typeElement, mElementUtils);mAnnotatedClassMap.put(fullName, annotatedClass);}return annotatedClass;}
}
build一下下:
package com.example.viewbinderdemo;import android.widget.TextView;
import com.example.api.ViewBinder;
import com.example.api.ViewFinder;
import java.lang.Object;
import java.lang.Override;public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {@Overridepublic void bind(MainActivity host, Object source, ViewFinder finder) {host.textView = (TextView)(finder.findView(source,2131165426));}@Overridepublic void unBind(MainActivity host) {host.textView = null;}
}
奥吆,漂亮!!!
下面我们将以上代码 Inject 注入(一直不清楚注入的正式含义,这里就通了) 到MainActivity中
在api模块补入注入类:MyViewBinder
package com.example.api;import android.app.Activity;import java.util.LinkedHashMap;
import java.util.Map;/*** Copyright (C), 2019-2020, 佛生* FileName: MyViewBinder* Author: 佛学徒* Date: 2020/11/12 15:05* Description: 将实例注入到类中* History:*/
public class MyViewBinder {//该类用于实现view在activity中的初始化private static ViewFinder activityViewFinder = new ActivityViewFinder();//...private static ViewFinder fragmentViewFinder = new FragmentViewFinder();private static final Map<String, ViewBinder> binderMap = new LinkedHashMap<>();//完成绑定工作public static void bind(Activity activity) {bind(activity, activity, activityViewFinder);}private static void bind(Object host, Object object, ViewFinder finder) {String className = host.getClass().getName();try {ViewBinder binder = binderMap.get(className);if (binder == null) {Class<?> aClass = Class.forName(className + "$$ViewBinder");binder = (ViewBinder) aClass.newInstance();binderMap.put(className, binder);}if (binder != null) {//核心代码binder.bind(host, object, finder);}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}public static void unBind(Object host) {String className = host.getClass().getName();ViewBinder binder = binderMap.get(className);if (binder != null) {binder.unBind(host);}binderMap.remove(className);}
}
然后在MainActivity中添加注入代码:
package com.example.viewbinderdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.view.View;
import android.widget.TextView;import com.example.annotation.BindView;
import com.example.api.MyViewBinder;public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_text)TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// textView = this.findViewById(R.id.tv_text);MyViewBinder.bind(this);textView.setText("我是一个IT搬运工!!");textView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {}});}@Overrideprotected void onDestroy() {super.onDestroy();MyViewBinder.unBind(this);}
}
perfect!!!完美
延伸之BindClick编写
首先我们先看在MainActivity&&ViewBinder中应该如何初始化,clickBind方法
package com.example.viewbinderdemo;import android.view.View;
import android.widget.TextView;
import android.widget.Toast;import com.example.api.ViewBinder;
import com.example.api.ViewFinder;
import java.lang.Object;
import java.lang.Override;public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {@Overridepublic void bind(MainActivity host, Object source, ViewFinder finder) {host.textView = (TextView)(finder.findView(source,2131165426));}public void clickBind(final MainActivity host){host.findViewById(R.id.tv_text).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(host, "你点击的是text", Toast.LENGTH_LONG);}});host.findViewById(R.id.tv_text1).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(host, "你点击的是text1,别搞错了", Toast.LENGTH_LONG);}});}@Overridepublic void unBind(MainActivity host) {host.textView = null;}
}
MainActivity中添加BindClick注解
package com.example.viewbinderdemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;import com.example.annotation.BindClick;
import com.example.annotation.BindView;
import com.example.api.MyViewBinder;public class MainActivity extends AppCompatActivity {@BindView(R.id.tv_text)TextView textView;@BindClick({R.id.tv_text, R.id.tv_text1})public void clicked(View view) {if (view.getId() == R.id.tv_text) {Toast.makeText(MainActivity.this, "你点击的是text", Toast.LENGTH_LONG).show();} else if (view.getId() == R.id.tv_text1) {Toast.makeText(MainActivity.this, "你点击的是text1,别搞错了", Toast.LENGTH_LONG).show();}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// textView = this.findViewById(R.id.tv_text);MyViewBinder.bind(this);textView.setText("我是一个IT搬运工!!");}@Overrideprotected void onDestroy() {super.onDestroy();MyViewBinder.unBind(this);}
}
沿用ViewBinder接口
package com.example.api;/*** Copyright (C), 2019-2020, 佛生* FileName: ViewBinder* Author: 佛学徒* Date: 2020/11/11 13:48* Description: view捆绑者* History:*/
public interface ViewBinder<T> {//实现view和host(host表示view所在的类:activity,fragment等)绑定,啥意思:host.view = obj.findViewById(id)//obj.findViewById(id),因为有在activity初始化的,也有在fragment初始化的,还有...,因此我们在ViewFinder中实现初始化步骤//所以:最终理解host.view = ViewFinder.findViewvoid bindView(T host, Object obj,ViewFinder viewFinder);void bindClick(T host, Object obj,ViewFinder viewFinder);void unBind(T host);
}
MyViewBinder注入类如下:
package com.example.api;import android.app.Activity;import java.util.LinkedHashMap;
import java.util.Map;/*** Copyright (C), 2019-2020, 佛生* FileName: MyViewBinder* Author: 佛学徒* Date: 2020/11/12 15:05* Description: 将实例注入到类中* History:*/
public class MyViewBinder {//该类用于实现view在activity中的初始化private static ViewFinder activityViewFinder = new ActivityViewFinder();//...private static ViewFinder fragmentViewFinder = new FragmentViewFinder();private static final Map<String, ViewBinder> binderMap = new LinkedHashMap<>();//完成绑定工作public static void bind(Activity activity) {bind(activity, activity, activityViewFinder);}private static void bind(Object host, Object object, ViewFinder finder) {String className = host.getClass().getName();try {ViewBinder binder = binderMap.get(className);if (binder == null) {Class<?> aClass = Class.forName(className + "$$ViewBinder");binder = (ViewBinder) aClass.newInstance();binderMap.put(className, binder);}if (binder != null) {//核心代码binder.bindView(host, object, finder);binder.bindClick(host,object,finder);}} catch (ClassNotFoundException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}public static void unBind(Object host) {String className = host.getClass().getName();ViewBinder binder = binderMap.get(className);if (binder != null) {binder.unBind(host);}binderMap.remove(className);}
}
添加了
binder.bindClick(host,object,finder);
下面是compiler生成器代码,用以生成MainActivity V i e w B i n d e r 类 , 首 先 重 新 编 排 一 下 M a i n A c t i v i t y ViewBinder类,首先重新编排一下MainActivity ViewBinder类,首先重新编排一下MainActivityViewBinder吧,上面那个仅仅用来理解:
package com.example.viewbinderdemo;import android.view.View;
import android.widget.TextView;
import android.widget.Toast;import com.example.api.ViewBinder;
import com.example.api.ViewFinder;import java.lang.Object;
import java.lang.Override;
import java.util.HashMap;
import java.util.Map;public class MainActivity$$ViewBinder implements ViewBinder<MainActivity> {//用于存放Viewprivate Map<Integer, View> views = new HashMap<>();@Overridepublic void bindView(MainActivity host, Object source, ViewFinder finder) {if (views.get(2131165426) == null) {views.put(2131165426, finder.findView(source, 2131165426));}host.textView = (TextView) views.get(2131165426);}@Overridepublic void bindClick(final MainActivity host, Object source, ViewFinder finder) {if (views.get(R.id.tv_text) == null) {views.put(R.id.tv_text, finder.findView(source, R.id.tv_text));}views.get(R.id.tv_text).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(host, "你点击的是text", Toast.LENGTH_LONG);}});if (views.get(R.id.tv_text1) == null) {views.put(R.id.tv_text1, finder.findView(source, R.id.tv_text1));}views.get(R.id.tv_text1).setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(host, "你点击的是text1,别搞错了", Toast.LENGTH_LONG);}});}@Overridepublic void unBind(MainActivity host) {// host.textView = null;views.clear();}
}
编译代码之前先温习一下BindClick 类
package com.example.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Copyright (C), 2019-2020, 佛生* FileName: BindClick* Author: 佛学徒* Date: 2020/11/11 14:59* Description:点击监听注解* History:*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface BindClick {int[] value();
}
compiler编译器模块编写
接下来就是如何通过编译器生成$$ViewBidner java代码
1.编写BindClickMethod 域,这里仅仅用于提取int[] mResId,如果还要其他的,我们可以回来补充(补:事实证明还差一个方法名的获取,还有采用ExecutableElement 来获取方法对应的信息)
package com.example.compiler;import com.example.annotation.BindClick;import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;/*** Copyright (C), 2019-2020, 佛生* FileName: ViewBinderField* Author: 佛学徒* Date: 2020/11/11 14:28* Description: 定义一个BindClass域,主要通过element获取id* History:*/
class BindClickMethod {//表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数等private ExecutableElement mExecutableElement;private int[] mResId;BindClickMethod(Element element) throws IllegalArgumentException {if (element.getKind() != ElementKind.METHOD) {//判断Element的类型,种类如下://PACKAGE 包//ENUM 枚举//CLASS 类//ANNOTATION_TYPE 注解//INTERFACE 接口//ENUM_CONSTANT 枚举常量//FIELD 字段//PARAMETER 方法参数//LOCAL_VARIABLE 局部变量//METHOD 方法//CONSTRUCTOR 构造方法//TYPE_PARAMETER 类型参数throw new IllegalArgumentException(String.format("Only fields can be annotated with @%s",BindClick.class.getSimpleName()));}mExecutableElement = (ExecutableElement) element;BindClick bindClick = mExecutableElement.getAnnotation(BindClick.class);mResId = bindClick.value();if (mResId.length == 0) {//String.format()字符串的拼接throw new IllegalArgumentException(String.format("value() in %s for field %s is not valid!", BindClick.class.getSimpleName(), mExecutableElement.getSimpleName()));}}/*** 获取变量id** @return*/int[] getResIds() {return mResId;}/*** 获取方法名称** @return*/Name getMethodName() {return mExecutableElement.getSimpleName();}
}
2.MyProcessor类中getSupportedAnnotationTypes方法需添加BindClick类,process方法同样针对BindClick注解进行筛选
@Overridepublic Set<String> getSupportedAnnotationTypes() {HashSet<String> supportTypes = new LinkedHashSet<>();supportTypes.add(BindView.class.getCanonicalName());supportTypes.add(BindClick.class.getCanonicalName());return supportTypes;}@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {...try {processBindView(roundEnvironment);processBindClick(roundEnvironment);} catch (IllegalArgumentException e) {e.printStackTrace();error(e.getMessage());}...return false;}//查找到所有的BindClick注解private void processBindClick(RoundEnvironment roundEnvironment) throws IllegalArgumentException {//非常消耗效率的代码,这里在build构建非常完美,如果程序运行执行非常不妥for (Element element : roundEnvironment.getElementsAnnotatedWith(BindClick.class)) {AnnotatedClass annotatedClass = getAnnotatedClass(element);BindClickMethod bindClickMethod = new BindClickMethod(element);annotatedClass.addBindClickMethod(bindClickMethod);}}
还有个核心代码AnnotatedClass代码生成类
package com.example.compiler;import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;/*** Copyright (C), 2019-2020, 佛生* FileName: AnnotatedClass* Author: 佛学徒* Date: 2020/11/10 16:48* Description:生成代码的工具类* History:*/
class AnnotatedClass {private static class TypeUtil {static final ClassName BINDER = ClassName.get("com.example.api", "ViewBinder");static final ClassName PROVIDER = ClassName.get("com.example.api", "ViewFinder");}private TypeElement mTypeElement;//表示一個class類private Elements mElements;private ArrayList<BindViewField> mFields;private ArrayList<BindClickMethod> mClickMethods;public AnnotatedClass(TypeElement classElement, Elements elements) {this.mTypeElement = classElement;this.mElements = elements;mFields = new ArrayList<>();mClickMethods = new ArrayList<>();}void addBindViewField(BindViewField field) {mFields.add(field);}void addBindClickMethod(BindClickMethod bindClickMethod) {mClickMethods.add(bindClickMethod);}JavaFile generateFile() {//生成java文件JavaFile javaFile;FieldSpec.Builder bindViewField = FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(Map.class),ClassName.get(Integer.class),ClassName.get("android.view", "View")),"views", Modifier.PRIVATE).initializer("new $T()\n", HashMap.class);//如下代码,添加bindView方法,// public类型,// 加入Override元注解,三// 个参数T(view所在的类,activity或fragment等) host, Object source,ViewFinder finderMethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bindView").addModifiers(Modifier.PUBLIC).addAnnotation(Override.class).addParameter(TypeName.get(mTypeElement.asType()), "host").addParameter(TypeName.OBJECT, "source").addParameter(TypeUtil.PROVIDER, "finder");for (BindViewField field : mFields) {//这里的意思就是添加了代码,host.xx = (Obj)finder.findView(source,resId);//有多少添加多少//find viewsbindViewMethod.addStatement("if(views.get($L) == null) {", field.getResId());bindViewMethod.addStatement("views.put($L,finder.findView(source,$L));", field.getResId(), field.getResId());// statemenet 中的类型占位符,$N 是名称替换,$T 是类型替换,$L 是字面量替换bindViewMethod.addStatement("host.$N = ($T)views.get($L);" +"}",field.getFieldName(),ClassName.get(field.getFiledType()),field.getResId());}//生成bindClick点击类MethodSpec.Builder bindClickMethod = MethodSpec.methodBuilder("bindClick").addModifiers(Modifier.PUBLIC).addAnnotation(Override.class).addParameter(TypeName.get(mTypeElement.asType()), "host", Modifier.FINAL).addParameter(TypeName.OBJECT, "source").addParameter(TypeUtil.PROVIDER, "finder");for (BindClickMethod method : mClickMethods) {if (method == null) break;for (int res : method.getResIds()) {//find viewsbindClickMethod.addStatement("if(views.get($L) == null) {\n", res).addStatement("views.put($L,finder.findView(source,$L));" +"}", res, res);CodeBlock.Builder builder = CodeBlock.builder();builder.add("views.get($L).setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(View v) { host.$N(v); }});", res, method.getMethodName());bindClickMethod.addCode(builder.build());}}//生成unBindView解绑方法MethodSpec.Builder unBindViewMethod = MethodSpec.methodBuilder("unBind").addModifiers(Modifier.PUBLIC).addParameter(TypeName.get(mTypeElement.asType()), "host").addAnnotation(Override.class);// for (BindViewField field : mFields) {unBindViewMethod.addStatement("views.clear();\n");unBindViewMethod.addStatement("host = null;\n");// }//动态生成class类TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$ViewBinder").addModifiers(Modifier.PUBLIC)//addSuperinterface:继承父类接口ViewBinder<T>,并且将当前view所在类作为 T.addSuperinterface(ParameterizedTypeName.get(TypeUtil.BINDER, TypeName.get(mTypeElement.asType()))).addField(bindViewField.build()).addMethod(bindViewMethod.build()).addMethod(bindClickMethod.build()).addMethod(unBindViewMethod.build()).build();String packageName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();javaFile = JavaFile.builder(packageName, injectClass).build();return javaFile;}}
试了一下才知道,代码真特么难写,不过并不难,只是非常麻烦+不熟悉!!!
好了,我再次彻底了解了注解了!!!还有那个注入,没有动手始终不知道到底啥意思。
Java学习之注解(五)Android循序渐进实现高逼格自定义ViewBinder相关推荐
- java学习记录十五:集合二Collections、Set、Map
java学习记录十五:集合二 一.Collections工具类 一.解释 二.常用方法 1.打乱集合顺序 2.按照默认规则排序 3.按指定规则排序 4.批量添加元素 二.可变参数 一.解释 二.写法 ...
- Java学习 第十五天
Java学习 第十五天 第一章 StringBuilder类 1.1 字符串的不可变 1.2 StringBuilder概述 1.3 构造方法 1.4 两个常用方法 1.4.1 append方法 1. ...
- 动力节点『lol版』Java学习路线图(五)Java框架阶段
五.框架之路-丛林沙漠巨神峰 框架技术 送君千里终须一别,拜别了厄运小姐和格雷福斯的盛情挽留,在目送了崔斯特开大消失后,ez在哈雷尔港驻足了片刻便启程前往以绪奥肯.他摸了摸纳袋(空间口袋),里面是菲兹 ...
- Java学习之注解Annotation实现原理
前言: 最近学习了EventBus.BufferKinfe.GreenDao.Retrofit 等优秀开源框架,它们新版本无一另外的都使用到了注解的方式,我们使用在使用的时候也尝到不少好处,基于这种想 ...
- 2021-08-04 Java学习基础第五天总结
Java第五天基础学习 1.集合体系 2.迭代器 3.并发修改异常 4.增强for循环 5.泛型 6.可变参数 7.正则表达式 8.数据结构 集合体系: 单列集合: Collection接口: LIs ...
- Java学习日报—注解、Hash、Lombok—2021/12/02
目录 1 相关注解 1.1 @Controller 和 @RestController 1.2 @ApiIgnore 1.3 @PostMapping 2 Java知识点 2.1 instanceof ...
- java学习第二十五天
目录 目录 类与对象 一.引出 二.概述和快速入门 类与对象关系示意图 例子 编译错误记录 三.对象在内存中的存在形式 四.属性/成员变量的注意事项和细节说明 五.如何创建对象 六.如何访问属性 七. ...
- Java学习 第十五章 成员变量和局部变量的区别 / 三大特征之一 (封装性)/构造方法 /private关键字
第十五章 局部变量和成员变量: 1.定义位置不一样 局部变量:在方法内部定义 成员变量:在方法的外部,直接写在类当中 2.作用范围不一样 局部变量:只能在方法当中使用 成员变量:整个类都可以使用 3. ...
- Java 学习手记(五)第二部分 继承与接口
五.abstract 类和 abstract 方法 用关键字 abstract 修饰的类称为 abstract 类(抽象类).如: abstract class A { ... } abstract ...
最新文章
- 在Android Studio中打开Android Device Monitor时报错的解决方法
- 把一个dataset的表放在另一个dataset里面_使用中文维基百科语料库训练一个word2vec模型并使用说明...
- python文件打包发布(引用的包也可以加进来),打包出错解决了,运行出错解决了...
- pythonjson实例_python:JSON的两种常用编解码方式实例解析
- Java中的一些基本转换
- 基于内容的图像检索系统(合集)
- vue 背景透明度_Visual Studio 2017 设置透明背景图
- http抓包实践--(一)--fiddler和http(s)
- 数据库多表链接查询的方式
- 示波器使用方法入门之道
- LayUI使用distpicker.js插件实现三级联动
- HD AUDIO For XP SP3 声卡修正补丁下载
- 第一届FME模板开发者大赛
- html- 颜色代码
- 0008:《以色列:一个国家的诞生》读后感
- Python3高级篇
- 【计算专业】有趣的拓扑学问题 动画演示
- 【jitpack】android implementation 远程仓库
- FeelTheBase(进制转换工具)v1.2.0.1版本更新
- RX 6500 XT和rtx3050 哪个好
热门文章
- Self-attention(李宏毅2022
- html5限制拖拽区域怎么实现,html5怎么实现拖拽
- progisp编程下载器:芯片识别字不匹配 不能完成芯片擦除
- 万字长文,带你入门异步编程
- 基于RBAC 的SAAS系统权限设计
- Redis:集合SADD、SISMEMBER、SPOP、SRANDMEMBER、SREM、SMOVE、SCARD、SMEMBERS、SSCAN命令介绍
- aspen如何确定塔板数_Aspen中有关回流比、塔板数、进料板位置等灵敏度分析作用...
- 什么是大数据分析 主要应用于哪些行业?以制造业为例
- java if 跳出循环_break跳出的是if语句,还是for循环?
- 查看网卡ip linux,教你如何查看本机ip地址?