Android架构设计——【 APT技术实现butterknife框架 】
创始人
2024-05-26 11:43:17
0

APT简介

APT英文全称:Android annotation process tool是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。

Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成源文件和原来的源文件,将它们一起生成class文件。简言之:APT可以把注解,在编译时生成代码。

是一种处理注释的工具,它对源代码文件进行检测并找出其中的 Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够运行,必须要通过 APT 工具来处理。

简单说:根据规则,帮我们生成代码、生成类文件

编译时注解就是通过 APT 来通过注解信息生成代码来完成某些功能,典型代表有 ButterKnife、Dagger、ARouter 等 ButterKnife 原理分析使用APT来处理

annotation的流程

  • 1.定义注解(如@MyButterKnife)
  • 2.定义注解处理器
  • 3.在处理器里面完成处理方式,通常是生成java代码。
  • 4.注册处理器
  • 5.利用APT完成如下图的工作内容。

手写ButterKnife

想要完全理解ButterKnife底层的APT技术,手写实现ButterKnife可以帮助更好地吸收这种技术。

准备工作

(1)创建Android工程,并且在此项目中新建一个java Module取名为annotataion,用于存放注解。 注意:必须新建java library而不能是Android lib,为什么只能新建java工程,不能是Android工程后面讲

(2)创建第二个java library取名为annotation_process,故名思议此module是存放注解处理器。

(3)为了简单起见,此处就在annotaiton中创建两个注解BindView和OnClick,分别作用于编译期,因此定于为CLASS。

BindView作用:初始化View成员变量,替开发者实现findViewById。

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

OnClick作用:初始化Button等按钮,替用户实现setOnClickListener等等。

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface OnClick {int[] value();
}

(4)导入项目依赖

app module依赖annotation和annotation_process

anontation_process依赖annotation。从此处可以说明为什么annotation和annotation_process必须定义为java library,android工程可以依赖java工程,但反之不行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sdm1iXSz-1676966714695)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/538be825bf864a1ea7b45d5379fd6519~tplv-k3u1fbpfcp-zoom-1.image)]

(5)在annotation_process的build.grade中添加以下两句依赖用于实现注解处理器。

annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'

定义注解处理器

1、继承AbstractAnnotation, 2、在其类上添加@AutoService 3、实现process方法

@AutoService(Process.class)
public class AnnotationProcess extends AbstractProcessor {//通过io流动态生成代码private Filer filer;
​@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);filer = processingEnvironment.getFiler();}@Overridepublic SourceVersion getSupportedSourceVersion() {return super.getSupportedSourceVersion();}@Overridepublic Set getSupportedAnnotationTypes() {Set set = new HashSet<>();set.add(BindView.class.getName());set.add(OnClick.class.getName());return set;}@Overridepublic boolean process(Set set, RoundEnvironment roundEnvironment) {//将Activity和其内部所有被OnCLick+BindView标记了的元素一一对应起来HashMap hashMap = findAnnotationForActivity(roundEnvironment);//写文件if (hashMap.size() != 0) {Writer writer = null;Iterator iterator = hashMap.keySet().iterator();while (iterator.hasNext()) {TypeElement element = iterator.next();ElementForType elementForType = hashMap.get(element);//获取Activity类名String className = element.getSimpleName().toString();//生成新类名String newClassName = className + "$$ButterKnife";//获取包名PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(element);String packageName = packageElement.getQualifiedName().toString();JavaFileObject javaFileObject = null;try {javaFileObject = filer.createSourceFile(packageName + "." + newClassName);writer = javaFileObject.openWriter();//将所有要生成的代码存到StringBuffer中StringBuffer stringBuffer = getStringBuffer(packageName, newClassName, element, elementForType);} catch (IOException e) {e.printStackTrace();}finally {try {writer.close();} catch (IOException e) {e.printStackTrace();}}}}return false;}
​
​public StringBuffer getStringBuffer(String packageName, String newClazzName,TypeElement typeElement, ElementForType elementForType) {StringBuffer stringBuffer = new StringBuffer();stringBuffer.append("package " + packageName + ";\n");stringBuffer.append("import android.view.View;\n");stringBuffer.append("public class " + newClazzName + "{\n");stringBuffer.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");if (elementForType != null && elementForType.getViewElements() != null && elementForType.getViewElements().size() > 0) {List viewElements = elementForType.getViewElements();for (VariableElement viewElement : viewElements) {//获取到类型TypeMirror typeMirror = viewElement.asType();//获取到控件的名字Name simpleName = viewElement.getSimpleName();//获取到资源IDint resId = viewElement.getAnnotation(BindView.class).value();stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.findViewById(" + resId + ");\n");}}if (elementForType != null && elementForType.getMethodElements() != null && elementForType.getMethodElements().size() > 0) {List methodElements = elementForType.getMethodElements();for (ExecutableElement methodElement : methodElements) {int[] resIds = methodElement.getAnnotation(OnClick.class).value();String methodName = methodElement.getSimpleName().toString();for (int resId : resIds) {stringBuffer.append("(target.findViewById(" + resId + ")).setOnClickListener(new View.OnClickListener() {\n");stringBuffer.append("public void onClick(View p0) {\n");stringBuffer.append("target." + methodName + "(p0);\n");stringBuffer.append("}\n});\n");}}}stringBuffer.append("}\n}\n");return stringBuffer;}
​
​private HashMap findAnnotationForActivity(RoundEnvironment roundEnvironment) {HashMap hashMap = new HashMap<>();//获取到所有被BindView标记了的View元素Set bindViewSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);//获取所有被OnClick标记了的Method元素Set onClickSet = roundEnvironment.getElementsAnnotatedWith(OnClick.class);for (Element element : bindViewSet) {VariableElement bindView = (VariableElement) element;//获取含有该BindView标记对应的ActivityTypeElement typeElement = (TypeElement) element.getEnclosingElement();//获取该Activity中所有被注解标记的元素ElementForType elementForType = hashMap.get(typeElement);List variableElementList;if (elementForType != null) {//获取该Activity中所有被BindView标记的View元素variableElementList = elementForType.getViewElements();if (variableElementList == null) {variableElementList = new ArrayList<>();elementForType.setViewElements(variableElementList);}} else {//若ElementForType不存在则创建elementForType = new ElementForType();variableElementList = new ArrayList<>();elementForType.setViewElements(variableElementList);//判断hashmap中是否保存了elementForType和typeElementif (!hashMap.containsKey(typeElement)) {hashMap.put(typeElement, elementForType);}}variableElementList.add(bindView);}//onclick同理for (Element element : onClickSet) {ExecutableElement onClick = (ExecutableElement) element;//获取含有该OnClick标记对应的ActivityTypeElement typeElement = (TypeElement) element.getEnclosingElement();//获取该Activity中所有被注解标记的元素ElementForType elementForType = hashMap.get(typeElement);List executableElementList;if (elementForType != null) {//获取该Activity中所有被OnClick标记的Method元素executableElementList = elementForType.getMethodElements();if (executableElementList == null) {executableElementList = new ArrayList<>();elementForType.setMethodElements(executableElementList);}} else {//若ElementForType不存在则创建elementForType = new ElementForType();executableElementList = new ArrayList<>();elementForType.setMethodElements(executableElementList);//判断hashmap中是否保存了elementForType和typeElementif (!hashMap.containsKey(typeElement)) {hashMap.put(typeElement, elementForType);}}executableElementList.add(onClick);}return hashMap;}
}
​

ButterKnife在编译阶段会为每个使用了BindView、OnClick等注解的类生成一个新类,因此创建了ElementForType用以封装同一个Activity中的所有被BindView+OnCLick等注解的元素。

/*** 此类用以封装同一个Activity中的所有被BindView+OnCLick注解的元素*/
​
public class ElementForType {//此出的VariableElement代表的控件元素private List viewElements;//此处的ExecutableElement代表的方法元素private List methodElements;
​public ElementForType() {}public ElementForType(List viewElements, List methodElements) {this.viewElements = viewElements;this.methodElements = methodElements;}public List getViewElements() {return viewElements;}public void setViewElements(List viewElements) {this.viewElements = viewElements;}public List getMethodElements() {return methodElements;}public void setMethodElements(List methodElements) {this.methodElements = methodElements;}
}
​

定义ButterKnife

由于APT技术是在编译期替我们生成的一个新类,因此需要注意的是ButterKnife.java中的newClassName类名必须要和AnnotationProcess.java中的newClassName一致,否则无法生效。

public class ButterKnife {public static void bind(Context context){//获取类名String className = context.getClass().getName();//获取生成的类的构造器String newClassName = className + "$$ButterKnife";Constructor constructor = null;try {constructor = Class.forName(newClassName).getConstructor();constructor.newInstance(context);} catch (Exception e) {e.printStackTrace();}}
}

使用Butterknife

使用和ButterKnife一样即可

public class MainActivity extends AppCompatActivity {@BindView(R.id.btn1)private Button btn1;@BindView(R.id.btn2)private Button btn2;@BindView(R.id.btn3)private Button btn3;@BindView(R.id.text)private EditText text;
​@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);text.setText("xixi");}@OnClick({R.id.btn1,R.id.btn2,R.id.btn3})public void onClick(View view){switch (view.getId()){case R.id.btn1:Log.d("kkjj","按钮1");break;case R.id.btn2:Log.d("kkjj","按钮1");break;case R.id.btn3:Log.d("kkjj","按钮1");break;}}
}
​

上文解析了Android架构设计技能中;APT技术实现butterknife;更多的Android框架学习及高级Android进阶;可参考《Android核心技术手册》里面内容包含上千个技术知识点。点击查看》》》

BUtterKnife通过注解+注解处理器的方式在编译期动态地生成XXX$$ButterKnife.java文件,在此文件中替开发者实现了findViewById、setOnClickListener等等操作。

所以ButterKnife等三方框架仍然通过findViewById、setOnClickListener操作实现的view绑定,监听注册。因此并没有绕过Android原生的开发规则。

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...