框架手写系列---apt注解处理器方式实现ButterKnife框架
Posted 战国剑
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了框架手写系列---apt注解处理器方式实现ButterKnife框架相关的知识,希望对你有一定的参考价值。
一、ButterKnife
ButterKnife作为常用框架之一,主要用于简化代码,减少重复代码。
这里主要注重原理与核心,将分步骤手写它的核心代码。
ButterKnife最常用的是去除代码中的findViewById,以注解的方式代替原有的代码,这里也从这里入手。
Android之注解的使用——绑定android控件 这是前文中通过反射方式实现,可对比查看
二、原理说明
public class MainActivity extends AppCompatActivity
@BindView(R.id.hello)
TextView hello;
@BindView(R.id.btn)
Button btn;
...
核心原理:
1、ButterKnife的用法,如上图所示,以注解的方式标识控件。
2、通过apt注解处理器,处理该注解BindView,将注解上的参数传入到处理器中,生成代码。
3、通过调用生成的代码,实现findviewById等。
三、手写实现
1、定义注解BindView
//编译时起效
@Retention(RetentionPolicy.CLASS)
//针对的是属性
@Target(ElementType.FIELD)
public @interface BindView
int value();
2、注解处理器的编写
//注解处理器的依赖,此处有注意点: dependencies implementation fileTree(dir: 'libs', include: ['*.jar']) //依赖注解 implementation project(path: ':bind-annotation') //如果是3.6+的android studio,auto-service需要按如下依赖 compileOnly'com.google.auto.service:auto-service:1.0-rc4' annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
//注册编译处理器到系统
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment)
super.init(processingEnvironment);
//filter 用于后续写文件
filer = processingEnvironment.getFiler();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
//roundEnvironment中根据annotation获取节点
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
//区分上面获取到的节点,获取类名和该类下的BindView节点====> 形成一个Map<类名,BindView标记的View集合>这样的结构。
//后续根据这个结构生成一个或者多个java文件
Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
Map<String,List<VariableElement>> map = new HashMap<>();
List<VariableElement> variableElements;
while (iterator.hasNext())
//节点集合
VariableElement variableElement = (VariableElement)iterator.next();
//类名
String className = variableElement.getEnclosingElement().getSimpleName().toString();
variableElements = map.get(className);
if(map.get(className) == null)
variableElements = new ArrayList<>();
map.put(className,variableElements);
variableElements.add(variableElement);
//写文件
Writer writer = null;
Iterator<String> iteratorNames = map.keySet().iterator();
while (iteratorNames.hasNext())
String currentClassName = iteratorNames.next();
List<VariableElement> currentVariableElements = map.get(currentClassName);
//包名
String packageName = processingEnv.getElementUtils().getPackageOf(currentVariableElements.get(0)).toString();
try
JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + currentClassName + "$$ViewBind");
writer = sourceFile.openWriter();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("package "+packageName+";\\n");
stringBuilder.append("import com.sunny.bind_api.IBindView;\\n");
stringBuilder.append("public class "+currentClassName + "$$ViewBind implements IBindView<"+packageName+"."+currentClassName+">"+"\\n");
stringBuilder.append("public void bind("+packageName+"."+currentClassName+" target)\\n");
for(VariableElement currentElement :currentVariableElements)
//控件名字
String filedName = currentElement.getSimpleName().toString();
//控件类型
TypeMirror typeMirror = currentElement.asType();
//控件resId
int resourceId = currentElement.getAnnotation(BindView.class).value();
stringBuilder.append("target."+filedName +" = ("+typeMirror+")target.findViewById("+resourceId+");\\n");
stringBuilder.append("\\n\\n");
writer.write(stringBuilder.toString());
catch (IOException e)
e.printStackTrace();
finally
if(writer != null)
try
writer.close();
catch (IOException e)
e.printStackTrace();
return false;
@Override
public SourceVersion getSupportedSourceVersion()
return processingEnv.getSourceVersion();
@Override
public Set<String> getSupportedAnnotationTypes()
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
这里的核心在于:如何获取到Field类型的注解控件,然后 获取该控件的类型,resourceId,并把以上控件信息,放置到一个集合中,后续遍历并生成文件。
1、variableElement代表属性节点
2、ExecutableElement代表方法节点
3、TypeElement代表最外层的类节点
此处在生成代码时,实现了一个接口:IBindView,主要是为了后续在调用代码时更有指向性与范围。
public interface IBindView<T>
void bind(T t);
3、调用代码的编写
public class ButterKnife
public static void bind(Activity activity)
String className = activity.getClass().getName()+"$$ViewBind";
try
Class<?> aClass = Class.forName(className);
//IBindView的使用
IBindView o = (IBindView)aClass.newInstance();
//具体调用
o.bind(activity);
catch (Exception e)
e.printStackTrace();
目前在用的ButterKnife,复杂性与完整性,比以上的核心简写复杂很多,但本质原理就是上面描述的这样:用apt的方式,简化代码与去除重复代码。
apt对于去重代码、生成代码、切面编程等,十分有效,多数涉及到框架的,都会用到该用法。
以上是关于框架手写系列---apt注解处理器方式实现ButterKnife框架的主要内容,如果未能解决你的问题,请参考以下文章