腾讯三面:ButterKnife为什么执行效率为什么比其他注入框架高?它的原理是什么

private ElementType() {
}
}

  • @Retention 该注解的保留策略,有三种选项:

public enum RetentionPolicy {
SOURCE, //被编译器所忽略

CLASS, //被编译器保留至类文件,但不会保留至运行时

RUNTIME //保留至类文件,且保留至运行时,能在运行时反射该注解修饰的对象
}

#####2. 注解处理器
真正处理注解并生成代码的操作都在这里。 在写代码之前我们需要先导入两个重要的库,以及我们的注解模块:

compile ‘com.google.auto.service:auto-service:1.0-rc4’
compile ‘com.squareup:javapoet:1.9.0’
implementation project(‘:lib_annotations’)

新建类RandomProcessor.java:

@AutoService(Processor.class)
public class RandomProcessor extends AbstractProcessor{

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

@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}

@Override
public Set getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}

  • @AutoService @AutoService(Processor.class)会告诉编译器该注解处理器的存在,并在编译时自动在META-INF/services下生成javax.annotation.processing.Processor文件,文件的内容为

com.rhythm7.lib_compiler.RandomProcessor

也就是说,你所声明的注解处理器都会在被写入这个配置文件中。 这样子,当外部程序装载这个模块的时候,就能通过该模块的jar包下的META-INF/services下找到具体的注解处理器的实现类名,并加载实例化,完成模块的注入。 注解处理器需要实现AbstractProcessor接口,并实现对应的方法

  • init() 可选 在该方法中可以获取到processingEnvironment对象,借由该对象可以获取到生成代码的文件对象, debug输出对象,以及一些相关工具类
  • getSupportedSourceVersion() 返回所支持的java版本,一般返回当前所支持的最新java版本即可
  • getSupportedAnnotationTypes() 你所需要处理的所有注解,该方法的返回值会被process()方法所接收
  • process() 必须实现 扫描所有被注解的元素,并作处理,最后生成文件。该方法的返回值为boolean类型,若返回true,则代表本次处理的注解已经都被处理,不希望下一个注解处理器继续处理,否则下一个注解处理器会继续处理。
    #####初始化
    较详细代码如下:

private static final List<Class<? extends Annotation>> RANDOM_TYPES
= Arrays.asList(RandomInt.class, RandomString.class);

private Messager messager;
private Types typesUtil;
private Elements elementsUtil;
private Filer filer;

private TypeonProcess()per.init(processingEnv);
messager = processingEnv.getMessager();
typesUtil = processingEnv.getTypeUtils();
elementsUtil = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
}

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

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

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

#####处理注解
process()方法中执行以下操作:

1.扫描所有注解元素,并对注解元素的类型做判断

for (Element element : roundEnv.getElementsAnnotatedWith(RandomInt.class)) {
//AnnotatedRandomInt是对被RandomInt注解的Elment的简单封装
AnnotatedRandomInt randomElement = new AnnotatedRandomInt(element);
messager.printMessage(Diagnostic.Kind.NOTE, randomElement.toString());
//判断被注解的类型是否符合要求
if (!element.asType().getKind().equals(TypeKind.INT)) {
messager.printMessage(Diagnostic.Kind.ERROR, randomElement.getSimpleClassName().toString() + “#”

  • randomElement.getElementName().toString() + " is not in valid type int");
    }

//按被注解元素所在类的完整类名为key将被注解元素存储进Map中,后面会根据key生成类文件
String qualifier = randomElement.getQualifiedClassName().toString();
if (annotatedElementMap.get(qualifier) == null) {
annotatedElementMap.put(qualifier, new ArrayList());
}
annotatedElementMap.get(qualifier).add(randomElement);
}

#####生成类文件
将之前以注解所在类为key的map遍历,并以key值为分组生成类文件。

for (Map.Entry<String, List> entry : annotatedElementMap.entrySet()) {
MethodSpec constructor = createConstructor(entry.getValue());
TypeSpec binder = createClass(getClassName(entry.getKey()), constructor);
JavaFile javaFile = JavaFile.builder(getPackage(entry.getKey()), binder).build();
javaFile.writeTo(filer);
}

生成类、构造函数、代码段以及文件都是利用到了javapoet依赖库。当然你也可以选择拼接字符串和自己用文件IO写入,但是用javapoet要更方便得多。

private MethodSpec createConstructor(List randomElements) {
AnnotatedRandomElement firstElement = randomElements.get(0);
MethodSpec.Builder builder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(TypeName.get(firstElement.getElement().getEnclosingElement().asType()), “target”);
for (int i = 0; i < randomElements.size(); i++) {
addStatement(builder, randomElements.get(i));
}
return builder.build();
}

private void addStatement(MethodSpec.Builder builder, AnnotatedRandomElement randomElement) {
builder.addStatement(String.format(
“target.%1 s = s = %2 s=s”,
randomElement.getElementName().toString(),
randomElement.getRandomValue())
);
}

private TypeSpec createClass(String className, MethodSpec constructor) {
return TypeSpec.classBuilder(className + “_Random”)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(constructor)
.build();
}

private String getPackage(String qualifier) {
return qualifier.substring(0, qualifier.lastIndexOf(“.”));
}

private String getClassName(String qualifier) {
return qualifier.substring(qualifier.lastIndexOf(“.”) + 1);
}

通过以上几行代码,创建了类文件。在类的构造函数中添加参数(target), 并为每一个被注解元素添加语句"target.%1$s = %2$s",最后通过javaFile.writeTo(filer)完成文件写入。
###3. 调用生成类的方法
在lib_api中新建一个类:RandomUtil.java,添加注入方法:

public static void inject(Object object) {
Class bindingClass = Class.forName(object.getClass().getCanonicalName() + “_Random”);
Constructor constructor = bindingClass.getConstructor(object.getClass());
constructor.newInstance(object);
}

这里利用反射找到了以“Object类名_Random”命名的生成类,并调用它的构造方法。而在我们之前的注解处理器中,我们已在生成类的构造方法中实现了属性的赋值操作。
###4. 使用生成类
在app module中依赖刚才创建的库:

implementation project(‘:lib_annotations’)
implementation project(‘:lib_api’)
annotationProcessor project(‘:lib_compiler’)

在Activity中的使用

public class MainActivity extends AppCompatActivity {
@RandomInt(minValue = 10, maxValue = 1000)
int mRandomInt;

@RandomString
String mRandomString;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

RandomUtil.inject(this);

Log.i("RandomInt ==> ", mRandomInt + “”);
Log.i("RandomString ==> ", mRandomString);
}
}

编译,运行,查看结果:

RandomInt ==>: 700
RandomString ==>: HhRayFyTtt

被注解的元素成功被自动赋值,说明注入成功。
注解处理的使用可参考完整的demo地址
###调试
注解处理器的debug跟普通的代码debug有点不同:

在当前工程路径下输入命令

gradlew --no-daemon -Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac

并在Edit Configurations中新添加一个远程配置(remote),名字随意,端口为5005。 然后点击debug按钮,就可以连接上远程调试器进行Annotation的调试了。

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

Android学习PDF+架构视频+面试文档+源码笔记

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

尾声

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

  • 思维脑图
  • 性能优化学习笔记


  • 性能优化视频

    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
    《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
    )]
    [外链图片转存中…(img-1WKeN1e2-1715361233788)]

  • 性能优化视频
    [外链图片转存中…(img-muZ9Z0v8-1715361233789)]
    当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
    《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值