前言

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相关推荐

  1. java学习记录十五:集合二Collections、Set、Map

    java学习记录十五:集合二 一.Collections工具类 一.解释 二.常用方法 1.打乱集合顺序 2.按照默认规则排序 3.按指定规则排序 4.批量添加元素 二.可变参数 一.解释 二.写法 ...

  2. Java学习 第十五天

    Java学习 第十五天 第一章 StringBuilder类 1.1 字符串的不可变 1.2 StringBuilder概述 1.3 构造方法 1.4 两个常用方法 1.4.1 append方法 1. ...

  3. 动力节点『lol版』Java学习路线图(五)Java框架阶段

    五.框架之路-丛林沙漠巨神峰 框架技术 送君千里终须一别,拜别了厄运小姐和格雷福斯的盛情挽留,在目送了崔斯特开大消失后,ez在哈雷尔港驻足了片刻便启程前往以绪奥肯.他摸了摸纳袋(空间口袋),里面是菲兹 ...

  4. Java学习之注解Annotation实现原理

    前言: 最近学习了EventBus.BufferKinfe.GreenDao.Retrofit 等优秀开源框架,它们新版本无一另外的都使用到了注解的方式,我们使用在使用的时候也尝到不少好处,基于这种想 ...

  5. 2021-08-04 Java学习基础第五天总结

    Java第五天基础学习 1.集合体系 2.迭代器 3.并发修改异常 4.增强for循环 5.泛型 6.可变参数 7.正则表达式 8.数据结构 集合体系: 单列集合: Collection接口: LIs ...

  6. Java学习日报—注解、Hash、Lombok—2021/12/02

    目录 1 相关注解 1.1 @Controller 和 @RestController 1.2 @ApiIgnore 1.3 @PostMapping 2 Java知识点 2.1 instanceof ...

  7. java学习第二十五天

    目录 目录 类与对象 一.引出 二.概述和快速入门 类与对象关系示意图 例子 编译错误记录 三.对象在内存中的存在形式 四.属性/成员变量的注意事项和细节说明 五.如何创建对象 六.如何访问属性 七. ...

  8. Java学习 第十五章 成员变量和局部变量的区别 / 三大特征之一 (封装性)/构造方法 /private关键字

    第十五章 局部变量和成员变量: 1.定义位置不一样 局部变量:在方法内部定义 成员变量:在方法的外部,直接写在类当中 2.作用范围不一样 局部变量:只能在方法当中使用 成员变量:整个类都可以使用 3. ...

  9. Java 学习手记(五)第二部分 继承与接口

    五.abstract 类和 abstract 方法 用关键字 abstract 修饰的类称为 abstract 类(抽象类).如: abstract class A { ... } abstract ...

最新文章

  1. 在Android Studio中打开Android Device Monitor时报错的解决方法
  2. 把一个dataset的表放在另一个dataset里面_使用中文维基百科语料库训练一个word2vec模型并使用说明...
  3. python文件打包发布(引用的包也可以加进来),打包出错解决了,运行出错解决了...
  4. pythonjson实例_python:JSON的两种常用编解码方式实例解析
  5. Java中的一些基本转换
  6. 基于内容的图像检索系统(合集)
  7. vue 背景透明度_Visual Studio 2017 设置透明背景图
  8. http抓包实践--(一)--fiddler和http(s)
  9. 数据库多表链接查询的方式
  10. 示波器使用方法入门之道
  11. LayUI使用distpicker.js插件实现三级联动
  12. HD AUDIO For XP SP3 声卡修正补丁下载
  13. 第一届FME模板开发者大赛
  14. html- 颜色代码
  15. 0008:《以色列:一个国家的诞生》读后感
  16. Python3高级篇
  17. 【计算专业】有趣的拓扑学问题 动画演示
  18. 【jitpack】android implementation 远程仓库
  19. FeelTheBase(进制转换工具)v1.2.0.1版本更新
  20. RX 6500 XT和rtx3050 哪个好

热门文章

  1. Self-attention(李宏毅2022
  2. html5限制拖拽区域怎么实现,html5怎么实现拖拽
  3. progisp编程下载器:芯片识别字不匹配 不能完成芯片擦除
  4. 万字长文,带你入门异步编程
  5. 基于RBAC 的SAAS系统权限设计
  6. Redis:集合SADD、SISMEMBER、SPOP、SRANDMEMBER、SREM、SMOVE、SCARD、SMEMBERS、SSCAN命令介绍
  7. aspen如何确定塔板数_Aspen中有关回流比、塔板数、进料板位置等灵敏度分析作用...
  8. 什么是大数据分析 主要应用于哪些行业?以制造业为例
  9. java if 跳出循环_break跳出的是if语句,还是for循环?
  10. 查看网卡ip linux,教你如何查看本机ip地址?