注解
注解和反射的区别
反射:JAVA 反射机制是在运行状态中,对于任意一个类 (class 文件),都能够知道这个类的所有属性和方法。
注解:降低项目的耦合度;自动完成一些规律性的代码;自动生成 java 代码,减轻开发者的工作量。
而注解需要用到反射:
定义注解,使用注解,读取注解(用到反射)
注解和反射效率问题
反射先 new 类 class,然后在从类里面 new 对象。Class.getMethod (…)还要查找所有的方法。
而注解编译期间就完成了注解的反射工作, jvm 只是读取。
反射的缺点
不安全
编译器没法对反射相关的代码做优化
慢的原因还有安全检查,访问控制等。比如说这个方法你能不能获得,能不能执行等,你传进的参数的类型检查等。比如说在使用反射调用方法的时候,传进的参数需要检查是否符合方法参数类型要求吧?
注解的用法
获取类的注解
URLBuilder. Path path =paramEntity.getClass (). getAnnotation (URLBuilder. Path. class);
1 2 3 4 5 6 7
| Builder(Retrofit retrofit, Method method) { this.retrofit = retrofit; this.method = method; this.methodAnnotations = method.getAnnotations(); this.parameterTypes = method.getGenericParameterTypes(); this.parameterAnnotationsArray = method.getParameterAnnotations(); }
|
获取方法的注解
Method.getAnnotations (); 是获取方法上面对应的注解。
method.getParameterAnnotations ();获取的是方法参数上面的注解,是一个二维数组,第一个维度代表的是方法参数对应的下标,比如,一个方法有3个参数,那0代表第一个参数,1代表第二个参数,2代表第三个参数。
获取泛型参数
method.getGenericParameterTypes (); 获取的是方法参数的类型,里面带有实际的参数类型。
生命周期
@Retention注解保留的生命周期
- SOURCE: 在源文件中有效(即源文件保留)而被编译器丢弃,运行的时候不会在 class 文件里。(编译器在,运行时没有)
- CLASS: 在 class 文件中有效(即 class 保留)
- RUNTIME: 在运行时有效(即运行时保留) 这个范围更大,写这个一般不会有什么问题
生命周期:SOURCE < CLASS < RUNTIME
使用场景
- 一般如果需要在运行时去动态获取注解信息,用 RUNTIME 注解,Retrofit
- 要在编译时进行一些预处理操作,如 ButterKnife,用 CLASS 注解。注解会在 class 文件中存在,但是在运行时会被丢弃
- 做一些检查性的操作,如@Override,用 SOURCE 源码注解。注解仅存在源码级别,在编译的时候丢弃该注解
@Target:注解对象的作用范围。
创建一个注解遵循: public @interface 注解名 {方法参数} (运行时注入)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface getViewTo { int value() default -1; }
@BindView(R.id.textview)
public class Binding { public static void bind(Activity activity) { for (Field field : activity.getClass().getDeclaredFields()) { BindView bindView = field.getAnnotation(BindView.class); if (bindView != null) { try { field.setAccessible(true); field.set(activity, activity.findViewById(bindView.value())); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }
|
编译时注解与运行时注解区别?
保留阶段: 编译时注解在编译期间被存储在. Class 字节码文件中,运行时无法访问。而运行时注解保留到运行时,可在运行时访问。
原理: 编译时注解通过 APT、AbstractProcessor 等机制工作,而运行时注解是 Java 反射机制。
性能: 运行时注解由于使用 Java 反射,对性能有影响。编译时注解对性能没影响。
立物: 运行时注解只需自定义注解处理器即可,不会产生其他文件。而编译时注解通常会立生新的 Java 源文件。
用途: 运行时注解通常在运行时被访问,例如 Retrofit 的运行时注解,需要用的时候才用到。编译时注解如@Override、 @SuppressWarnings 等,在编译期间被处理
AOP(思想)
正如面向对象编程是对常见问题的模块化一样,面向切面编程是对横向的同一问题进行模块化,比如在某个包下的所有类中的某一类方法中都需要解决一个相似的问题,可以通过 AOP 的编程方式对此进行模块化封装,统一解决
AOP 编程的具体使用场景
日志记录
持久化
行为监测
数据验证
缓存
…
比如埋点,记录方法执行的时长。可以定义注解。aop 可以过滤所有被”这个注解”标记的方法和构造器。然后可以根据他提供的方法(注解),将我们想要埋点的日志插入到前面或后面。
aop 三大工具
APT:
解析注解生成新的文件,这其中我们最常见到的了,难度也最低。定义编译期的注解,再通过继承 Proccesor 实现代码生成逻辑,实现了编译期生成代码的逻辑。(DataBinding, Dagger2, ButterKnife, EventBus3)
AspectJ:
编译期和加载时代码方法等前后注入(代表框架: Hugo (Jake Wharton))
javassist:
这层还有 asm,在 class 文件被转化为 dex 文件之前去修改。( [[Gradle插件]] )
APT
Annotation Processing Tool (APT),注解处理工具,javac 中用于编译时扫描和解析 Java 注解的工具。可以自己定义。
在编译阶段执行的,它的原理就是读入 Java 源代码,解析注解,然后生成新的 Java 代码,新生成的 Java 代码最后被编译成 Java 字节码,注解解析器不能改变读入的 Java 类,比如不能加入或删除 Java 方法。
Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解处理逻辑。
APT 的原理:在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者在根据注解元素在编译期输出对应的 Java 代码。
APT 负责处理编译时注解,JavaPoet 用于生成 Java 代码,AutoService 负责注册注解处理器。
AutoServcie
以前要注册注解处理器要在 module 的 META_INFO 目录新建 services 目录,并创建一个名为 Java.annotation.processing.Processor 的文件,然后在文件中写入要注册的注解处理器的全名。
后来 Google 推出了 AutoService 注解库来实现注册注解处理器的注册,通过在注解处理器上加上 @AutoService(Processor.class) 注解,即可在编译时生成 META_INFO 信息。
1 2 3
| @AutoService(Processor.class) public class BindViewProcessor extends AbstractProcessor { }
|
JavaPoet
javaPoet使用指南
eventbus如何用的
JavaPoet比普通方法 (手动字符串拼接)创建更加方便
APT 传统方式(手动字符串拼接)—》生成 java 文件
APT JavaPoet 方式–》生成 Java 文件
Butterknife_Framework_20200225 项目原生
New_Modular_CustomARouter 项目 javaPoet
ButterKnife 的实现
分为四步:
- 定义注解
- 注解处理器处理注解
- 生成 Java 文件
- 引入
定义注解
1 2 3 4 5
| @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView { int value(); }
|
注解处理器处理注解、生成 Java 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| @AutoService(Processor.class) public class BindViewProcessor extends AbstractProcessor {
private Elements mElementsUtils; private Types mTypesUtils; private Filter mFilter; private Messager mMessager;
@Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mElementsUtils = processingEnvironment.getElementUtils(); }
@Override public Set<String> getSupportedAnnotationTypes() { Set<String> hashSet = new HashSet<>(); hashSet.add(BindView.class.getCanonicalName()); return hashSet; }
@Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class); Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>(); for (Element element : elementSet) { VariableElement variableElement = (VariableElement) element; TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement(); Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement); if (variableElementMap == null) { variableElementMap = new HashMap<>(); typeElementMapHashMap.put(typeElement, variableElementMap); } BindView bindAnnotation = variableElement.getAnnotation(BindView.class); int viewId = bindAnnotation.value(); variableElementMap.put(viewId, variableElement); } for (TypeElement key : typeElementMapHashMap.keySet()) { Map<Integer, VariableElement> elementMap = typeElementMapHashMap.get(key); String packageName = ElementUtil.getPackageName(mElementsUtils, key); JavaFile javaFile = JavaFile.builder(packageName, generateCodeByPoet(key, elementMap)).build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } return true; }
private TypeSpec generateCodeByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) { return TypeSpec.classBuilder(ElementUtil.getEnclosingClassName(typeElement) + "ViewBinding") .addModifiers(Modifier.PUBLIC) .addMethod(generateMethodByPoet(typeElement, variableElementMap)) .build(); }
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) { ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString()); String parameter = "_" + StringUtil.toLowerCaseFirstChar(className.simpleName()); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(className, parameter); for (int viewId : variableElementMap.keySet()) { VariableElement element = variableElementMap.get(viewId); String name = element.getSimpleName().toString(); String type = element.asType().toString(); String text = "{0}.{1}=({2})({3}.findViewById({4}));\n"; methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId))); } return methodBuilder.build(); } }
|
引入
1 2 3 4 5 6 7 8 9 10 11 12
| public class ButterKnife { public static void bind(Activity activity) { Class clazz = activity.getClass(); try { Class bindViewClass = Class.forName(clazz.getName() + "ViewBinding"); Method method = bindViewClass.getMethod("bind", clazz); method.invoke(null, activity); } catch (Exception e) { e.printStackTrace(); } } }
|
debug apt
如何debug自定义AbstractProcessor 好用
Android APT 编译期进入debug模式
选 app 或者生成代码的 module
1 2 3 4 5 6
| netstat -aon|findstr "5005" tasklist|findstr "5005" gradlew
gradlew clean :app:assembleDebug -Dorg.gradle.debug=true gradlew clean assembleDebug
|
AspectJ
AspectJ 就是面向切面编程在 Java 中的一种具体实现。
Join point:程序中执行代码插入的点,例如方法调用时或者方法执行时。
Android开发之AOP编程(不错)
1 2 3 4 5 6 7 8 9 10
| private static boolean enabled = true;
@Around("execution(@com.allin.medrecorddossier.aop.annotation.PMUserPermission * *(..))") public Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable { if (enabled) { return joinPoint.proceed(); } return null; }
|
双击
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| @Aspect public class AspectOnClick {
private static Long sLastclick = 0L;
private static final Long FILTER_TIMEM = 500L; private WeakReference<View> weakReference; private boolean checkClick = true; @Pointcut("execution(* android.view.View.OnClickListener.onClick(..))") public void setOnClick() { } @Around("setOnClick()") public void aroundSetOnClick(ProceedingJoinPoint proceedingJoinPoint ) throws Throwable { if (System.currentTimeMillis() - sLastclick >= FILTER_TIMEM) { doClick(proceedingJoinPoint); } else { if (!checkClick || weakReference == null || weakReference.get() != proceedingJoinPoint.getArgs()[0]) { doClick(proceedingJoinPoint); } else {
} } } private void doClick(ProceedingJoinPoint joinPoint) throws Throwable { if (joinPoint.getArgs().length == 0) { joinPoint.proceed(); return; } View lastView = (View) (joinPoint).getArgs()[0]; weakReference = new WeakReference<>(lastView); sLastclick = System.currentTimeMillis(); joinPoint.proceed(); } }
|
IOC (思想)
原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转
- 运行时注入 xUtils, eventBus, springMVC (在运行的时候获取注解、解析注解)
- 源码时注入 android studio 插件
- 编译时注入 butterknife, dagger2 (apt 就是编译时注入,生成文件,运行的时候加载这个文件调方法就可以了)
1 2 3 4 5 6 7 8 9 10 11 12
| public class JettButterknife { public static void bind(Activity activity){ String name=activity.getClass().getName()+"_ViewBinding"; try{ Class<?> aClass=Class.forName(name); IBinder iBinder=(IBinder)aClass.newInstance(); iBinder.bind(activity); }catch(Exception e){
} } }
|