ButterKnife 原理简易分析

677 阅读2分钟

首先记住2个结论:

  1. BF使用的是编译时注解而非运行时注解,没有使用反射,性能更优
  2. BF不是没有调用findViewById(),而是通过生成类的方式帮我们做了这样的工作

接下来我们自己实现一个最简单的ButterKnife框架,就能够大致了解它的原理。

先看看使用方法:

public class MainActivity extends AppCompatActivity {

	@BindView(R.id.tv)
	public TextView mTv;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ButterKnife.bind(this);
		mTv.setText("####");
	}
}

和BF一样的使用方法。

1. 自定义BindView 注解

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
@Documented
public @interface BindView {
	int value();
}

注解的作用顾名思义,注释和解析其标注的对象,这里是TextView,相当于给TextView打了一个标签,便于在代码里获取和解析它。

2. ButterKnife.bind(this) 方法

/**
 * @param activity
 */
public static void bind(Activity activity) {
	if (activity != null) {
		try {
			// 反射拿到 MainActivity_ViewBinding 类对象
			Class<?> bindClass = Class.forName(activity.getClass().getCanonicalName() + "_ViewBinding");
			Class<?> activityClass = Class.forName(activity.getClass().getCanonicalName());

			// 这里传入activityClass,是为了区分重载的构造函数
			// MainActivity_ViewBinding(MainActivity target)
			Constructor<?> constructor = bindClass.getDeclaredConstructor(activityClass);
			constructor.newInstance(activity);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

通过反射构造了MainActivity_ViewBinding对象,这个MainActivity_ViewBinding就是是通过APT自动生成的,接下来只要搞懂是怎么生成的就完事了。

  1. 先注册一个自定义注解处理器,新建src\main\resources\META-INF\services\javax.annotation.processing.Processor文件,这个写法是固定的,这样程序就会执行我们自己定义的注解注解处理程序,这里是BindProcessor

    com.lu.lib_processor.BindProcessor
    
  2. 完成BindProcessor代码编写

public class BindProcessor extends AbstractProcessor {
	private Filer    mFiler;
	private Elements mElementUtils;

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

		mFiler = processingEnvironment.getFiler();
		mElementUtils = processingEnv.getElementUtils();
	}

	@Override
	public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
		//------------获取注解-----------
		Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
		//LinkedHashMap输出和输入的顺序相同,先输入就先输出
		Map<Element, List<Element>> elementsMap = new LinkedHashMap<>();
		for (Element element : elements) {
			//这里会把所有跟注解有关的field全部拿到,包括各个类中的field,也就是说在编译时,
			// 项目中所有涉及到这个注解的地方的所有field都在这个Set中返回了,我们需要手动进行分类
			System.out.println("[lu]###" + element.getSimpleName());// [lu]###mTv
			//得到的enclosingElement是这个field所在类的类名
			Element enclosingElement = element.getEnclosingElement();
			// ------------enclosingElement-----------MainActivity
			System.out.println("------------enclosingElement-----------" + enclosingElement.getSimpleName());
			//以类名为key值,存储一个类中所有的field到集合elementsMap中
			List<Element> bindViewElements = elementsMap.get(enclosingElement);
			if (bindViewElements == null) {
				bindViewElements = new ArrayList<>();
				elementsMap.put(enclosingElement, bindViewElements);
			}
			bindViewElements.add(element);
		}

		// 遍历elementsMap (LinkedHashMap)
		for (Map.Entry<Element, List<Element>> entry : elementsMap.entrySet()) {
			Element       enclosingElement = entry.getKey();// MainActivity
			List<Element> bindViewElements = entry.getValue();// field: TextView mTv;

			ClassName unbinderClassName = ClassName.get("com.lu.lib_annotation", "Unbinder");
			// ------------Unbinder-----------Unbinder
			System.out.println("------------Unbinder-----------" + unbinderClassName.simpleName());
			//得到类名的字符串
			String    activityName      = enclosingElement.getSimpleName().toString();
			ClassName activityClassName = ClassName.bestGuess(activityName);

			//拼装这一行代码:public final class xxx_ViewBinding implements Unbinder
			TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityName + "_ViewBinding")
					//类名前添加public final
					.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
					//添加类的实现接口
					.addSuperinterface(unbinderClassName)// implements Unbinder
					//添加一个成员变量,这个名字target是仿照butterknife
					.addField(activityClassName, "target", Modifier.PRIVATE);

			//实现 unbinder的方法
			/*
			@Override
			@CallSuper
			public final void unbind() {
				target.mTv = null;
			}
			*/

			//CallSuper这个注解不像Override可以直接拿到,需要用这种方式
			ClassName callSuperClass = ClassName.get("android.support.annotation", "CallSuper");
			MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
					//和你创建的Unbinder中的方法名保持一致
					.addAnnotation(Override.class)
					.addAnnotation(callSuperClass)
					.addModifiers(Modifier.FINAL, Modifier.PUBLIC);

			/*
			MainActivity_ViewBinding(MainActivity target) {
				this.target = target;
				target.mTv = (TextView) target.findViewById(2131165312);
			}
			*/

			//添加构造函数
			MethodSpec.Builder constructMethodBuilder = MethodSpec.constructorBuilder().addParameter(activityClassName, "target");
			constructMethodBuilder.addStatement("this.target = target");

			for (Element bindViewElement : bindViewElements) {
				String fieldName = bindViewElement.getSimpleName().toString();

				//在构造方法中添加初始化代码
				ClassName utilsClassName = ClassName.get("com.lu.lib_annotation", "Utils");
				ClassName tv = ClassName.get("android.widget", "TextView");
				BindView  annotation     = bindViewElement.getAnnotation(BindView.class);
				if (annotation != null) {
					int resId = annotation.value();
					constructMethodBuilder.addStatement("target.$L = ($T) target.findViewById($L)"
							, fieldName, tv, resId);

					//在unbind方法中添加代码 target.textView1 = null;
					//不能用addCode,因为它不会在每一行代码后加分号和换行
					unbindMethodBuilder.addStatement("target.$L = null", fieldName);
				}
			}

			classBuilder.addMethod(constructMethodBuilder.build());// 添加构造函数

			classBuilder.addMethod(unbindMethodBuilder.build());// 添加unbind()方法

			// 写入代码
			try {
				//得到包名
				String packageName = mElementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();

				JavaFile.builder(packageName, classBuilder.build())
						//添加类的注释
						.addFileComment("butterknife 自动生成").build().writeTo(mFiler);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return false;

	}

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

	}

	@Override
	public Set<String> getSupportedAnnotationTypes() {
		Set<String> types = new LinkedHashSet<>();

		for (Class<? extends Annotation> annotation : getSupportAnnotations()) {
			types.add(annotation.getCanonicalName());
		}
		return types;
	}

	private Set<Class<? extends Annotation>> getSupportAnnotations() {
		Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
		annotations.add(BindView.class);
		return annotations;
	}
}

重要部分都有注释,应该不难理解,其他的查查资料搞懂JavaPoet的使用就可以了。

完整代码参考

luliang6/MyButterKnife (github.com)