01.创建项目步骤
08.部分源码说明
03.注解详细介绍
04.APT技术详解
06.自定义annotation注解
07.注解之兼容kotlin
08.注解之处理器类Processor
10.注解遇到问题和解决方案
11.注解代替枚举
12.注解练习案例开源代码
13 ARouter路由解析
14 搭建路由条件
15 通过注解去实现路由跳转
16 自定义路由Processor编译器
17 利用apt生成路由映射文件
18 路由框架的设计和初始化
[19 路由框架设计注意要点]()
20 为何需要依赖注入
21 Activity属性注入
项目目录结构如图:
新建一个类,OnceClick。就是我们自定义的注解。
/** * <pre> * @author 杨充 * blog : https://github.com/yangchong211 * time : 2017/06/21 * desc : 一定time时间内该点击事件只能执行一次 * revise:
*/ //@Retention用来修饰这是一个什么类型的注解。这里表示该注解是一个编译时注解。 @Retention(RetentionPolicy.CLASS) //@Target用来表示这个注解可以使用在哪些地方。 // 比如:类、方法、属性、接口等等。这里ElementType.METHOD 表示这个注解可以用来修饰:方法 @Target(ElementType.METHOD) //这里的interface并不是说OnceClick是一个接口。就像申明类用关键字class。申明注解用的就是@interface。 public @interface OnceClick { //返回值表示这个注解里可以存放什么类型值 int value(); } ```
Processor是用来处理Annotation的类。继承自AbstractProcessor。
/** * <pre> * @author 杨充 * blog : https://github.com/yangchong211 * time : 2017/06/21 * desc : 自定义Processor编译器 * revise:
*/ @SupportedSourceVersion(SourceVersion.RELEASE_7) public class OnceClickProcessor extends AbstractProcessor { private Messager messager; private Elements elementUtils; @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); messager = processingEnv.getMessager(); elementUtils = processingEnv.getElementUtils(); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //获取proxyMap Map<String, OnceProxyInfo> proxyMap = getProxyMap(roundEnv); //遍历proxyMap,并生成代码 for (String key : proxyMap.keySet()) { OnceProxyInfo proxyInfo = proxyMap.get(key); writeCode(proxyInfo); } return true; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); types.add(OnceClick.class.getCanonicalName()); return types; } @Override public SourceVersion getSupportedSourceVersion() { return super.getSupportedSourceVersion(); } } ```
build.gradle文件配置
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.google.auto.service:auto-service:1.0-rc3' implementation 'com.squareup:javapoet:1.10.0' implementation project(':OnceClickAnnotation') } sourceCompatibility = "7" targetCompatibility = "7"
这里有一个坑,主Module是不可以直接引用这个java Module的。(直接引用,可以成功运行一次~修改代码以后就不能运行了)而如何单独编译这个java Module呢?在编译器Gradle视图里,找到Module apt下的build目录下的Build按钮。双击运行。
代码如下所示
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化OnceClick,并设置点击事件间隔是2秒 OnceInit.once(this,2000); } @OnceClick(R.id.tv_1) public void Click1(){ Log.d("tag--------------------","tv_1"); } @OnceClick(R.id.tv_2) public void Click2(View v){ Log.d("tag--------------------","tv_2"); } }
编译之后生成的代码路径,在项目中的build文件夹,如图所示
编译之后生成的代码
// 编译生成的代码,不要修改 // 更多内容:https://github.com/yangchong211 package com.ycbjie.ycapt; import android.view.View; import com.ycbjie.api.Finder; import com.ycbjie.api.AbstractInjector; public class MainActivity$$_Once_Proxy<T extends MainActivity> implements AbstractInjector<T> { public long intervalTime; @Override public void setIntervalTime(long time) { intervalTime = time; } @Override public void inject(final Finder finder, final T target, Object source) { View view; view = finder.findViewById(source, 2131165325); if(view != null){ view.setOnClickListener(new View.OnClickListener() { long time = 0L; @Override public void onClick(View v) { long temp = System.currentTimeMillis(); if (temp - time >= intervalTime) { time = temp; target.Click1(); } }}); } view = finder.findViewById(source, 2131165326); if(view != null){ view.setOnClickListener(new View.OnClickListener() { long time = 0L; @Override public void onClick(View v) { long temp = System.currentTimeMillis(); if (temp - time >= intervalTime) { time = temp; target.Click2(v); } }}); } } }
先看process代码:
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //获取proxyMap Map<String, OnceProxyInfo> proxyMap = getProxyMap(roundEnv); //遍历proxyMap,并生成代码 for (String key : proxyMap.keySet()) { OnceProxyInfo proxyInfo = proxyMap.get(key); //写入代码 writeCode(proxyInfo); } return true; }
其实这个类,才是这个框架的重中之重,因为生成什么代码,全靠这个类说了算。这个类也没什么好讲的,就是用StringBuidler拼出一个类来。ProxyInfo保存的是类信息,方法信息我们用List methods保存。然后根据这些信息生成类。
public class OnceProxyInfo { private String packageName; private String targetClassName; private String proxyClassName; private TypeElement typeElement; private List<OnceMethod> methods; private static final String PROXY = "_Once_Proxy"; OnceProxyInfo(String packageName, String className) { this.packageName = packageName; this.targetClassName = className; this.proxyClassName = className + "$$" + PROXY; } String getProxyClassFullName() { return packageName + "." + proxyClassName; } String generateJavaCode() throws OnceClickException { StringBuilder builder = new StringBuilder(); builder.append("// 编译生成的代码,不要修改/n"); builder.append("// 更多内容:https://github.com/yangchong211/n"); builder.append("package ").append(packageName).append(";/n/n"); //写入导包 builder.append("import android.view.View;/n"); builder.append("import com.ycbjie.api.Finder;/n"); builder.append("import com.ycbjie.api.AbstractInjector;/n"); builder.append('/n'); builder.append("public class ").append(proxyClassName) .append("<T extends ").append(getTargetClassName()).append(">") .append(" implements AbstractInjector<T>").append(" {/n"); builder.append('/n'); generateInjectMethod(builder); builder.append('/n'); builder.append("}/n"); return builder.toString(); } private String getTargetClassName() { return targetClassName.replace("$", "."); } private void generateInjectMethod(StringBuilder builder) throws OnceClickException { builder.append(" public long intervalTime; /n"); builder.append('/n'); builder.append(" @Override /n") .append(" public void setIntervalTime(long time) {/n") .append(" intervalTime = time;/n } /n"); builder.append('/n'); builder.append(" @Override /n") .append(" public void inject(final Finder finder, final T target, Object source) {/n"); builder.append(" View view;"); builder.append('/n'); //这一步是遍历所有的方法 for (OnceMethod method : getMethods()) { builder.append(" view = ") .append("finder.findViewById(source, ") .append(method.getId()) .append(");/n"); builder.append(" if(view != null){/n") .append(" view.setOnClickListener(new View.OnClickListener() {/n") .append(" long time = 0L;/n"); builder.append(" @Override/n") .append(" public void onClick(View v) {/n"); builder.append(" long temp = System.currentTimeMillis();/n") .append(" if (temp - time >= intervalTime) {/n" + " time = temp;/n"); if (method.getMethodParametersSize() == 1) { if (method.getMethodParameters().get(0).equals("android.view.View")) { builder.append(" target.") .append(method.getMethodName()).append("(v);"); } else { throw new OnceClickException("Parameters must be android.view.View"); } } else if (method.getMethodParametersSize() == 0) { builder.append(" target.") .append(method.getMethodName()).append("();"); } else { throw new OnceClickException("Does not support more than one parameter"); } builder.append("/n }/n") .append(" }") .append("});/n }/n"); } builder.append(" }/n"); } TypeElement getTypeElement() { return typeElement; } void setTypeElement(TypeElement typeElement) { this.typeElement = typeElement; } List<OnceMethod> getMethods() { return methods == null ? new ArrayList<OnceMethod>() : methods; } void addMethod(OnceMethod onceMethod) { if (methods == null) { methods = new ArrayList<>(); } methods.add(onceMethod); } }
需要讲的一点是,每一个使用了@OnceClick注解的Activity或View,都会为其生成一个代理类,而一个代理中有可能有很多个@OnceClick修饰的方法,所以我们专门为每个方法有创建了一个javaBean用于保存方法信息:
public class OnceMethod { private int id; private String methodName; private List<String> methodParameters; OnceMethod(int id, String methodName, List<String> methodParameters) { this.id = id; this.methodName = methodName; this.methodParameters = methodParameters; } int getMethodParametersSize() { return methodParameters == null ? 0 : methodParameters.size(); } int getId() { return id; } String getMethodName() { return methodName; } List<String> getMethodParameters() { return methodParameters; } }