- Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制;
- Java中的类、方法、变量、参数和包等都可以被标注,Java 标注可以通过反射获取标注内容;
在编译器生成类文件时,标注可以被嵌入到字节码中,Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容; - 注解以@注解名的形式存在于代码中,也支持自定义 Java 注解;
- 注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。程序可以利用ava的反射机制来了解你的类及各种元素上有无何种标记,针对不同的标记,就去做相应的事件。
- 作用在代码的注解,在java.lang中
- 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- 标记表示过时的,不推荐使用。可以用于修饰方法,属性,类。如果使用被此注解修饰的方法,属性或类,会报编译警告。
- 指示编译器去忽略注解中声明的警告
- Java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告;
- Java8开始支持,标识一个匿名函数或函数式接口;
- Java8开始支持,标识某注解可以在同一个声明上使用多次;
- 作用在其他注解的注解,在java.lang.annotation中
- 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
- 其可选策略如下:
- RetentionPolicy.SOURCE: 注解仅存在于源码中,在class字节码文件中不包含;
- RetentionPolicy.CLASS: 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得;
- RetentionPolicy.RUNTIME: 注解会在class字节码文件中存在,在运行时可以通过反射获取到;
- 如果自定义注解,只存在于源码或字节码中无法发挥作用,在运行期间能获取到注解才能实现我们目的,所以自定义注解中是使用 @Retention(RetentionPolicy.RUNTIME);
- 标记这个注解作用的范围
- 其可选类型如下
- ElementType.TYPE: 接口、类、枚举、注解 - ElementType.FIELD:属性字段、枚举的常量 - ElementType.METHOD:方法 - ElementType.PARAMETER: 方法参数 - ElementType.CONSTRUCTOR: 构造函数 - ElementType.LOCAL_VARIABLE: 局部变量 - ElementType.ANNOTATION_TYPE: 注解(@Retention注解中就使用该属性) - ElementType.PACKAGE: 包 - ElementType.TYPE_PARAMETER:类型泛型,即泛型方法、泛型类、泛型接口 (jdk1.8加入) - ElementType.TYPE_USE:类型使用.可以用于标注任意类型除了 class (jdk1.8加入)@documented
- 标记这些注解是否包含在用户文档中
- 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
- 创建自定义注解和创建一个接口相似,但是注解的interface关键字需要以@符号开头,可以为注解声明方法,示例如下
//创建: @documented // 此注解包含在用户文档中 @Target(ElementType.METHOD)//此注解只能用在方法上 @Inherited//子类可以继承父类中的此注解 @Retention(RetentionPolicy.RUNTIME)// 此注解保存在运行时,可以通过反射访问 public @interface MyAnnotation { //注解方法不能带有参数,可以有默认值, //返回值类型限定为:基本类型、String、Enums、Annotation或者是这些类型的数组 String name() ;//没有默认值,使用时需要显示赋值 int age() default -1;//有默认值,使用时可以不赋值 } //使用: @MyAnnotation(name = "ljy",age = 18) private fun initView() { }实战:使用自定义注解+AOP实现打印方法耗时
- 上面说了自定义注解如何定义,那么我们来结合实际业务需求写一个实战的例子:打印方法耗时,当然也可以用来做埋点统计
- 创建自定义注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface GetTime { String tag() default ""; }
- 通过AOP解析注解,我这里用的aspectj框架,关于AOP并不是本篇相关的技术就不做展开说明了;
@Around("execution(@GetTime * *(..))") public void getTime(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature(); Method method = joinPointObject.getMethod(); boolean flag = method.isAnnotationPresent(GetTime.class); LjyLogUtil.d("flag:"+flag); String tag = null; if (flag) { GetTime getTime = method.getAnnotation(GetTime.class); tag = getTime.tag(); } if (TextUtils.isEmpty(tag)) { Signature signature = joinPoint.getSignature(); tag = signature.toShortString(); } long time = System.currentTimeMillis(); try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } LjyLogUtil.d( tag+" get time: " + (System.currentTimeMillis() - time)); }
- 使用注解:给需要统计的方法添加注解,例如Application.onCreate方法里面经常会做许多初始化 *** 作,我们可能很关系它的耗时过长,影响应用的启动时长
@GetTime(tag = "MyApplication.onCreate() 耗时") @Override public void onCreate() { super.onCreate(); ProcessLifecycleOwner.get().getLifecycle().addObserver(new ApplicationLifecycleObserver()); MMKV.initialize(this); GreenDaoHelper.getInstance().init(getApplicationContext()); ARouter.init(MyApplication.this); // Application的onCreate方法中开启卡顿监控 BlockCanary.install(this, new AppBlockCanaryContext()).start(); //Application的onCreate方法中初始化ANR-WatchDog new ANRWatchDog().start(); NetworkListener.getInstance().init(this); }反射
- 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;
- 反射就是把java类中的各种成分映射成一个个的Java对象;
- 反射有两个作用:1.反编译:.class->.java; 2.通过反射机制访问java对象中的属性,方法,构造器等;
- 实现反射,实际上是得到Class对象
java.lang.Class //类的创建 java.lang.reflect.Constructor //反射构造方法 java.lang.reflect.Field //反射属性 java.lang.reflect.Method //反射方法 java.lang.reflect.Modifier //访问修饰符的信息获取类(Class)对象
- 三种获取Class对象的方法
//1.会让ClassLoader装载类,并进行类的初始化 val c1 = Class.forName("com.ljy.demo.Person") //2.会让ClassLoader装载类,不进行类的初始化 *** 作 val c2 = Person::class.java //3.在实例中获取,返回类对象运行时真正所指的对象 val c3 = Person("bob").javaClass反射构造方法
- 无参数创建对象
val person = c1.newInstance() as Person LjyLogUtil.d(person.toString()) //new Person()是直接创建一个实列,同时完成类的装载和连接 //newInstance是使用类加载机制,可以灵活的创建类的实例,更换类的时候无需修改之前的代码
- 有参数的创建对象: 使用Constructor
val constructor = c1.getConstructor(String::class.java) val person2 = constructor.newInstance("Emily") as Person LjyLogUtil.d(person2.toString())
- 遍历构造器
for (it in c1.declaredConstructors) { LjyLogUtil.d("declaredConstructors.it:") for (i in it.parameterTypes) { LjyLogUtil.d("it.parameterTypes.i:${i.name}") } }反射属性
- 获取属性(Field)
val fieldAge = c1.getDeclaredField("age") val fieldName = c1.getDeclaredField("name")
- 遍历属性
for (it in c1.declaredFields) { LjyLogUtil.d("declaredFields.it:${it.name}") }
- 修改属性
//取消封装,特别是取消私有字段的访问限制,并不是将方法的权限设置为public,而是取消java的权限控制检查, // 所以即使是public方法,其isAccessible默认也是false fieldAge.isAccessible = true fieldName.isAccessible = true fieldAge.set(person2, 18) LjyLogUtil.d("fieldAge.name=${fieldAge.name}")反射方法
- 获取方法
val method = c1.getDeclaredMethod("setAge", Int::class.java)//获取类中的方法 method.invoke(person2, 22)//通过反射调用方法 LjyLogUtil.d(person2.toString())
- 遍历方法
for (it in c1.declaredMethods) { LjyLogUtil.d("declaredMethods.it:${it.name}") }反射静态方法
val clz = Class.forName("com.ljy.publicdemo.util.LjyLogUtil") val m = clz.getDeclaredMethod("d", CharSequence::class.java) m.invoke(LjyLogUtil::class.java, "log.d")反射泛型参数方法
//class Test访问修饰符的信息{ // public void test(T t){ // LjyLogUtil.d("Test.test(),t:"+t); // } //} val clz2 = Class.forName("com.ljy.publicdemo.activity.Test") //注意这里有个泛型的基础--泛型擦除,编译器会自动类型向上转型,T向上转型是Object,所以下面第二个参数是Object.class val tm = clz2.getDeclaredMethod("test", Object::class.java) tm.isAccessible = true tm.invoke(Test (), 666)
val modifierField = Modifier.toString(fieldName.modifiers) val modifierMethod = Modifier.toString(method.modifiers) LjyLogUtil.d("modifierField=$modifierField, modifierMethod=$modifierMethod")实践:通过反射获取运行时注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface GetTime { String tag() default ""; } public class Test { @GetTime(tag = "做啥子") private static void doSomething(String info) { //todo... } public static void main(String[] args) { Test test = new Test(); GetTime getTime ; Class clazz = test.getClass(); try { Method method = clazz.getMethod("doSomething", String.class); getTime = method.getAnnotation(GetTime.class); System.out.print("needLogin:"+getTime.tag()); } catch (NoSuchMethodException e) { System.out.print("NoSuchMethod"); } } }实践:通过反射越过泛型检查
- 泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
public static void main(String[] args) throws Exception{ ArrayList实践:通过反射运行配置文件内容strList = new ArrayList<>(); strList.add("aaa"); strList.add("bbb"); // strList.add(100); //获取ArrayList的Class对象,反向的调用add()方法,添加数据 Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象 //获取add()方法 Method m = listClass.getMethod("add", Object.class); //调用add()方法 m.invoke(strList, 100); //遍历集合 for(Object obj : strList){ System.out.println(obj); } }
- 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改,我们只需要将新类发送给客户端,并修改配置文件即可
//student类: public class Student { public void show(){ System.out.println("is show()"); } } // 配置文件以txt文件为例子(pro.txt): className = cm.ljy.Student methodName = show //测试类 public class Demo { public static void main(String[] args) throws Exception { //通过反射获取Class对象 Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student" //2获取show()方法 Method m = stuClass.getMethod(getValue("methodName"));//show //3.调用show()方法 m.invoke(stuClass.getConstructor().newInstance()); } //此方法接收一个key,在配置文件中获取相应的value public static String getValue(String key) throws IOException{ Properties pro = new Properties();//获取配置文件的对象 FileReader in = new FileReader("pro.txt");//获取输入流 pro.load(in);//将流加载到配置文件对象中 in.close(); return pro.getProperty(key);//返回根据key获取的value值 } }实践:动态代理
- java的反射机制提供了动态代理模式实现,代理模式的作用是为其他对象提供一种代理,以控制对这个对象的访问
interface Subject { fun doSomething() } class Test : Subject { override fun doSomething() { LjyLogUtil.d("Test.doSomething") } } class DynamicProxy(private val target: Subject) : InvocationHandler { override fun invoke(proxy: Any?, method: Method?, args: Array实践:动态创建fragment?): Any? { LjyLogUtil.d("Proxy:${proxy?.javaClass?.name}") LjyLogUtil.d("before target") //Kotlin中数组转为可变长参数,通过前面加*符号 val invoke = method!!.invoke(target, *(args ?: emptyArray())) LjyLogUtil.d("after target") return invoke } } //使用: val test = Test() val myProxy = DynamicProxy(test) val subject: Subject = Proxy.newProxyInstance( test.javaClass.classLoader, test.javaClass.interfaces, myProxy ) as Subject subject.doSomething() LjyLogUtil.d("subject.className:" + subject.javaClass.name)
//管理fragment标题及路径的类 public class PageConfig { public static List反射简化: jOORpageTitles = new ArrayList (); public static List getPageTitles(Context context) { pageTitles.clear(); pageTitles.add("Fragment1"); pageTitles.add("Fragment2"); pageTitles.add("Fragment3"); return pageTitles; } private static final String PATH_FRAGMENT1 = "com.ljy.publicdemo.activity.fragment.Fragment1"; private static final String PATH_FRAGMENT2 = "com.ljy.publicdemo.activity.fragment.Fragment2"; private static final String PATH_FRAGMENT3 = "com.ljy.publicdemo.activity.fragment.Fragment3"; public static String[] fragmentNames = { PATH_FRAGMENT1, PATH_FRAGMENT2, PATH_FRAGMENT3, }; } //通过反射遍历添加 class FragmentActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_frag) try { //遍历Fragment地址 for (address in PageConfig.fragmentNames) { //反射获得Class val clazz = Class.forName(address) //创建类 val fragment = clazz.newInstance() as Fragment //添加到viewPagerAdapter的资源 supportFragmentManager .beginTransaction() .add(R.id.frame_layout, fragment) .commit() } } catch (e: ClassNotFoundException) { } catch (e: IllegalAccessException) { } catch (e: InstantiationException) { } } }
- 当预期工程非常多的使用到反射时,我们需要更加简化的工具来优化开发流程,一个很棒的反射框架jOOR,非常轻量,让代码更加优雅
- 项目地址 https://github.com/jOOQ/jOOR
//1. 添加依赖 implementation 'org.jooq:joor:0.9.13' //2. 使用 val helloStr: String? = Reflect.onClass("java.lang.String")//类似于Class.forName .create("Hello jOOR")//调用类中构造方法 .call("substring", 8)//调用类中方法 .call("toString") .get()//获取包装好的对象 LjyLogUtil.d("helloStr=$helloStr") //3. 也支持动态代理 Reflect.onClass(RealSubject::class.java).create().`as`(Subject::class.java).doSomething()注意
- 由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射;
- jvm无法对反射部分的代码进行优化,反射生成大量的临时对象,造成大量GC,导致UI卡顿;
- 另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题;
- ButterKnife使用还是非常简单的,示例如下
class ButterKnifeActivity : AppCompatActivity() { @BindView(R.id.et_name) lateinit var etName: EditText @BindView(R.id.et_pwd) lateinit var etPwd: EditText @BindString(R.string.login_error) lateinit var loginErrorMessage: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_butter_knife) ButterKnife.bind(this) } @onClick(R.id.btn_submit) fun submit() { LjyLogUtil.d("etName=${etName.text}") LjyLogUtil.d("etPwd=${etPwd.text}") if ("ljy".equals(etName.text) && "123".equals(etPwd.text)) { LjyLogUtil.d("登录成功") } else { LjyLogUtil.d(loginErrorMessage) } } }ButterKnife源码解析
- 主要有下面两步:
- 编译的时候通过APT扫描注解,并进行相应处理,通过 javapoet库生成Java代码;
- 调研ButterKnife.bind(this)方法时通过反射将id与相应的上下文绑定;
- ButterKnife的APT注解解析类是ButterKnifeProcessor,继承了AbstractProcessor,我们先来看看他是如何实现的
- 重写init方法初始化工具类, 注意该方法添加了synchronized关键字
@Override public synchronized void init(ProcessingEnvironment env) { super.init(env); String sdk = env.getOptions().get(OPTION_SDK_INT); 。。。 debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE)); typeUtils = env.getTypeUtils(); filer = env.getFiler(); 。。。 }getSupportedAnnotationTypes方法
- 该方法返回支持的注解类型,用于指定定义的注解
@Override public Setprocess方法getSupportedAnnotationTypes() { Set types = new linkedHashSet<>(); for (Class extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } //指定定义的注解,注册了一系列的Bindxxx注解类和监听列表LISTENERS private Set > getSupportedAnnotations() { Set > annotations = new linkedHashSet<>(); annotations.add(BindAnim.class); annotations.add(BindArray.class); annotations.add(BindBitmap.class); annotations.add(BindBool.class); annotations.add(BindColor.class); annotations.add(BindDimen.class); annotations.add(BindDrawable.class); annotations.add(BindFloat.class); annotations.add(BindFont.class); annotations.add(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; } private static final List > LISTENERS = Arrays.asList( OnCheckedChanged.class, // OnClick.class, // OnEditorAction.class, // OnFocusChange.class, // OnItemClick.class, // OnItemLongClick.class, // OnItemSelected.class, // OnLongClick.class, // OnPageChange.class, // OnTextChanged.class, // OnTouch.class // );
- 最核心的方法, 注解的处理和生成代码都是在这个方法中完成
@Override public boolean process(Set extends TypeElement> elements, RoundEnvironment env) { //查询并解析注解,保存到bindingMap中 MapfindAndParseTargets方法bindingMap = findAndParseTargets(env); for (Map.Entry entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { //通过javaFile.writeTo(filer)生成java源文件 javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; }
- 上面的process第一行就调用了findAndParseTargets方法,他主要是用来扫描注解,并将注解信息保存到map
private MapBindingSet.brewJava方法findAndParseTargets(RoundEnvironment env) { Map builderMap = new linkedHashMap<>(); Set erasedTargetNames = new linkedHashSet<>(); // 扫描@BindAnim注解并调用parseResourceAnimation方法解析处理,保存到上面两个集合中 for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceAnimation(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindAnim.class, e); } } 。。。//省略了其他的一系列注解扫描 //同样的扫描并解析监听注解,保存到集合中 for (Class extends Annotation> listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } Map classpathBindings = findAllSupertypeBindings(builderMap, erasedTargetNames); // 创建一个双向队列entries,放入builderMap.entrySet() Deque > entries = new ArrayDeque<>(builderMap.entrySet()); // Map bindingMap = new linkedHashMap<>(); //遍历entries中每个 while (!entries.isEmpty()) { Map.Entry entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet()); //判断是否有父类 if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingInformationProvider parentBinding = bindingMap.get(parentType); if (parentBinding == null) { parentBinding = classpathBindings.get(parentType); } if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // Has a superclass binding but we haven't built it yet. Re-enqueue for later. entries.addLast(entry); } } } return bindingMap; }
- 再回到process方法,接下来的循环中,通过brewJava返回javapoet的JavaFile对象,用来生成java文件,brewJava代码如下
JavaFile brewJava(int sdk, boolean debuggable) { TypeSpec bindingConfiguration = createType(sdk, debuggable); //通过javapoet的builder构造器将上面得到的bindingConfiguration对象构建生成一个JavaFile对象 return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); }BindingSet.createType方法
- createType中使用javapoet库生成TypeSpec对象,里面保存各种的绑定配置信息
private TypeSpec createType(int sdk, boolean debuggable) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC) .addOriginatingElement(enclosingElement); if (isFinal) { result.addModifiers(FINAL); } if (parentBinding != null) { result.superclass(parentBinding.getBindingClassName()); } else { result.addSuperinterface(UNBINDER); } if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } if (isView) { result.addMethod(createBindingConstructorForView()); } else if (isActivity) { result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog()); } if (!constructorNeedsView()) { result.addMethod(createBindingViewDelegateConstructor()); } result.addMethod(createBindingConstructor(sdk, debuggable)); if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); }ButterKnife类:通过反射调用APT生成的java文件的方法,完成绑定 1. bind
- 代码如下:
@NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { //sourceView:当前界面的顶级父View View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); } @NonNull @UiThread public static Unbinder bind(@NonNull View target) { return bind(target, target); } @NonNull @UiThread public static Unbinder bind(@NonNull Dialog target) { View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); }2. createBinding
- 可以看到单参的bind有几个重载方法,但最终都是调用了两个参数的bind方法,其实这个方法早期版本的名字可能更容易理解:createBinding,代码如下:
public static Unbinder bind(@NonNull Object target, @NonNull View source) { Class> targetClass = target.getClass(); //这里调用findBindingConstructorForClass获取APT生成文件的构造器 Constructor extends Unbinder> constructor = findBindingConstructorForClass(targetClass); //判空 if (constructor == null) { return Unbinder.EMPTY; } //通过反射创建其对象,也就是createBinding try { return constructor.newInstance(target, source); } catch 。。。//省略异常处理代码 }3. findBindingConstructorForClass
- 再看一下上面代码中调用的findBindingConstructorForClass方法,代码如下:
static final Map番外:使用APT自定义注解处理器, Constructor extends Unbinder>> BINDINGS = new linkedHashMap<>(); @Nullable @CheckResult @UiThread private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) { //1. 先判断binding map中有没有缓存过cls,有则直接取用 Constructor extends Unbinder> bindingCtor = BINDINGS.get(cls); if (bindingCtor != null || BINDINGS.containsKey(cls)) { return bindingCtor; } //2. 判断类路径是否以android.或androidx.或java.开头,是则返回null String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.") || clsName.startsWith("androidx.")) { return null; } try { //使用classLoader加载cls对应的绑定类(APT生成的),获取其class对象 Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); //通过反射调用其构造器创建绑定类的对象 bindingCtor = (Constructor extends Unbinder>) bindingClass.getConstructor(cls, View.class); } catch (ClassNotFoundException e) { //没找到则递归调用找其父类的 bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { //没有找到构造函数则抛出异常 throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } BINDINGS.put(cls, bindingCtor); return bindingCtor; }
- 即Annotation Processing Tool,编译时注解处理器, APT可以用来在编译时扫描和处理注解
- APT技术被广泛的运用在Java框架中,ButterKnife,EventBus,Dagger2以及ARouter等都运用到APT;
- 新版的ButterKnife是使用APT编译时注解处理器在编译时扫描注解,并做相应处理,生成java代码,生成Java代码是调用javapoet库生成的;
- 那么让我们尝试使用APT实现一个最简版的ButterKnife吧
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } sourceCompatibility = "1.8" targetCompatibility = "1.8"
- 如果创建Android Library模块会发现不能找到AbstractProcessor这个类,这是因为Android平台是基于OpenJDK的,
而OpenJDK中不包含APT的相关代码。因此,在使用APT时,必须在Java Library中进行,如果不知道如何创建,直接修改为上面的配置即可;
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) //AutoService是Google开发的一个库,用来生成meta-INF/services/javax.annotation.processing.Processor文件 implementation 'com.google.auto.service:auto-service:1.0-rc6' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' //square的自动生成代码库 implementation 'com.squareup:javapoet:1.11.1' implementation project(path: ':annotation', configuration:'default') } sourceCompatibility = "1.8" targetCompatibility = "1.8"3. 将前两部创建的module添加到主工程app module下
//导入自定义注解 implementation project(path: ':annotation') // 指定自定义注释处理器 ,如果使用kotlin, replace annotationProcessor with kapt kapt project(path: ':processor')4. 自定义注解
@Target(ElementType.FIELD) // 作用于成员属性上 @Retention(RetentionPolicy.CLASS) public @interface BindView { int value(); }5. Element元素
- Java中Element是一个接口,表示一个程序元素,它可以指代包、类、方法或者一个变量
PackageElement: 表示一个包程序元素。提供对有关包及其成员的信息的访问。 ExecutableElement: 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。 TypeElement: 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。 VariableElement: 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。
- 自定义一个ElementInfo类,用于存储解析Element元素的信息
internal class ElementInfo( var packageName: String, var className: String, var typeName: String, var nodeName: String, var value: Int )6. 自定义注解处理器,需继承AbstractProcessor
@AutoService(Processor.class) public class MyProcessor extends AbstractProcessor { private Types mTypeUtils; private Messager mMessagerUtils; private Filer mFilerUtils; private Elements mElementUtils; private Map> mCache = new HashMap<>(); @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); //类信息工具类 mTypeUtils = processingEnv.getTypeUtils(); // Messager可以用来打印错误信息; mMessagerUtils = processingEnv.getMessager(); // Filer可以用来编写新文件; mFilerUtils = processingEnv.getFiler(); // Elements是一个可以处理Element的工具类。 mElementUtils = processingEnv.getElementUtils(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); // return super.getSupportedSourceVersion(); } @Override public Set getSupportedAnnotationTypes() { Set types = new linkedHashSet<>(); for (Class extends Annotation> annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; // return super.getSupportedAnnotationTypes(); } private Set > getSupportedAnnotations() { Set > annotations = new linkedHashSet<>(); annotations.add(LjyBindView.class); annotations.add(ClickGap.class); annotations.add(GetTime.class); annotations.add(NeedLogin.class); return annotations; } @Override public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) { if (annotations == null || annotations.isEmpty()) { return false; } //扫描所有被@GetTime注解的元素 Set extends Element> elements = roundEnv.getElementsAnnotatedWith(LjyBindView.class); if (elements == null || elements.isEmpty()) { return false; } //遍历被@GetTime注解的元素 for (Element element : elements) { if (element.getKind() != ElementKind.FIELD) { throw new RuntimeException("only classes can be annotated with " + element.getSimpleName()); } // 获取节点包信息 String packageName = mElementUtils.getPackageOf(element).getQualifiedName().toString(); // 获取节点类信息,由于 @NeedLogin 作用方法上,所以这里使用 getEnclosingElement() 获取父节点信息 String className = element.getEnclosingElement().getSimpleName().toString(); // 获取节点类型 String typeName = element.asType().toString(); // 获取节点标记的属性名称 String nodeName = element.getSimpleName().toString(); // 获取注解的值 int value = element.getAnnotation(LjyBindView.class).value(); // 打印 mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "packageName:" + packageName); mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "className:" + className); mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "typeName:" + typeName); mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "nodeName:" + nodeName); mMessagerUtils.printMessage(Diagnostic.Kind.NOTE, "value:" + value); // 缓存KEY String key = packageName + "." + className; // 缓存节点信息 List nodeInfos = mCache.get(key); if (nodeInfos == null) { nodeInfos = new ArrayList<>(); nodeInfos.add(new ElementInfo(packageName, className, typeName, nodeName, value)); // 缓存 mCache.put(key, nodeInfos); } else { nodeInfos.add(new ElementInfo(packageName, className, typeName, nodeName, value)); } } // 判断临时缓存是否不为空 if (!mCache.isEmpty()) { // 遍历临时缓存文件 for (Map.Entry > stringListEntry : mCache.entrySet()) { try { // 创建文件 createFile(stringListEntry.getValue()); } catch (Exception e) { e.printStackTrace(); } } } return false; } private void createFile(List infos) throws IOException { ElementInfo info = infos.get(0); // 生成的文件名(类名) String className = info.getClassName() + "__ViewBinding"; // 方法参数 ParameterSpec parameterSpec = ParameterSpec.builder( ClassName.get(info.getPackageName(), info.getClassName()), "target") .build(); // 方法 MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(parameterSpec) .returns(void.class); // 给方法添加代码块 for (ElementInfo nodeInfo : infos) { // target.textView = (TextView) target.findViewByID(R.id.text_view); methodSpecBuilder.addStatement("target.$L = ($L)target.findViewById($L)", nodeInfo.getNodeName(), nodeInfo.getTypeName(), nodeInfo.getValue()); } // 类 TypeSpec typeSpec = TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC) .addMethod(methodSpecBuilder.build()) .build(); // 生成文件 JavaFile.builder(info.getPackageName(), typeSpec) .build() .writeTo(mFilerUtils); } }
- reBuild项目之后,就可以|在项目的appbuildgeneratedsourcekaptdebug包名 路径下找到自动生成的文件ButterKnifeActivity__ViewBinding,其内容如下:
public class ButterKnifeActivity__ViewBinding { public static void bind(ButterKnifeActivity target) { target.etName = (android.widget.EditText)target.findViewById(2131296530); } }7. 自定义LjyButterKnife类
- 然后就可以仿照ButterKnife在App模块下新建一个LjyButterKnife类,利用反射来调用上面方法
public class LjyButterKnife { public static void bind(Activity target) { try { Class> clazz = target.getClass(); // 反射获取apt生成的指定类 Class> bindViewClass = Class.forName(clazz.getName() + "__ViewBinding"); // 获取它的方法 Method method = bindViewClass.getMethod("bind", clazz); // 执行方法 method.invoke(bindViewClass.newInstance(), target); } catch (Exception e) { e.printStackTrace(); } } }8. 在Activity中使用
- 可以看到我是和ButterKnife同时使用的,也不会有任何冲突问题
class ButterKnifeActivity : AppCompatActivity() { @LjyBindView(R.id.et_name) lateinit var etName: EditText @BindView(R.id.et_pwd) lateinit var etPwd: EditText @BindString(R.string.login_error) lateinit var loginErrorMessage: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_butter_knife) ButterKnife.bind(this) LjyButterKnife.bind(this) } @onClick(R.id.btn_submit) fun submit() { LjyLogUtil.d("etName=${etName.text}") LjyLogUtil.d("etPwd=${etPwd.text}") if ("ljy".equals(etName.text) && "123".equals(etPwd.text)) { LjyLogUtil.d("登录成功") } else { LjyLogUtil.d(loginErrorMessage) } } }参考
- JakeWharton/ButterKnife源码
- ButterKnife源码全面解析
- ButterKnife原理解析
- ButterKnife源码分析
- Java编译期注解处理器APT
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)