注解全面解析#注解使用案例#手写Butterknife依赖注入架构

手写butterKnife深入理解注解框架

写在博客前面的有用的废话:为什么会写这篇博客,很多人会问,ButterKnife很简单啊,我会用啊,你写这有毛用,我用第三方的贼6,谁还自己写这玩意儿啊。。。。是啊,第三方的东西很nb,但是如果我们只停留在简单的会用层次上,我们永远只能Ctrl + C + V 写代码,如此一来我们就很难成为那些提供那些造轮子的人。所以我建议大家不止要把轮子咕噜起来,还要了解轮子的内部构造,了解他是怎么咕噜起来的,终有一天,我们也要成为一个能够造轮子的人,因此,这篇文章被蛋生了。
这篇文章包括:
** 在开始之前,让我们先了解一些概念

  1. 什么是注解
  2. 注解分类
  3. 注解使用案例**

什么是注解

我们先看一看官方API的解释:
	注解是一种元数据形式,提供程序本身以外的数据,对代码没有直接的影响。

what?相信第一次听到注解的猴子肯定一脸懵逼,通俗的来讲,注解其实就是一个标签,就是给我们的程序打标签。
比如系统给我们提供的一些常见注解:
@Override :表示当前方法覆盖超类中的方法。如果你所写的方法和超类中的方法签名不同的话,编译器会报错。主要用于检查。
@Deprecated:表明当前的元素已经不适用。当使用了注解为@Deprecated的元素时,编译器会报出警告。
@SuppressWarnings : 关闭不当的编译器警告。

如何自定义注解

元注解:
在了解自定义注解之前,我们先来了解一下元注解,元注解是用在注解上的注解,主要分为以下几种:
1.@Target
表明当前注解可以用在什么目标之上。
ElementType主要包括一下几种:

  • CONSTRUCTOR 构造器声明
  • FIELD 域声明(包括enum实例)
  • LOCAL_VARIABLE 局部变量声明
  • METHOD 方法声明
  • PACKAGE 包声明
  • PARAMETER 参数声明
  • TYPE 类、接口、注解类型、enum类型

2.@Retention
标识着什么时候处理这些注解,也标识着该注解的生命周期

  • SOURCE 源码级别,注解将被编译器丢弃,只存在源码中,其功能是与编译器交互,用于代码检测, 如@Override,@SuppressWarings,许多框架如Dragger就是使用这个级别的注解,这个级别的框架额外效率损耗发生在编译时。
  • CLASS 字节码级别,注解存在源码与字节码文件中,主要用于编译时生成而外的文件,如XML,Java文件等,这个级别需要添加JVM加载时候的代理(javaagent)。
  • RUNTIME 运行时级别,注解存在源码,字节码与Java虚拟机中,主要用于运行时反射获取相关信息,许多框架如OrmLite就是使用这个级别的注解,这个级别的框架额外的效率损耗发生在程序运行时。

@Documented 被修饰的注解会生成到javadoc中。

@Inherited 可以让注解类似被继承一样,但是这并不是真的继承。通过使用@Inherited,只可以让子类类对象使用getAnnotations()反射获取父类被@Inherited修饰的注解。

我们先来看一个简单的运行时注解案例,运用运行时注解实现view注入:
定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindV {
    int value();
}

使用注解

public class MainActivity extends AppCompatActivity {

    @BindV(R.id.button1)
    Button button1;
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterV.bind(this);
        button1.setText("我也改名字了,好开心");
    //此处省略1万行代码
    }

因为我们用的是运行时注解,所以需要在运行时去解析注解,那肯定大家都想到了,对,就是运用反射解析注解(这里用到了反射的基础,如果有同学对反射不是很了解,可以先去了解下反射):

public class ButterV {

    public static void bind(Activity activity) {
        //获取类
        Class<? extends Activity> clazz = activity.getClass();
        //获取成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //获取被注解的元素
            BindV bindV = field.getAnnotation(BindV.class);
            if (bindV != null) {
                //view id
                int resId = bindV.value();
                try {
                    //动态调用 activity的findViewById方法
                    Method findViewById = clazz.getMethod("findViewById", int.class);
                    Object view = findViewById.invoke(activity, resId);
                    //这里因为field.set()方法是私有的,不能直接赋值,需要先设置该属性的访问状态
                    field.setAccessible(true);
                    field.set(activity, view);
                    field.setAccessible(false);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

到此为止一个完整的运行时注解小案例就完结了,大家是不是感觉很简单,大家会有所以问,难道辣么NB的注解框架就是这么实现的?我擦?恭喜你,你问到点子上了,当然没这么简单。
由于运行时注解的解析需要在运行时通过反射来解析,而在程序中运用大量的反射会影响程序运行效率,试想一下,我们的代码量很大的时候,在运行时还要运用反射去遍历所有view,显然,这不是我们想要的框架。
那么有没有在运行时之前就把这些view注入完成的方法呢,可能有的同学想到了,对就是编译时注解。
编译时注解实现ButterKnife注解注入
在这里插入图片描述
在这里插入图片描述
直接上代码(这里编译时创建类 xxxx_ViewBinding.java,的工具是 javapoet ,大家点击可以先了解下,有助于下面代码理解)

butterKnife库的依赖,见上图
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.auto.service:auto-service:1.0-rc3'
    compile 'com.squareup:javapoet:1.8.0'
}
定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}


//定义unbinder
public interface Unbinder {
    @UiThread
    void unbind();
}

//用于调用findviewbyid
public class Utils {
    public static <T extends View> T findViewById(Activity activity, int resourceId) {
        return (T) activity.findViewById(resourceId);
    }
}

//编译时注解解析器
@AutoService(Processor.class)
public class ButterProcessor extends AbstractProcessor {

    //获取所有被某个注解的元素
    private Elements elementUtils;
    //创建一个文件,并返回这个对象,以便用户写入数据
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
    }

    /**
     * 解析编译注解
     *
     * @param annotations 注解(TypeElement 标识一个类或者接口程序元素,枚举类型是一个类,注解类型是一个接口)
     * @param roundEnv    注解处理工具框架将{@linkplain Processor#process提供带有对象的注解处理器实现此接口}以便处理器可以查询有关一轮注释处理的信息。
     * @return boolean
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //获取所有BindView注解修饰的Element集合
        Set<? extends Element> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(BindView.class);
        //将emements按照activity分类
        Map<Element, List<Element>> elementListMap = new LinkedHashMap<>();
        for (Element element : elementsAnnotatedWith) {
            //返回包裹此element的元素对象
            Element enclosingElement = element.getEnclosingElement();
            List<Element> bindViewElements = elementListMap.get(enclosingElement);
            if (bindViewElements == null) {
                bindViewElements = new ArrayList<>();
                elementListMap.put(enclosingElement, bindViewElements);
            }
            bindViewElements.add(element);
        }

        //以activity为单位便利Map
        for (Map.Entry<Element, List<Element>> entry : elementListMap.entrySet()) {
            Element enclosingElement = entry.getKey();
            List<Element> bindViewElements = entry.getValue();

            //TODO 编译时创建类 xxxx_ViewBinding类,在此类中。。。。、
            //获取activity ClassName
            String activityNameNameStr = enclosingElement.getSimpleName().toString();
            ClassName activityName = ClassName.bestGuess(activityNameNameStr);
            //获取接口Unbinder的ClassName,在编译时生成 xxxx_ViewBinding.java
            ClassName unbinder = ClassName.get("com.c.butter", "Unbinder");
            TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName + "_ViewBinding")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addSuperinterface(unbinder)
                    .addField(activityName, "mActivity", Modifier.PRIVATE);

            ClassName callSuper = ClassName.get("android.support.annotation", "CallSuper");
            MethodSpec.Builder unbinderMethod = MethodSpec.methodBuilder("unbind")
                    .addAnnotation(Override.class)
                    .addAnnotation(callSuper)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL);

            MethodSpec.Builder constructorMethod = MethodSpec.constructorBuilder()
                    .addParameter(activityName, "mActivity")
                    .addModifiers(Modifier.PUBLIC)
                    .addStatement("this.mActivity = mActivity");

            for (Element bindViewElement : bindViewElements) {
                String fieldName = bindViewElement.getSimpleName().toString();
                ClassName utils = ClassName.get("com.c.butter", "Utils");
                int resourceId = bindViewElement.getAnnotation(BindView.class).value();
                constructorMethod.addStatement("mActivity.$L = $T.findViewById(mActivity,$L)", fieldName, utils, resourceId);
                unbinderMethod.addStatement("mActivity.$L = null", fieldName);
            }

            classBuilder.addMethod(constructorMethod.build())
                    .addMethod(unbinderMethod.build());

            String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();

            try {
                JavaFile.builder(packageName, classBuilder.build())
                        .addFileComment("编译时生成的xxxx_ViewBinding文件")
                        .build().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

   @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

}

运用编译时注解,在编译时动态生成了xxxx_ViewBinding.java类,然后再用户调用bind(activity)方法时,去创建这个对象,而在这个对象的构造器中我们就已经对activity的所有view进行了绑定,这样避免了在运行时用反射遍历activity的所有Fields,大大提高了效率。
最后,我们浏览一下自定生成的类是什么样子的:

public final class MainActivity_ViewBinding implements Unbinder {
  private MainActivity target;

  public MainActivity_ViewBinding(MainActivity target) {
    this.target = target;
    target.button = Utils.findViewById(target,2131165218);
  }

  @Override
  @CallSuper
  public final void unbind() {
    target.button = null;
  }
}

好了,到这里这次的分享就结束了,感谢大家的浏览,如果有什么不足,还望不吝批评。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值