Android Kotlin使用APT手写ButterKnife

news2024/11/25 7:15:34

前言

ButterKnife通过使用@BindView注解就可以完成findViewById工作,它的实现原理其实也很简单,通过APT(Annotation Processing Too,注解解析器)技术,在编译期为我们生成了一个绑定类,而从完成了View的绑定。

// ButterKnife编译时生成的绑定类
package com.crystal.essayjoker.activity;

import android.view.View;
import android.widget.Button;
import androidx.annotation.UiThread;
import butterknife.Unbinder;
import butterknife.internal.Utils;
import com.crystal.essayjoker.R;
import java.lang.IllegalStateException;
import java.lang.Override;

public final class CheckNetActivity_ViewBinding implements Unbinder {
  private CheckNetActivity target;

  @UiThread
  public CheckNetActivity_ViewBinding(CheckNetActivity target) {
    this(target, target.getWindow().getDecorView());
  }

  @UiThread
  public CheckNetActivity_ViewBinding(CheckNetActivity target, View source) {
    this.target = target;
    target.btn2 = Utils.findRequiredViewAsType(source, R.id.btn2, "field 'btn2'", Button.class);
  }

  @Override
  public void unbind() {
    CheckNetActivity target = this.target;
    if (target == null) throw new IllegalStateException("Bindings already cleared.");
    this.target = null;
    target.btn2 = null;
  }
}

那我们是不是可以模仿ButterKnife通过APT去自己实现一套@BindView呢?

具体实现

  • 首先,模仿ButterKnife定义三个模块:
    1. butterknife-annotations:Java模块,用于定义@BindView注解;
    2. butterkinfe-compiler:Java模块,用于解析@BindView注解生成Activity_ViewBinding类;
    3. butterknife:Android模块,用于定义UnBinder接口以及实例化Activity_ViewBinding类;
      他们的依赖关系如下:
      依赖关系
  • butterknife-annotations模块中,定义@BindView注解`
   @Retention(AnnotationRetention.BINARY) //保留到编译期
   @Target(AnnotationTarget.FIELD) //作用于属性
   annotation class BindView(val resId: Int)
  • butterknife模块中,添加FindViewById工具类、ButterKnife.bind()工具类以及Unbinder接口

Utils

object Utils {
    @JvmStatic
    fun <T : View> findViewById(target: Activity, viewId: Int): T {
        return target.findViewById(viewId)
    }
}

Unbinder接口

public interface Unbinder {
    void unbind();
    Unbinder EMPTY = () -> {
    };
}

ButterKnife

object ButterKnife {
    @JvmStatic
    fun bind(activity: Activity): Unbinder {
        val activityClass = Class.forName(activity::class.java.canonicalName + "_ViewBinding")
        val constructor = activityClass.getDeclaredConstructor(activity::class.java)
        return try {
            constructor.newInstance(activity) as Unbinder
        } catch (e: Exception) {
            e.printStackTrace()
            Unbinder.EMPTY
        }
    }
}
  • butterknife-compiler模块中,定义ButterKnifeProcessor类,处理@BindView注解;
    1. 使用JavaPoet来完成Java文件生成;
    2. 使用Kotlin,无法用AutoService来定义注解解析器,会导致编译不执行,使用的最原始定义javax.annotation.processing.Processor的方式;ButterKnifeProcessor描述文件
package com.crystal.butterkinfe_compiler

import com.crystal.butterknife_annotations.BindView
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.util.Elements


/**
 * 用于处理BindView注解
 * on 2022/12/15
 */
class ButterKnifeProcessor : AbstractProcessor() {
    private lateinit var filer: Filer
    private lateinit var elementUtils: Elements


    override fun init(processingEnv: ProcessingEnvironment) {
        super.init(processingEnv)
        filer = processingEnv.filer
        elementUtils = processingEnv.elementUtils
    }

    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        val set = mutableSetOf<String>()
        for (supportAnnotation in getSupportAnnotations()) {
            set.add(supportAnnotation.canonicalName)
        }
        return set
    }


    private fun getSupportAnnotations(): Set<Class<out Annotation>> {
        val annotations = linkedSetOf<Class<out Annotation>>()
        annotations.add(BindView::class.java)
        return annotations
    }

    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.latest()
    }


    override fun process(typeElement: MutableSet<out TypeElement>, env: RoundEnvironment): Boolean {
        // 调试打印
        System.out.println("------------------------------------>");
        System.out.println("------------------------------------>");
        val elements = env.getElementsAnnotatedWith(BindView::class.java)
        //解析属性对应规则  1个activity -> List<View>
        val elementsHashMap = linkedMapOf<Element, ArrayList<Element>>()
        for (element in elements) {
            val enclosingElement = element.enclosingElement //返回该元素对应的父元素,对应就是Activity
            var viewElementsList = elementsHashMap[enclosingElement]
            if (viewElementsList == null) {
                viewElementsList = arrayListOf()
                elementsHashMap[enclosingElement] = viewElementsList
            }
            viewElementsList.add(element)
        }
        //生成代码
        for (mutableEntry in elementsHashMap) {
            val enclosingElement = mutableEntry.key //对应Activity
            val viewElements = mutableEntry.value  //对应List<View>
            //1.生成类名 public class MainActivity_ViewBinding implements Unbinder
            val activityClassNameStr = enclosingElement.simpleName.toString()
            val activityClassName = ClassName.bestGuess(activityClassNameStr)
            //获取Unbinder ClassName
            val unBinderClassName = ClassName.get("com.crystal.butterknife", "Unbinder")
            val classBuilder = TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addSuperinterface(unBinderClassName)
                .addField(
                    activityClassName,
                    "target",
                    Modifier.PRIVATE
                ) //2.添加属性 private MainActivity target

            val uiThreadClassName = ClassName.get("androidx.annotation", "UiThread")
            //3.添加构造方法
            val constructorBuilder =
                MethodSpec.constructorBuilder().addParameter(activityClassName, "target")
                    .addStatement("this.target = target")
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(uiThreadClassName)

            //4.实现unbind方法
            val callSuperClassName = ClassName.get("androidx.annotation", "CallSuper")
            val unBindMethodBuilder = MethodSpec.methodBuilder("unbind")
                .addAnnotation(Override::class.java)
                .addAnnotation(callSuperClassName)
                .addModifiers(Modifier.PUBLIC)
            //添加具体语句
            //MainActivity target = this.target;
            //if (target == null) throw new IllegalStateException("Bindings already cleared.");
            // this.target = null;
            unBindMethodBuilder.addStatement("\$T target = this.target", activityClassName)
            unBindMethodBuilder.addStatement("if (target == null) throw new IllegalStateException(\"Bindings already cleared.\")")
            unBindMethodBuilder.addStatement("this.target = null")

            //5.构造方法中添加findViewById
            // unbind方法中添加    target.btn1 = null; target.btn2 = null;
            for (viewElement in viewElements) {
                val filedName = viewElement.simpleName.toString()
                val utilsClassName = ClassName.get("com.crystal.butterknife", "Utils")
                val viewId = viewElement.getAnnotation(BindView::class.java).resId
                constructorBuilder.addStatement(
                    "target.\$L = \$T.findViewById(target,\$L)",
                    filedName,
                    utilsClassName,
                    viewId
                )
                unBindMethodBuilder.addStatement("target.\$L = null", filedName)
            }

            classBuilder.addMethod(constructorBuilder.build())
            classBuilder.addMethod(unBindMethodBuilder.build())
            //生成类
            val packageName = elementUtils.getPackageOf(enclosingElement).qualifiedName.toString()
            JavaFile.builder(packageName, classBuilder.build())
                .addFileComment("Generated code from Butter Knife. Do not modify!")
                .build().writeTo(filer)

        }

        return false
    }

}

测试验证

  • 在Activity中使用自定义的@BindView注解并绑定Activity
class CheckNetActivity : AppCompatActivity() {
    @BindView(R.id.btn1)
    lateinit var btn1: Button
    @BindView(R.id.btn2)
    lateinit var btn2: Button
    private lateinit var unBind: Unbinder
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_check_net)
        unBind = ButterKnife.bind(this)
        btn1.setOnClickListener {

        }
    }
    override fun onDestroy() {
        super.onDestroy()
        unBind.unbind()
    }
}

对应生成的CheckNetActivity_ViewBinding类;
CheckNetActivity_ViewBinding类

总结

通过手写ButterKnife,不仅对ButterKnife的原理有了进一步的理解,同时也学习了APT技术,虽然ButterKnife已经被淘汰,但它其中的思想却是对我们开发有很大的帮助!

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenAI掌门人Sam Altman:AI的下一个发展阶段

来源&#xff5c;Greylock OneFlow社区编译 翻译&#xff5c;胡燕君、贾川 预告了一整年的GPT-4迟迟没来&#xff0c;人们猜想OpenAI是不是要跳票了&#xff0c;更何况他们之前的得意之作DALL-E也被开源Stable Diffusion打了个措手不及&#xff0c;再不来点深水炸弹业界地位危矣…

jsp+ssm计算机毕业设计ssm学生成绩考核管理系统【附源码】

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; JSPSSM mybatis Maven等等组成&#xff0c;B/S模式 Mave…

对 CSS 工程化的理解

CSS 工程化是为了解决以下问题&#xff1a; 宏观设计&#xff1a;CSS 代码如何组织、如何拆分、模块结构怎样设计&#xff1f;编码优化&#xff1a;怎样写出更好的 CSS&#xff1f;构建&#xff1a;如何处理我的 CSS&#xff0c;才能让它的打包结果最优&#xff1f;可维护性&a…

SQL基础——查询基础

查询基础前言整体思维导图列的查询语法2.1 基本的SELECT语句代码示例&#xff1a;从Product表中输出3列执行结果查询出所有的列语法2.2 查询全部的列代码示例2.2 输出Product表中全部的列执行结果为列设置别名代码示例2.3 为列设置别名执行结果代码示例2.4 设置中文别名执行结果…

Redis持久化没玩明白,你就会把缓存数据丢了

引言 我们都知道Redis是微服务架构中重要的基础数据库中间件&#xff0c;通过Redis可以将数据库中的数据缓存到内存中&#xff0c;当服务端有数据查询请求的时候&#xff0c;可以直接从内存中获取数据。如此&#xff0c;一方面服务端可以获得比较快的数据请求响应&#xff0c;…

透明质酸-嵌段共聚物 透明质酸修饰聚乳酸 HA-PLA/PCL/PLGA/PEI/PLL

品牌&#xff1a;为华生物 产地&#xff1a;广州 中文名称&#xff1a;透明质酸-聚乳酸 英文名称&#xff1a;HA-PLA 溶解度&#xff1a;溶于部分有机溶剂 保存时间&#xff1a;一年 PLA分子量&#xff1a;根据客户要求定制 HA分子量&#xff1a;3000--1500K 其他分子量可根据客…

鑫磊股份通过注册:年营收8.2亿 为钟仁志与蔡海红夫妻店

雷递网 雷建平 12月15日鑫磊压缩机股份有限公司&#xff08;简称&#xff1a;“鑫磊股份”&#xff09;日前通过注册&#xff0c;准备在深交所创业板上市。鑫磊股份计划募资4.53亿元&#xff0c;其中&#xff0c;1.97亿元用于新增年产3万台螺杆式空压机技改项目&#xff0c;1.0…

Python+Selenium使用Page Object实现页面自动化测试

&#x1f4cc; 博客主页&#xff1a; 程序员二黑 &#x1f4cc; 专注于软件测试领域相关技术实践和思考&#xff0c;持续分享自动化软件测试开发干货知识&#xff01; &#x1f4cc; 公号同名&#xff0c;欢迎加入我的测试交流群&#xff0c;我们一起交流学习&#xff01; 目录…

03解锁源码分析-ReentrantReadWriteLock原理-AQS-并发编程(Java)

文章目录1 读锁解锁1.1 tryReleaseShared()1.2 doReleaseShared()1.3 unparkSuccessor()1.4 示意图2 写锁解锁2.1 tryRelease()2.2 尝试解锁成功2.3 setHeadAndPropagate()5 后记1 读锁解锁 查看下读锁的解锁相关源代码&#xff1a; public void unlock() {sync.releaseShare…

【实时数仓】在Hbase建立维度表、保存维度数据到Hbase、保存业务数据到kafka主题

文章目录一 分流Sink之建立维度表到HBase(Phoenix)1 拼接建表语句&#xff08;1&#xff09;定义配置常量类&#xff08;2&#xff09;引入依赖&#xff08;3&#xff09;hbase-site.xml&#xff08;4&#xff09;在phoenix中执行&#xff08;5&#xff09;增加代码a TableProc…

Docker安装简单命令

一、 Docker是什么? 要了解Docker&#xff0c;首先要了解什么是容器&#xff1f; 容器是一个软件的轻量级独立可执行软件包&#xff0c;包含运行它所需的一切&#xff1a;代码&#xff0c;运行时&#xff0c;系统工具&#xff0c;系统库&#xff0c;设置。不管环境如何&…

C++(第十二篇):多态(虚函数、抽象类、虚函数表、虚表指针、多继承下的多态)

&#x1f4d2;博客主页&#xff1a;Morning_Yang丶 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4cc;本文所属专栏&#xff1a; 【C拒绝从入门到跑路】 &#x1f64f;作者水平有限&#xff0c;如果发现错误&#xff0c;敬请指正&…

13485-59-1,二肽Ala-Pro,H2N-AP-OH

Substrate for skin fibroblast prolidase.皮肤成纤维细胞prolida酶的底物。 编号: 199181中文名称: 二肽Ala-Pro英文名: Ala-ProCAS号: 13485-59-1单字母: H2N-AP-OH三字母: H2N-Ala-Pro-COOH氨基酸个数: 2分子式: C8H14N2O3平均分子量: 186.21精确分子量: 186.1等电点(PI): 6…

web前端期末大作业:红色主题中国文化网页设计与实现——基于HTML+CSS实现中国梦(20页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

电脑技巧:Win11系统新增的磁盘分区功能介绍

很多用户发现&#xff0c;新买的电脑中出厂自带的硬盘只有一个分区&#xff0c;目前只有少部分电脑机型出厂会有分多个分区的。 磁盘的单一分区往往不能满足我们的使用需要&#xff0c;Win7/8/10我们都知道可以在磁盘管理下通过压缩卷的方式来分区&#xff0c;Win11操作系统又新…

C# 程序的错误与调试

一 错误的zhongl 程序的错误通常可以分为三大类 ① 语法错误 ② 运行错误 ③ 逻辑错误 二 语法错误 1 常见的语法错误 ① 如括号不配对&#xff0c;多了或少了分号&#xff1b; ② 字母写错&#xff0c;变量未定义&#xff0c;控件命名写错&#xff1b; ③ 函数少了一个参数…

2022面试官常考的前端面试题

Ajax 它是一种异步通信的方法&#xff0c;通过直接由 js 脚本向服务器发起 http 通信&#xff0c;然后根据服务器返回的数据&#xff0c;更新网页的相应部分&#xff0c;而不用刷新整个页面的一种方法。 面试手写&#xff08;原生&#xff09;&#xff1a; //1&#xff1a;创建…

大一学生《Web编程基础》期末网页制作 HTML+CSS鲜花网页设计实例

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

NLP学习笔记(二) LSTM基本介绍

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲长短期记忆网络 (Long Short-Term Memory, LSTM) 文章行文思路如下&#xff1a; 首先通过循环神经网络引出为啥需要长短期记忆网络然后介绍长短期记忆网络的核心思想与运作方式最后通过简短的代码深入理解长短期记忆网络的…

Java面试题总结-hashcode和equals

前段时间有朋友问我&#xff1a;“你重写过 hashcode 和 equals 么&#xff0c;为什么重写 equals 时必须重写 hashCode 方法&#xff1f;” 之前的学习中有深入了解过&#xff0c;后来很久没复习了&#xff0c;淡忘许多&#xff0c;回答的时候也有很多地方卡壳&#xff0c;干脆…