IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android Kotlin使用APT手写ButterKnife -> 正文阅读

[移动开发]Android Kotlin使用APT手写ButterKnife

前言

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已经被淘汰,但它其中的思想却是对我们开发有很大的帮助!

结语

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

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 11:21:31  更:2022-12-25 11:22:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 0:01:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码