01.建立項目步驟php
08.部分源碼說明java
03.註解詳細介紹android
04.APT技術詳解git
06.自定義annotation註解github
08.註解之處理器類Processorsegmentfault
11.註解代替枚舉markdown
[19 路由框架設計注意要點]()
項目目錄結構如圖:
新建一個類,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; } }