Android注解、反射及其应用--轻量级ButterKnife实现

本文详细解析了Android开发中注解与反射的基本概念及应用,重点介绍了自定义注解和反射机制的结合,通过实例展示了如何实现一个轻量级的ButterKnife框架,用于自动绑定视图。

1. 背景

Android开发,少不了遇到注解,比如写一个Activity,基本都要用注解@Override。反射呢,一般用不到,但是如果想自己实现注解,就得用了。所以一般注解和反射,都放在一起讨论。

2. 注解

Annotation(注解)就是Java提供了一种源程序中的元素关联任何信息或者任何元数据(metadata)的途径和方法。
Annotation是被动的元数据,永远不会有主动行为

自定义一个注解,格式通常如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
@Documented
@Inherited
public @interface BindView {
    int value() default 1;
    boolean visiable() default false;
}

@interface 表明这是一个注解,它只有成员变量,没有方法。
Annotation的成员变量在Annotation定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。比如上面的value和visiable。

自定义注解里也会有注解存在,给自定义注解使用的注解就是元注解

@Rentention
@Rentention 用来标记自定义注解的有效范围,他的取值有以下三种:
RetentionPolicy.SOURCE: 只在源代码中保留 一般都是用来增加代码的理解性或者帮助代码检查之类的,比如我们的Override;
RetentionPolicy.CLASS: 默认的选择,能把注解保留到编译后的字节码class文件中,仅仅到字节码文件中,运行时是无法得到的;
RetentionPolicy.RUNTIME: ,注解不仅 能保留到class字节码文件中,还能在运行通过反射获取到,这也是我们最常用的。

@Target
@Target指定Annotation用于修饰哪些程序元素。
@Target也包含一个名为”value“的成员变量,该value成员变量类型为ElementType[ ],ElementType为枚举类型,值有如下几个:
ElementType.TYPE:能修饰类、接口或枚举类型
ElementType.FIELD:能修饰成员变量
ElementType.METHOD:能修饰方法
ElementType.PARAMETER:能修饰参数
ElementType.CONSTRUCTOR:能修饰构造器
ElementType.LOCAL_VARIABLE:能修饰局部变量
ElementType.ANNOTATION_TYPE:能修饰注解
ElementType.PACKAGE:能修饰包
使用了@Documented的可以在javadoc中找到
使用了@Interited表示注解里的内容可以被子类继承,比如父类中某个成员使用了上述@From(value),From中的value能给子类使用到。

3. 反射

Java反射机制是指在运行状态
对于任意一个类,都能知道这个类的所有属性和方法;
对于任何一个对象,都能够调用它的任何一个方法和属性;
这样动态获取新的以及动态调用对象方法的功能就叫做反射。

比如像下面:

//获取类
Class c = Class.forName("java.lang.String");
// 获取所有的属性
Field[] fields = c.getDeclaredFields();
StringBuffer sb = new StringBuffer();
sb.append(Modifier.toString(c.getModifiers()) + " class " + c.getSimpleName() + "{\n");
// 遍历每一个属性
for (Field field : fields) {
     sb.append("\t");// 空格
     sb.append(Modifier.toString(field.getModifiers()) + " ");// 获得属性的修饰符,例如public,static等等
     sb.append(field.getType().getSimpleName() + " ");// 属性的类型的名字
     sb.append(field.getName() + ";\n");// 属性的名字+回车
}
sb.append("}\n");
System.out.println(sb);

反射涉及的关键类:
java.lang.Class 编译后的class文件的对象
java.lang.reflect.Constructor 构造方法
java.lang.reflect.Field 类的成员变量(属性)
java.lang.reflect.Method 类的成员方法
java.lang.reflect.Modifier 判断方法类型
java.lang.annotation.Annotation类的注解

3.1 Class的常用方法

  1. 获取类的属性(成员变量)
    Field[] fields = c.getDeclaredFields();
    返回的是一个数组 ,包含所有的属性。每一个属性就是一个Filed
  2. 获取类的方法
    Method[] ms = c.getDeclaredMethods();
    和属性类似,我们依然可以通过一系列的方法获取到方法的返回值类型,名称以及参数。

4. 注解,反射的应用–轻量级ButterKnife实现

ButterKnife 是一个非常有名的开源包,主要用于帮你节省findViewById的工作。它主要是用注解与反射实现。如果不想引用它,我们完全可以通过几个类,自己实现一个。

4.1 定义BindView注解

package com.example.annotationtest;

import android.support.annotation.IdRes;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Bind a field to the view for the specified ID. The view will automatically be cast to the field
 * type.
 * <pre><code>
 * {@literal @}BindView(R.id.title) TextView title;
 * </code></pre>
 */
@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value(); //@IdRes 注解这个值为资源ID,如果不写,也可以
}

4.2 使用注解

package com.example.annotationtest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.text_view)//这里只是表示,textView有个注解,值为text_view
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);//这里才是关键,读取textView的注解,把值取出来,然后执行findViewById(R.id.text_view)
        textView.setText("CHENXF CHENXF");
    }
}

ButterKnife.bind(this) 是真正初始化textView的地方,这个是运行期间执行的。怎么实现呢

4.3 ButterKnife.bind(this) 实现

package com.example.annotationtest;

import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.util.Log;
import android.view.View;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.List;

import static java.lang.reflect.Modifier.PRIVATE;
import static java.lang.reflect.Modifier.PUBLIC;
import static java.lang.reflect.Modifier.STATIC;

public final class ButterKnife {
    private ButterKnife() {
        throw new AssertionError();
    }

    private static final String TAG = "ButterKnife";
    private static boolean debug = false;

    /**
     * Control whether debug logging is enabled.
     */
    public static void setDebug(boolean debug) {
        ButterKnife.debug = debug;
    }

    /**
     * BindView annotated fields and methods in the specified {@link Activity}. The current content
     * view is used as the view root.
     *
     * @param target Target activity for view binding.
     */
    @NonNull
    @UiThread
    public static Unbinder bind(@NonNull Activity target) {
        View sourceView = target.getWindow().getDecorView();
        return bind(target, sourceView);
    }


    /**
     * BindView annotated fields and methods in the specified {@code target} using the {@code source}
     * {@link View} as the view root.
     *
     * @param target Target class for view binding.
     * @param source View root on which IDs will be looked up.
     */
    @NonNull
    @UiThread
    public static Unbinder bind(@NonNull Object target, @NonNull View source) {
        List<Unbinder> unbinders = new ArrayList<>();
        Class<?> targetClass = target.getClass();//读取当前的activity
        if ((targetClass.getModifiers() & PRIVATE) != 0) {
            throw new IllegalArgumentException(targetClass.getName() + " must not be private.");
        }

        while (true) {
            String clsName = targetClass.getName();
            if (clsName.startsWith("android.") || clsName.startsWith("java.")
                    || clsName.startsWith("androidx.")) {
                break;
            }

            for (Field field : targetClass.getDeclaredFields()) {//读取activity的变量
                int unbinderStartingSize = unbinders.size();
                Unbinder unbinder;

                unbinder = parseBindView(target, field, source);//根据变量的注解,初始化变量
                if (unbinder != null) unbinders.add(unbinder);

                if (unbinders.size() - unbinderStartingSize > 1) {
                    throw new IllegalStateException(
                            "More than one bind annotation on " + targetClass.getName() + "." + field.getName());
                }
            }

            targetClass = targetClass.getSuperclass();
        }

        if (unbinders.isEmpty()) {
            if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
            return Unbinder.EMPTY;
        }

        if (debug) Log.d(TAG, "HIT: Reflectively found " + unbinders.size() + " bindings.");
        return new CompositeUnbinder(unbinders);
    }

    private static @Nullable
    Unbinder parseBindView(Object target, Field field, View source) {
        BindView bindView = field.getAnnotation(BindView.class);//获得一个变量的注解
        if (bindView == null) {
            return null;
        }
        validateMember(field);

        int id = bindView.value();//获得注解的值
        Class<?> viewClass = field.getType();
        if (!View.class.isAssignableFrom(viewClass) && !viewClass.isInterface()) {
            throw new IllegalStateException(
                    "@BindView fields must extend from View or be an interface. ("
                            + field.getDeclaringClass().getName()
                            + '.'
                            + field.getName()
                            + ')');
        }

        String who = "field '" + field.getName() + "'";
        Object view = Utils.findOptionalViewAsType(source, id, who, viewClass);//相当于view = activity.findViewById(id)
        trySet(field, target, view);//相当于把变量赋值为view,即field = view

        return new FieldUnbinder(target, field);//有需要解绑view才用,你就算想return null,也可以
    }

    private static <T extends AccessibleObject & Member> void validateMember(T object) {
        int modifiers = object.getModifiers();
        if ((modifiers & (PRIVATE | STATIC)) != 0) {
            throw new IllegalStateException(object.getDeclaringClass().getName()
                    + "."
                    + object.getName()
                    + " must not be private or static");
        }
        if ((modifiers & PUBLIC) == 0) {
            object.setAccessible(true);
        }
    }

    static void trySet(Field field, Object target, @Nullable Object value) {
        try {
            field.set(target, value);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to assign " + value + " to " + field + " on " + target, e);
        }
    }

}

实现很简单,看我上面的注释就可以。

4.4 Utils.findOptionalViewAsType 实现

package com.example.annotationtest;

import android.support.annotation.IdRes;
import android.view.View;


@SuppressWarnings("WeakerAccess") // Used by generated code.
public final class Utils {

  public static <T> T findOptionalViewAsType(View source, @IdRes int id, String who,
      Class<T> cls) {
    View view = source.findViewById(id);//哈哈,关键代码在这里
    return castView(view, id, who, cls);
  }


  public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
  }


  private static String getResourceEntryName(View view, @IdRes int id) {
    if (view.isInEditMode()) {
      return "<unavailable while editing>";
    }
    return view.getContext().getResources().getResourceEntryName(id);
  }

  private Utils() {
    throw new AssertionError("No instances.");
  }
}

4.5 其他2个非重要的类:

package com.example.annotationtest;

import android.support.annotation.UiThread;

/** An unbinder contract that will unbind views when called. */
public interface Unbinder {
  @UiThread
  void unbind();

  Unbinder EMPTY = null;
}
package com.example.annotationtest;


import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.util.List;

final class CompositeUnbinder implements Unbinder {
  private @Nullable
  List<Unbinder> unbinders;

  CompositeUnbinder(@NonNull List<Unbinder> unbinders) {
    this.unbinders = unbinders;
  }

  @Override public void unbind() {
    if (unbinders == null) {
      throw new IllegalStateException("Bindings already cleared.");
    }
    for (Unbinder unbinder : unbinders) {
      unbinder.unbind();
    }
    unbinders = null;
  }
}

参考文献

理解Android中的注解与反射
Github ButterKnife

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

newchenxf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值