首先是工程结构,肯定要定义一个lib module。
其次定义我们的注解类:
有了这个注解的类,我们就可以在我们的MainAcitivity先用起来,虽然此时这个注解还并未起到什么作用。
到这里要稍微想一下,此时我们要做的是 通过注解来将R.id.xx 赋值给对应的field,也就是你定义的那些view对象(例如红框中的tv),对于我们的lib工程来说,因为是MainActivity 要依赖lib,自然你lib不可以依赖Main所属的app工程了,这里有2个原因:
public class BindingView {
public static void init(Activity activity) {
Field[] fields = activity.getClass().getDeclaredFields();
for (Field field : fields) {
//获取 被注解
BindView annotation = field.getAnnotation(BindView.class);
if (annotation != null) {
int viewId = annotation.value();
field.setAccessible(true);
try {
field.set(activity, activity.findViewById(viewId));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
最后我们在宿主的MainActivity中调用一下这个方法 即可:
到这里其实有人就要问了,这个运行时注解看起来也不难啊,为啥好像用的人不是很多?问题就出在刚才反射的那堆方法里,反射大家都知道 会对Android运行时带来一些性能损耗,而这里的代码是一段循环, 也就是说这里的代码会随着你使用lib的Activity的界面复杂程度的提高 而变得越来越慢,这是一个会随着你界面复杂度提高而逐步劣化的过程, 单次反射对于今天的手机来说几乎已经不存在什么性能消耗了,但是这种for循环中使用反射还是尽量少用。
然后在我们的BindingView(注意我们的BindingView是在lib module下的)中来调用这个方法不就解决这个反射的问题了吗?
但是这里会有个问题 就是你既然是一个lib 你不能依赖宿主 ,所以在lib Module 中你其实拿不到 MainActivityViewBinding 这个类的,还是得利用反射。
可以看一下上面注释掉的代码,为啥不直接字符串写死?因为你是lib库你当然得是动态的,不然怎么给别人用?其实就是获取宿主的class名称然后加上一个固定的后缀ViewBinding 即可。这个时候 我们就拿到这个Binding的class了,对吧,剩下就是调用构造方法即可。
public class BindingView {
public static void init(Activity activity) {
try {
Class bindingClass = Class.forName(activity.getClass().getCanonicalName() + "ViewBinding");
Constructor constructor = bindingClass.getDeclaredConstructor(activity.getClass());
constructor.newInstance(activity);
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
看下此时的代码结构:
有人这里要问,这里你不还是用了反射么,对! 这里虽然用了反射,但是我这里的反射只会调用一次,不管你的activity有都少field,在我这里反射方法都只会执行一次。所以性能肯定是比之前的方案要快很多倍的。接着看,虽然此刻代码可以正常运行,但是还有一个问题, 虽然我可以在lib中调用到我们app宿主的类的构造方法,但是,宿主的这个类依旧是我们手写的啊?那你这个lib库 还是没有起到任何可以让我们少写代码的作用。
这个时候就需要我们的apt 出场了,也就是编译期注解的核心部分了。我们创建一个Java Library,注意是Java lib不是android lib,然后在app module中引入他。
注意 引入的方式 不是imp了,是annotation processor ;
然后我们来修改一下lib_processor,首先创建一个 注解处理类:
再创建文件resources/META-INF/services/javax.annotation.processing.Processor ,这里要注意 文件夹创建不要写错了。
然后再这个Processor指定 一下我们的注解处理器即可:
到这里还没完,我们得告诉这个注解处理器 只处理我们的BindView注解即可,否则这个注解处理器默认处理全部注解 速度就太慢了,但是此时 我们的BindView这个注解类还在lib仓里面,显然我们要调整一下我们的工程结构:
我们再新建一个Javalib,只放BindView即可,然后让我们的lib_processor和app 都依赖这个lib_interface即可。再稍微修改一下代码,此时我们是编译期处理,所Policy不用是runtime了。
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
public class BindingProcessor extends AbstractProcessor {
Messager messager;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
messager = processingEnvironment.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, " BindingProcessor init");
super.init(processingEnvironment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
//要支持哪些注解
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(BindView.class.getCanonicalName());
}
}
到此我们的大部分工作就处理完毕了。再看一下代码结构(这里的代码结构一定要理解清楚为什么这样设计,否则你是学不会编译期注解的)。
我们现在已经能够做到 通过 lib 这个sdk 调用到MainActivityViewBinding这个里面的方法,但是他 还在app仓是我们手写的,不太智能,还没办法用。我们需要在注解处理器里面 ,动态的生成这个类,只要能完成这个步骤,那我们的SDK也就基本完成了。
这里要提一下,很多人注解始终学不会就是卡在这里,因为太多的文章或者教程上来就是Javapoet 那一套代码,压根学不会,或者只能复制粘贴别人的东西,稍微变动一下就不会了,其实这里最佳的学习方式是先用StringBuffer 字符串拼接的方式 拼出我们想要的代码就可以了,通过这个字符串拼接的过程 来理解对应的api以及生成java代码的思路,然后最后再用JavaPoet来优化代码即可。
我们可以先思考一下, 如果用字符串拼接的方式来做这个生成类的操作要完成哪些步骤。
public class BindingProcessor extends AbstractProcessor {
Messager messager;
Filer filer;
Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
//主要是输出一些重要的日志使用
messager = processingEnvironment.getMessager();
//你就理解成最终我们写java文件 要用到的重要 输出参数即可
filer = processingEnvironment.getFiler();
//一些方便的utils方法
elementUtils = processingEnvironment.getElementUtils();
//这里要注意的是Diagnostic.Kind.ERROR 是可以让编译失败的 一些重要的参数校验可以用这个来提示用户你哪里写的不对
messager.printMessage(Diagnostic.Kind.NOTE, " BindingProcessor init");
super.init(processingEnvironment);
}
private void generateCodeByStringBuffer(String className, List<Element> elements) throws IOException {
//你要生成的类 要和 注解的类 同属一个package 所以还要取 package的名称
String packageName = elementUtils.getPackageOf(elements.get(0)).getQualifiedName().toString();
StringBuffer sb = new StringBuffer();
// 每个java类 的开头都是package sth...
sb.append("package ");
sb.append(packageName);
sb.append(";\n");
// public class XXXActivityViewBinding {
final String classDefine = "public class " + className + "ViewBinding { \n";
sb.append(classDefine);
//定义构造函数的开头
String constructorName = "public " + className + "ViewBinding(" + className + " activity){ \n";
sb.append(constructorName);
//遍历所有element 生成诸如 activity.tv=activity.findViewById(R.id.xxx) 之类的语句
for (Element e : elements) {
sb.append("activity." + e.getSimpleName() + "=activity.findViewById(" + e.getAnnotation(BindView.class).value() + ");\n");
}
sb.append("\n}");
sb.append("\n }");
//文件内容确定以后 直接生成即可
JavaFileObject sourceFile = filer.createSourceFile(className + "ViewBinding");
Writer writer = sourceFile.openWriter();
writer.write(sb.toString());
writer.close();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// key 就是使用注解的class的类名 element就是使用注解本身的元素 一个class 可以有多个使用注解的field
Map<String, List<Element>> fieldMap = new HashMap<>();
// 这里 获取到 所有使用了 BindView 注解的 element
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
//取到 这个注解所属的class的Name
String className = element.getEnclosingElement().getSimpleName().toString();
//取到值以后 判断map中 有没有 如果没有就直接put 有的话 就直接在这个value中增加一个element
if (fieldMap.get(className) != null) {
List<Element> elementList = fieldMap.get(className);
elementList.add(element);
} else {
List<Element> elements = new ArrayList<>();
elements.add(element);
fieldMap.put(className, elements);
}
}
//遍历map,开始生成辅助类
for (Map.Entry<String, List<Element>> entry : fieldMap.entrySet()) {
try {
generateCodeByStringBuffer(entry.getKey(), entry.getValue());
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
//要支持哪些注解
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(BindView.class.getCanonicalName());
}
}
最后看下效果:
虽然生成的代码格式不太好看,但是运行起来是ok的。这里要注意一下Element 这个接口,实际上使用编译期注解的时候 如果能够理解了Element,那后续的工作就简单不少。
主要关注Element的这5个子类即可,举个例子:
package com.smart.annotationlib_2;//PackageElement |表示一个包程序元素
// TypeElement 表示一个类或接口程序元素。
public class VivoTest {
//VariableElement |表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。
int a;
//VivoTest 这个方法 :ExecutableElement|表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
//int b 这个函数参数: TypeParameterElement |表示一般类、接口、方法或构造方法元素的形式类型参数。
public VivoTest(int b ) {
this.a = b;
}
}
private void generateCodeByJavapoet(String className, List<Element> elements) throws IOException {
//声明构造方法
MethodSpec.Builder constructMethodBuilder =
MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).addParameter(ClassName.bestGuess(className), "activity");
//构造方法里面 增加语句
for (Element e : elements) {
constructMethodBuilder.addStatement("activity." + e.getSimpleName() + "=activity.findViewById(" + e.getAnnotation(BindView.class).value() + ");");
}
//声明类
TypeSpec viewBindingClass =
TypeSpec.classBuilder(className + "ViewBinding").addModifiers(Modifier.PUBLIC).addMethod(constructMethodBuilder.build()).build();
String packageName = elementUtils.getPackageOf(elements.get(0)).getQualifiedName().toString();
JavaFile build = JavaFile.builder(packageName, viewBindingClass).build();
build.writeTo(filer);
}
这里要提一下,现在越来越多的人使用Kotlin语言开发app,你甚至可以使用https://github.com/square/kotlinpoet 来直接生成Kotlin代码。有兴趣的可以尝试一下。
作者:vivo互联网客户端团队-Wu Yue
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
Unity自动旋转动画1.开门需要门把手先动,门再动2.关门需要门先动,门把手再动3.中途播放过程中不可以再次进行操作觉得太复杂?查看我的文章开关门简易进阶版效果:如果这个门可以直接打开的话,就不需要放置"门把手"如果门把手还有钥匙需要旋转,那就可以把钥匙放在门把手的"门把手",理论上是可以无限套娃的可调整参数有:角度,反向,轴向,速度运行时点击Test进行测试自己写的代码比较垃圾,命名与结构比较拉,高手轻点喷,新手有类似的需求可以拿去做参考上代码usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;u
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复
在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定