相关文章:
浅析ButterKnife的实现 (一) —— 搭建开发框架
浅析ButterKnife的实现 (二) —— BindResource
这里开始讲解最常用的绑定View的注解了,这个会比资源绑定注解复杂一点,不过大体流程都是相似的。
@Bind
定义个用来注入View资源的注解:
/**
* View绑定
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Bind {
@IdRes int[] value();
}
这里用
@IdRes 限定了属性的取值范围为
R.id.xxx,并且属性值是个数组,因为这个注解不仅可以绑定单个View还可以同时绑定多个View。
来看下注解处理器:
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 略...
// 处理Bind
for (Element element : roundEnv.getElementsAnnotatedWith(Bind.class)) {
if (VerifyHelper.verifyView(element, messager)) {
ParseHelper.parseViewBind(element, targetClassMap, erasedTargetNames,
elementUtils, typeUtils, messager);
}
}
// 略...
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(BindString.class.getCanonicalName());
annotations.add(BindColor.class.getCanonicalName());
annotations.add(Bind.class.getCanonicalName());
annotations.add(OnClick.class.getCanonicalName());
return annotations;
}
}
还是要看注解的检测和解析,对于该注解的检测并没有做特别的处理,所以代码我就不贴了,主要来看解析过程。
public final class ParseHelper {
private static final String BINDING_CLASS_SUFFIX = "$$ViewBinder";
private static final String LIST_TYPE = List.class.getCanonicalName();
private static final String ITERABLE_TYPE = "java.lang.Iterable<?>";
static final String VIEW_TYPE = "android.view.View";
/**
* 解析 View 资源
*
* @param element 使用注解的元素
* @param targetClassMap 映射表
* @param elementUtils 元素工具类
*/
public static void parseViewBind(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames,
Elements elementUtils, Types typeUtils, Messager messager) {
TypeMirror elementType = element.asType();
// 判断是一个 View 还是列表
if (elementType.getKind() == TypeKind.ARRAY) {
_parseBindMany(element, targetClassMap, erasedTargetNames, elementUtils, messager);
} else if (LIST_TYPE.equals(_doubleErasure(elementType, typeUtils))) {
_parseBindMany(element, targetClassMap, erasedTargetNames, elementUtils, messager);
} else if (_isSubtypeOfType(elementType, ITERABLE_TYPE)) {
_error(messager, element, "@%s must be a List or array. (%s.%s)", Bind.class.getSimpleName(),
((TypeElement) element.getEnclosingElement()).getQualifiedName(),
element.getSimpleName());
} else {
_parseBindOne(element, targetClassMap, erasedTargetNames, elementUtils, messager);
}
}
/*************************************************************************/
/**
* 先通过 Types 工具对元素类型进行形式参数擦除,再通过字符比对进行二次擦除如果必要的话
* 例:java.util.List<java.lang.String> -> java.util.List
*
* @param elementType 元素类型
* @param typeUtils 类型工具
* @return 类型完全限定名
*/
private static String _doubleErasure(TypeMirror elementType, Types typeUtils) {
String name = typeUtils.erasure(elementType).toString();
int typeParamStart = name.indexOf('<');
if (typeParamStart != -1) {
name = name.substring(0, typeParamStart);
}
return name;
}
/**
* 判断该类型是否为 otherType 的子类型
*
* @param typeMirror 元素类型
* @param otherType 比对类型
* @return
*/
private static boolean _isSubtypeOfType(TypeMirror typeMirror, String otherType) {
if (otherType.equals(typeMirror.toString())) {
return true;
}
if (typeMirror.getKind() != TypeKind.DECLARED) {
return false;
}
DeclaredType declaredType = (DeclaredType) typeMirror;
// 判断泛型列表
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments.size() > 0) {
StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
typeString.append('<');
for (int i = 0; i < typeArguments.size(); i++) {
if (i > 0) {
typeString.append(',');
}
typeString.append('?');
}
typeString.append('>');
if (typeString.toString().equals(otherType)) {
return true;
}
}
// 判断是否为类或接口类型
Element element = declaredType.asElement();
if (!(element instanceof TypeElement)) {
return false;
}
// 判断父类
TypeElement typeElement = (TypeElement) element;
TypeMirror superType = typeElement.getSuperclass();
if (_isSubtypeOfType(superType, otherType)) {
return true;
}
// 判断接口
for (TypeMirror interfaceType : typeElement.getInterfaces()) {
if (_isSubtypeOfType(interfaceType, otherType)) {
return true;
}
}
return false;
}
/**
* 解析单个View绑定
*
* @param element
* @param targetClassMap
* @param erasedTargetNames
*/
private static void _parseBindOne(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames, Elements elementUtils, Messager messager) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
TypeMirror elementType = element.asType();
if (elementType.getKind() == TypeKind.TYPEVAR) {
// 处理泛型,取它的上边界,例:<T extends TextView> -> TextView
TypeVariable typeVariable = (TypeVariable) elementType;
elementType = typeVariable.getUpperBound();
}
// 不是View的子类型,且不是接口类型则报错
if (!_isSubtypeOfType(elementType, VIEW_TYPE) && !_isInterface(elementType)) {
_error(messager, element, "@%s fields must extend from View or be an interface. (%s.%s)",
Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
// 资源ID只能有一个
int[] ids = element.getAnnotation(Bind.class).value();
if (ids.length != 1) {
_error(messager, element, "@%s for a view must only specify one ID. Found: %s. (%s.%s)",
Bind.class.getSimpleName(), Arrays.toString(ids), enclosingElement.getQualifiedName(),
element.getSimpleName());
return;
}
// 获取或创建绑定类
int id = ids[0];
BindingClass bindingClass = _getOrCreateTargetClass(element, targetClassMap, elementUtils);
FieldViewBinding existViewBinding = bindingClass.isExistViewBinding(id);
if (existViewBinding != null) {
// 存在重复使用的ID
_error(messager, element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
Bind.class.getSimpleName(), id, existViewBinding.getName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
String name = element.getSimpleName().toString();
TypeName type = TypeName.get(elementType);
// 生成资源信息
FieldViewBinding binding = new FieldViewBinding(name, type, true);
// 给BindingClass添加资源信息
bindingClass.addViewBinding(id, binding);
erasedTargetNames.add(enclosingElement);
}
/**
* 解析 View 列表
* @param element
* @param targetClassMap
* @param erasedTargetNames
* @param elementUtils
* @param messager
*/
private static void _parseBindMany(Element element, Map<TypeElement, BindingClass> targetClassMap,
Set<TypeElement> erasedTargetNames,
Elements elementUtils, Messager messager) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
TypeMirror elementType = element.asType();
TypeMirror viewType = null;
FieldCollectionViewBinding.Kind kind;
if (elementType.getKind() == TypeKind.ARRAY) {
ArrayType arrayType = (ArrayType) elementType;
// 获取数组里面包含的View类型
viewType = arrayType.getComponentType();
kind = FieldCollectionViewBinding.Kind.ARRAY;
} else {
// 默认不是数组就只能是 List
DeclaredType declaredType = (DeclaredType) elementType;
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments.size() != 1) {
// 列表的参数只能有一个
_error(messager, element, "@%s List must have a generic component. (%s.%s)",
Bind.class.getSimpleName(), enclosingElement.getQualifiedName(),
element.getSimpleName());
return;
} else {
// 获取 View 类型
viewType = typeArguments.get(0);
}
kind = FieldCollectionViewBinding.Kind.LIST;
}
// 处理泛型
if (viewType != null && viewType.getKind() == TypeKind.TYPEVAR) {
TypeVariable typeVariable = (TypeVariable) viewType;
viewType = typeVariable.getUpperBound();
}
// 不是View的子类型,且不是接口类型则报错
if (viewType != null && !_isSubtypeOfType(viewType, VIEW_TYPE) && !_isInterface(viewType)) {
_error(messager, element, "@%s List or array type must extend from View or be an interface. (%s.%s)",
Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
assert viewType != null; // Always false as hasError would have been true.
int[] ids = element.getAnnotation(Bind.class).value();
if (ids.length == 0) {
_error(messager, element, "@%s must specify at least one ID. (%s.%s)", Bind.class.getSimpleName(),
enclosingElement.getQualifiedName(), element.getSimpleName());
return;
}
// 检测是否有重复 ID
Integer duplicateId = _findDuplicate(ids);
if (duplicateId != null) {
_error(messager, element, "@%s annotation contains duplicate ID %d. (%s.%s)", Bind.class.getSimpleName(),
duplicateId, enclosingElement.getQualifiedName(), element.getSimpleName());
}
String name = element.getSimpleName().toString();
TypeName typeName = TypeName.get(viewType); // 这边取得是View的类型不是列表类型
BindingClass bindingClass = _getOrCreateTargetClass(element, targetClassMap, elementUtils);
FieldCollectionViewBinding binding = new FieldCollectionViewBinding(name, typeName, kind);
bindingClass.addFieldCollection(ids, binding);
erasedTargetNames.add(enclosingElement);
}
/**
* 判断是否为接口
*
* @param typeMirror
* @return
*/
private static boolean _isInterface(TypeMirror typeMirror) {
return typeMirror instanceof DeclaredType
&& ((DeclaredType) typeMirror).asElement().getKind() == ElementKind.INTERFACE;
}
/**
* 检测重复ID
*/
private static Integer _findDuplicate(int[] array) {
Set<Integer> seenElements = new LinkedHashSet<>();
for (int element : array) {
if (!seenElements.add(element)) {
return element;
}
}
return null;
}
}
东西还是比较多的,不过主要关注 _parseBindOne() 和_parseBindMany() 两个方法,分别对应单个View的绑定和复数View的绑定处理。对于如何选择哪个方法是通过判断元素的类型来处理,如果元素类型为 ARRAY或 LIST_TYPE则调用 _parseBindMany() 方法,否则调用 _parseBindOne()。对于元素类型的判断,ARRAY比较简单可以直接判断,但是LIST就麻烦点,因为 TypeKind 里面并没有包含LIST类型,更确切地说这个应该算 DECLARED 类型,在判断上我们要做点处理。在 _doubleErasure() 方法中我们进行了形式类型参数的擦除获取到类类型的完全限定名,然后进行字符串比较就行了。
下面先来说明单个View的绑定处理过程_parseBindOne(),首先进行元素泛型处理,如果该元素是个泛型的话就取它的上边界,举个例子:
public class MyTestView<T extends TextView> {
@Bind(R.id.my_view)
T mView;
}
这个时候就要进行泛型处理,而获取到的元素类型应该为 DECLARED 类型,更确切是 TextView。泛型判断完就判断该类型是不是 VIEW_TYPE = "android.view.View" 的子类型或是个接口类型,不是就报错。对于View的子类型应该好理解,因为我们现在是在处理View的注入,所以应该为View的子类,那为什么接口也可以呢?大家应该知道Java是面向接口编程的,我们可以把对象显示转换为它实现的接口类型,并可以调用接口相应的方法,如果转换为未实现的接口类型在转换的时候会报错,这个等下后面会看到。
这里主要来讲子类型的判断 _isSubtypeOfType(),这里用来判断的类型必须为类或接口 TypeKind.DECLARED,参数列表也进行判断,如果当前元素类型不符合则再去判断它的父类型和接口,如果都不是则表明该类型确实不符合。
这些判断完就可以获取注解属性值了,因为我们现在在处 理单个View的注入,所以只能有一个ID值。获取到ID值后我们就可以生成 FieldViewBinding 信息了,前面还要对这个ID是否已存在进行判断,一个ID只能对应一个View视图。下面看下 FieldViewBinding的定义:
/**
* View绑定信息
*/
final class FieldViewBinding implements ViewBinding {
private final String name;
private final TypeName type;
private final boolean required;
FieldViewBinding(String name, TypeName type, boolean required) {
this.name = name;
this.type = type;
this.required = required;
}
public String getName() {
return name;
}
public TypeName getType() {
return type;
}
@Override
public String getDescription() {
return "field '" + name + "'";
}
public boolean isRequired() {
return required;
}
public boolean requiresCast() {
return !VIEW_TYPE.equals(type.toString());
}
}
其中 name 就是使用注解的字段名称,这个没什么问题,然后是 TypeName这是JavaPoet提供的一个类型,其实它对应的就是字段对应的类类型,这个在生成Java文件的时候可以直接转换为对应类类型。required的话是在表明注解字段是否必须赋值,也就是是否可以为null,实际是判断是否存在@Nullable 注解,为了方便我这里强制定义字段必须不能为null,有兴趣可以看下源码对这个参数的处理。代码最后有个 requiresCast() 方法用来判断是否需要进行类型转换,通过 findViewById() 返回的都是View类型,我们可以转换为更准确的类型如TextView。getDescription() 只是用来出错时候输出的描述信息,这里简单了解下即可。
在对代码生成进行说明前,先回过头来看下我们之前定义的 Finder 这个类,里面有几个关于 findViewById()方法没给出。
public enum Finder {
// 略...
/**
* findViewById,会进行 null 判断
* @param source
* @param id 资源ID
* @param who 描述信息
* @param <T> 转换类型
* @return
*/
public <T> T findRequiredView(Object source, int id, String who) {
T view = findOptionalView(source, id, who);
if (view == null) {
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.");
}
return view;
}
/**
* findViewById,不进行 null 判断
*/
public <T> T findOptionalView(Object source, int id, String who) {
View view = findView(source, id);
return castView(view, id, who);
}
/**
* 类型转换
*/
@SuppressWarnings("unchecked") // That's the point.
public <T> T castView(View view, int id, String who) {
try {
return (T) view;
} catch (ClassCastException e) {
if (who == null) {
throw new AssertionError();
}
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);
}
}
/**
* 参数类型转换
*/
@SuppressWarnings("unchecked") // That's the point.
public <T> T castParam(Object value, String from, int fromPosition, String to, int toPosition) {
try {
return (T) value;
} catch (ClassCastException e) {
throw new IllegalStateException("Parameter #"
+ (fromPosition + 1)
+ " of method '"
+ from
+ "' was of the wrong type for parameter #"
+ (toPosition + 1)
+ " of method '"
+ to
+ "'. See cause for more info.", e);
}
}
}
这几个方法大家看下注释说明应该都能理解,下面直接来看下 BindingClass 的处理。
public final class BindingClass {
private static final ClassName FINDER = ClassName.get("com.dl7.butterknifelib", "Finder");
private static final ClassName VIEW_BINDER = ClassName.get("com.dl7.butterknifelib", "ViewBinder");
private static final ClassName VIEW = ClassName.get("android.view", "View");
private final List<FieldViewBinding> viewBindings = new ArrayList<>();
private final Map<Integer, FieldViewBinding> viewIdMap = new LinkedHashMap<>();
/**
* 创建方法
*
* @return MethodSpec
*/
private MethodSpec _createBindMethod() {
// 略...
if (_hasViewBinding()) {
// View
for (Map.Entry<Integer, FieldViewBinding> entry : viewIdMap.entrySet()) {
int id = entry.getKey();
FieldViewBinding viewBinding = entry.getValue();
result.addStatement("target.$L = finder.findRequiredView(source, $L, $S)",
viewBinding.getName(), id, viewBinding.getDescription());
}
}
return result.build();
}
/**
* 添加 ViewBinding
*
* @param binding 资源信息
*/
public void addViewBinding(int id, FieldViewBinding binding) {
FieldViewBinding fieldViewBinding = viewIdMap.get(id);
if (fieldViewBinding == null) {
viewBindings.add(binding);
viewIdMap.put(id, binding);
}
}
private boolean _hasViewBinding() {
return !(viewBindings.isEmpty() && collectionBindings.isEmpty());
}
/**
* 判断 id 是否已经绑定 View
*
* @param id 资源ID
* @return
*/
public FieldViewBinding isExistViewBinding(int id) {
return viewIdMap.get(id);
}
}
看下关键代码,这里用 viewIdMap 来存储设置过的ID 和 FieldViewBinding键值对,用来判断是否重复设置。在生成代码的时候,调用了 finder.findRequiredView() 方法来进行View的设置,这个是会进行null 判断的。
这里也处理完就可以正常注入单个View了,下面来看下复数View的注入处理。
复数View注入
1、在这里首先要判断到底是 ARRAY还是LIST,并且取到列表所包含参数的 viewType类型,这里 FieldCollectionViewBinding.Kind 是个枚举值,等下会提到;
2、如果 viewType 是泛型参数的话进行泛型处理,并判断是否是View的子类型或接口;
3、获取注解的ID值列表,并判断是否包含重复ID;
4、生成 FieldCollectionViewBinding并添加到 BindingClass中;
来看下 FieldCollectionViewBinding的定义:
/**
* View列表信息
*/
final class FieldCollectionViewBinding implements ViewBinding {
enum Kind {
ARRAY,
LIST
}
private final String name;
private final TypeName type;
private final Kind kind;
FieldCollectionViewBinding(String name, TypeName type, Kind kind) {
this.name = name;
this.type = type;
this.kind = kind;
}
public String getName() {
return name;
}
public TypeName getType() {
return type;
}
public Kind getKind() {
return kind;
}
@Override
public String getDescription() {
return "field '" + name + "'";
}
}
和前面的 FieldViewBinding 类似,多了个 Kind枚举值来指明是 ARRAY 还是 LIST。在进行代码生成前,我们需要在 butterknifelib库中引入两个类:Utils和ImmutableList。先看下 Utils:
@SuppressWarnings("deprecation") //
public final class Utils {
private Utils() {
throw new AssertionError("No instances.");
}
@SafeVarargs
public static <T> T[] arrayOf(T... views) {
return filterNull(views);
}
@SafeVarargs
public static <T> List<T> listOf(T... views) {
return new ImmutableList<>(filterNull(views));
}
private static <T> T[] filterNull(T[] views) {
int end = 0;
int length = views.length;
for (int i = 0; i < length; i++) {
T view = views[i];
if (view != null) {
views[end++] = view;
}
}
if (end == length) {
return views;
}
//noinspection unchecked
T[] newViews = (T[]) Array.newInstance(views.getClass().getComponentType(), end);
System.arraycopy(views, 0, newViews, 0, end);
return newViews;
}
}
它提供了两个接口来辅助实现View列表的注入,分别为 arrayOf(T... views) 和listOf(T... views),对应ARRAY 还是 LIST。在这里面会自动去除列表中 null 的 View。在转换 LIST 时定义了个 ImmutableList来帮助完成这个工作,它是一个轻量级不可更改的LIST 类,定义如下:
final class ImmutableList<T> extends AbstractList<T> implements RandomAccess {
private final T[] views;
ImmutableList(T[] views) {
this.views = views;
}
@Override
public T get(int index) {
return views[index];
}
@Override
public int size() {
return views.length;
}
@Override
public boolean contains(Object o) {
for (T view : views) {
if (view == o) {
return true;
}
}
return false;
}
}
最后来看下
BindingClass 的处理:
public final class BindingClass {
private static final ClassName FINDER = ClassName.get("com.dl7.butterknifelib", "Finder");
private static final ClassName VIEW_BINDER = ClassName.get("com.dl7.butterknifelib", "ViewBinder");
private static final ClassName UTILS = ClassName.get("com.dl7.butterknifelib", "Utils");
private final Map<FieldCollectionViewBinding, int[]> collectionBindings = new LinkedHashMap<>();
/**
* 创建方法
*
* @return MethodSpec
*/
private MethodSpec _createBindMethod() {
// 略...
// ViewList
for (Map.Entry<FieldCollectionViewBinding, int[]> entry : collectionBindings.entrySet()) {
String ofName; // UTILS的方法名
FieldCollectionViewBinding binding = entry.getKey();
int[] ids = entry.getValue();
// 获取方法名
if (binding.getKind() == FieldCollectionViewBinding.Kind.ARRAY) {
ofName = "arrayOf";
} else if (binding.getKind() == FieldCollectionViewBinding.Kind.LIST) {
ofName = "listOf";
} else {
throw new IllegalStateException("Unknown kind: " + binding.getKind());
}
// 填充复数 View 代码,作为 Utils 方法的参数
CodeBlock.Builder builder = CodeBlock.builder();
for (int i = 0; i < ids.length; i++) {
if (i > 0) {
builder.add(", ");
}
builder.add("\nfinder.<$T>findRequiredView(source, $L, $S)", binding.getType(), ids[i],
binding.getDescription());
}
// 调用 Utils 的方法
result.addStatement("target.$L = $T.$L($L)", binding.getName(), UTILS, ofName, builder.build());
}
// 略...
return result.build();
}
/**
* 添加 ViewBinding
*
* @param binding 资源信息
*/
void addFieldCollection(int[] ids, FieldCollectionViewBinding binding) {
collectionBindings.put(binding, ids);
}
}
这里会先判
断是 ARRAY 还是 LIST,然后调用 Utils对应的方法来进行View的注入,CodeBlock.Builder是
JavaPoet中代码块的写法,详细使用看官方例子。
样例
我们在代码中使用注解如下:
public class MainActivity extends AppCompatActivity {
@Bind(R.id.tv_desc)
TextView textView;
@Bind(R.id.fl_view)
FrameLayout view;
@Bind({R.id.btn_one, R.id.btn_two, R.id.btn_three})
List<Button> mButtons;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
}
生成的代码如下:
public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
@Override
@SuppressWarnings("ResourceType")
public void bind(final Finder finder, final T target, Object source) {
target.textView = finder.findRequiredView(source, 2131492948, "field 'textView'");
target.view = finder.findRequiredView(source, 2131492944, "field 'view'");
target.mButtons = Utils.listOf(
finder.<Button>findRequiredView(source, 2131492945, "field 'mButtons'"),
finder.<Button>findRequiredView(source, 2131492946, "field 'mButtons'"),
finder.<Button>findRequiredView(source, 2131492947, "field 'mButtons'"));
}
}
你再对照前面说讲的流程来理解整个代码生成的过程是怎么来的,这样可以更好地理解它的实现原理。