前面分析了ButterKnife的源码,了解其实现原理,那么就将原理运用于实践吧。
github地址: 点击打开链接
一、自定义注解
这里为了便于理解,只提供BindView注解。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
二、添加注解处理器
添加ViewInjectProcessor注解处理器,看代码,
public class ViewInjectProcessor extends AbstractProcessor {
// Storing all the annotation information under the same Class
Map<String, BindingClass> classMap = new HashMap<>();
private Filer mFiler;
Elements elementUtils;
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
elementUtils = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// collect information
gatherInformation(roundEnv);
// generate java code
try {
for (BindingClass bindingClass : classMap.values()) {
info("Generating file for %s", bindingClass.getFullClassName());
bindingClass.brewJava().writeTo(mFiler);
}
} catch (IOException e) {
e.printStackTrace();
error("Generate file failed, reason: %s", e.getMessage());
}
return true;
}
......
}
这里分别实现了init、getSupportedAnnotationTypes、getSupportedSourceVersion、process方法。
在process方法中主要实现了收集信息的gatherInformation方法和生成Java代码的brewJava方法。
具体来看gatherInformation方法;
private void gatherInformation(RoundEnvironment roundEnv) {
classMap.clear();
gatherBindView(roundEnv);
}
private void gatherBindView(RoundEnvironment roundEnv) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
BindingClass bindingClass = getBindingClass(element);
BindViewField field = new BindViewField(element);
bindingClass.addField(field);
}
}
这里定义了一个BindingClass类,看一下它的实现;
public class BindingClass {
public TypeElement mClassElement; //类名
public List<BindViewField> mFields;//成员变量
public Elements mElementUtils;
public BindingClass(TypeElement classElement, Elements elementUtils) {
this.mClassElement = classElement;
this.mElementUtils = elementUtils;
mFields = new ArrayList<>();
}
public String getFullClassName() {
return mClassElement.getQualifiedName().toString();
}
public void addField(BindViewField field) {
mFields.add(field);
}
private String getPackageName(TypeElement type) {
return mElementUtils.getPackageOf(type).getQualifiedName().toString();
}
private static String getClassName(TypeElement type, String packageName) {
int packageLen = packageName.length() + 1;
return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
}
public JavaFile brewJava() {
// method inject(final T host, Object source, Provider provider)
ClassName FINDER = ClassName.get("com.muse.api.finder", "Finder");
MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(TypeName.get(mClassElement.asType()), "target", Modifier.FINAL)
.addParameter(TypeName.OBJECT, "source")
.addParameter(FINDER, "finder");
//field
for (BindViewField field : mFields) {
injectMethodBuilder.addStatement("target.$N= ($T)(finder.findView(source,$L))", field.getFieldName()
, ClassName.get(field.getFieldType()), field.getResId());
}
String packageName = getPackageName(mClassElement);
String className = getClassName(mClassElement, packageName);
ClassName bindingClassName = ClassName.get(packageName, className);
ClassName INJECTOR = ClassName.get("com.muse.api", "Injector");
// generate whole class
TypeSpec finderClass = TypeSpec.classBuilder(bindingClassName.simpleName() + "$$ViewInjector")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(INJECTOR, TypeName.get(mClassElement.asType())))
.addMethod(injectMethodBuilder.build())
.build();
return JavaFile.builder(packageName, finderClass).build();
}
}
显然,这个类的设计是参考了源码中的BindingSet的设计,只是没有使用设计模式。
三、提供API
对外提供InjectHelper.代码如下
public class InjectHelper {
private static final String SUFFIX = "$$ViewInjector";
private static final ActivityFinder ACTIVITY_FINDER = new ActivityFinder();
private static final Map<String, Injector> FINDER_MAP = new HashMap<>();
public static void inject(Activity host) {
inject(host, host, ACTIVITY_FINDER);
}
public static void inject(Object host, Object source, Finder finder) {
String className = host.getClass().getName();
try {
Injector injector = FINDER_MAP.get(className);
if (injector == null) {
String classFullName = host.getClass().getName() + SUFFIX;
Class<?> finderClass = Class.forName(classFullName);
injector = (Injector) finderClass.newInstance();
FINDER_MAP.put(className, injector);
}
injector.inject(host, source, finder);
} catch (Exception e) {
throw new RuntimeException("Unable to inject for " + className, e);
}
}
}
至于API中涉及到的其他类,请参考源码。
四、使用框架
应用在使用时,需要做依赖
implementation project(':ioc-api')
annotationProcessor project(':ioc-compiler')
implementation project(':ioc-annotation')
代码使用如下:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.test_btn)
Button testBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectHelper.inject(this);
testBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "Button is bind", Toast.LENGTH_SHORT).show();
}
});
}
}
Demo程序运行结果如下:
关于ButterKnife的使用及源理探究至此已经完结。