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 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 手写ButterKnife -> 正文阅读

[移动开发]手写ButterKnife

一、原理简介
ButterKnife框架原理的是采用APT编译时技术,主要运用到注解+注解处理器的方式动态地为添加了BindView等注解的成员或方法生成类文件,开发者无需自己手写findViewById等等重复的代码,简化了开发者的工作量。

二、手写ButterKnife
想要完全理解ButterKnife底层的APT技术,手写实现ButterKnife可以帮助更好地吸收这种技术。

2.1准备工作
(1)创建Android工程,并且在此项目中新建一个java Module取名为annotataion,用于存放注解。
注意:必须新建java library而不能是Android lib,为什么只能新建java工程,不能是Android工程后面讲
了解了ButterKnife的原理后,今天我就带领大家手写一个简易的ButterKnife。因为ButterKnife使用到了编译时注解+反射,所以在手写项目之前,小伙伴们需要先熟悉下注解(Annotation)、反射和AbstractProcessor相关的知识。

话不多说,我们新建一个项目,命名为ButterknifeViewBind。这里先梳理下我们的逻辑,首先我们需要新建一个annotation库,专门用来存放我们的注解代码,其次还需要创建一个compiler库,用来放置编译的相关逻辑,最后我们还需要新建一个util工具库,提供注册的入口,供我们的业务使用。整个项目的目录结构如下:
工程结构
首先看下butterknife-annotations库,很简单,我们只声明了BindView这个注解类,相关代码如下:

package com.demo.butterknife_annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
好了,注解有了,现在我们需要做的就是针对我们特定的注解进行处理,这个时候我们的ButterKnifeProcessor就登场了:

@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {

private static final ClassName UI_THREAD =
        ClassName.get("androidx.annotation", "UiThread");
Map<TypeElement, List<FieldBinding>> map = new HashMap<>();

private Types mTypeUtils;
private Elements mElementUtils;
private Filer mFiler;
private Messager mMessager;

@Override
public synchronized void init(ProcessingEnvironment env) {
    super.init(env);

    //初始化我们需要的基础工具
    mTypeUtils = env.getTypeUtils();
    mElementUtils = env.getElementUtils();
    mFiler = env.getFiler();
    mMessager = env.getMessager();
}

@Override
public SourceVersion getSupportedSourceVersion() {
    //支持的Java版本
    return SourceVersion.latestSupported();
}

@Override
public Set<String> getSupportedAnnotationTypes() {
    //支持的注解
    Set<String> annotations = new LinkedHashSet<>();
    annotations.add(BindView.class.getCanonicalName());
    return annotations;
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

    //解析特定注解,生成Java文件
    for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
        if (annotatedElement.getKind() != ElementKind.FIELD) {
            error(annotatedElement, "Only field can be annotated with @%s",
                    BindView.class.getSimpleName());
            return true;
        }

        analysisAnnotated(annotatedElement);
    }

    for (Map.Entry<TypeElement, List<FieldBinding>> item :
            map.entrySet()) {

        TypeElement enclosingElement = item.getKey();
        String packageName = ((PackageElement) enclosingElement.getEnclosingElement()).getQualifiedName().toString();
        String className = enclosingElement.getSimpleName().toString();
        ClassName originClassName = ClassName.get(packageName, className);
        ClassName bindingClassName = ClassName.get(packageName, className + "_ViewBinding");

        //System.out.println("packageName -------------------" + packageName);
        //System.out.println("className -------------------" + className);
        //System.out.println("originClassName -------------------" + originClassName.toString());
        //System.out.println("bindingClassName -------------------" + bindingClassName.toString());

        //构造方法中拼接代码
        CodeBlock.Builder codeBuilder = CodeBlock.builder();
        for (FieldBinding fieldBinding : item.getValue()) {
            codeBuilder.addStatement("target.$L=($T)target.findViewById($L)", fieldBinding.getFieldName(),
                    fieldBinding.getType(), fieldBinding.getFieldId());
        }

        //创建构造方法
        MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(UI_THREAD)
                .addParameter(originClassName, "target")
                .addCode(codeBuilder.build());

        //创建类
        TypeSpec typeSpec = TypeSpec.classBuilder(bindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(constructor.build())
                .build();

        try {
            //生成java文件
            JavaFile.builder(packageName, typeSpec).build().writeTo(mFiler);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
    return false;
}

private void error(Element e, String msg, Object... args) {
    mMessager.printMessage(
            Diagnostic.Kind.ERROR,
            String.format(msg, args),
            e);
}

private void analysisAnnotated(Element element) {
    TypeElement activityElement = (TypeElement) element.getEnclosingElement();
    List<FieldBinding> activityBinding = map.get(activityElement);
    if (activityBinding == null) {
        activityBinding = new ArrayList<>();
        map.put(activityElement, activityBinding);
    }

    // Verify that the target type extends from View.
    TypeMirror elementType = element.asType();
    TypeName type = TypeName.get(elementType);
    int fieldId = element.getAnnotation(BindView.class).value();
    String fieldName = element.getSimpleName().toString();

    FieldBinding fieldBinding = new FieldBinding(fieldId, fieldName, type);
    activityBinding.add(fieldBinding);
}

}
可以看到ButterKnifeProcessor使用了@AutoService注解,这个是用来做什么的呢?AutoService是Google开发的注解处理器,用来生成 META-INF/services/javax.annotation.processing.Processor 文件的,简化了我们创建注解处理器的流程。

AutoService的使用方式很简单,第一步,在我们butterknife_compiler库的buid.gradle文件中添加相关依赖:

implementation ‘com.google.auto.service:auto-service:1.0-rc3’
annotationProcessor ‘com.google.auto.service:auto-service:1.0-rc3’
接着在我们创建的注解处理器上添加@AutoService(Processor.class)注解就OK了,有没有很简单哈哈。

我们接着看下ButterKnifeProcessor的process方法,该方法在项目中的作用就是解析@ BindView注解,生成“ _ViewBinding.java”文件。在生成“ _ViewBinding.java”文件的过程中,使用到了JavaPoet开源库,what? 这又是什么?其实JavaPoet 就是用来辅助我们生成 Java 代码的一个 Java Library。同样我们需要在buid.gradle文件中添加依赖:

implementation ‘com.squareup:javapoet:1.8.0’
对JavaPoet 的API操作不太了解的童鞋可以查下相关博客,笔者这里也是一边查博客一边写的,一把辛酸泪啊~

好了到现在为止ButterKnifeProcessor也被我们搞定了,接下来就只剩下util工具库,提供注册的入口,这个就easy多了,只涉及到反射操作,我们首先新建butterknifeinject library,接着创建ButterknifeInject类,该类只有一个bind方法,代码如下:

package com.demo.butterknifeinject;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public final class ButterknifeInject {

public static void bind(Object target) {

    try {
        String clsName = target.getClass().getName();
        Class<?> bindingClass = Class.forName(clsName + "_ViewBinding");
        try {
            Constructor constructor = bindingClass.getConstructor(target.getClass());
            try {
                System.out.println("constructor-----------newInstance- before");
                constructor.newInstance(target);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

}
大功告成,最后我们测试一下。在app的build.gradle文件中添加我们的library依赖:

annotationProcessor project(‘:butterknife-compiler’)
implementation project(‘:butterknife-annotations’)
implementation project(‘:butterknifeInject’)
ok,在MainActivity中我们现在就可以这么写啦:

public class MainActivity extends AppCompatActivity {

@BindView(R.id.tv_test)
TextView mTvTest;

@BindView(R.id.btn_test)
Button mBtnTest;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ButterknifeInject.bind(this);

    initData();
}

private void initData(){
    mBtnTest.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            mTvTest.setText("哈哈哈哈哈嗝");
        }
    });
}

}
最后我们run一把,一切正常,不出意外的话我们的“ _ViewBinding.java”文件已经生成了,我们看下:
1577184824451.png

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

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/30 4:16:49-

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