APT案例之點擊事件

目錄介紹

  • 01.建立項目步驟php

    • 1.1 項目搭建
    • 1.2 項目功能
  • 02.自定義註解
  • 03.建立Processor
  • 04.compiler配置文件
  • 05.編譯jar
  • 06.如何使用
  • 07.編譯生成代碼
  • 08.部分源碼說明java

    • 8.1 Process類-process方法
    • 8.2 OnceProxyInfo代理類
    • 8.3 OnceMethod類

好消息

  • 博客筆記大彙總【16年3月到至今】,包括Java基礎及深刻知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,固然也在工做之餘收集了大量的面試題,長期更新維護而且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
  • 連接地址:https://github.com/yangchong2...
  • 若是以爲好,能夠star一下,謝謝!固然也歡迎提出建議或者問題,萬事起於忽微,量變引發質變!

關於apt實踐與總結開源庫地址

https://github.com/yangchong2...

00.註解系列博客彙總

0.1 註解基礎系列博客

  • 01.Annotation註解詳細介紹
  • [02.Dagger2深刻分析,待更新]()
  • 03.註解詳細介紹android

    • 什麼是註解,註解分類有哪些?自定義註解分類?運行註解案例展現分析,以一個最簡單的案例理解註解……使用註解替代枚舉,使用註解限定類型
  • 04.APT技術詳解git

    • 什麼是apt?理解註解處理器的做用和用途……android-apt被替代?annotationProcessor和apt區別? 什麼是jack編譯方式?
  • 06.自定義annotation註解github

    • @Retention的做用?@Target(ElementType.TYPE)的解釋,@Inherited註解能夠被繼承嗎?Annotation裏面的方法爲什麼不能是private?
  • 07.註解之兼容kotlin面試

    • 後期更新
  • 08.註解之處理器類Processorsegmentfault

    • 處理器類Processor介紹,重要方法,Element的做用,修飾方法的註解和ExecutableElement,瞭解修飾屬性、類成員的註解和VariableElement……
  • 10.註解遇到問題和解決方案api

    • 沒法引入javax包下的類庫,成功運行一次,修改代碼後再運行就報錯
  • 11.註解代替枚舉markdown

    • 在作內存優化時,推薦使用註解代替枚舉,由於枚舉佔用的內存更高,如何說明枚舉佔用內存高呢?這是爲何呢?
  • 12.註解練習案例開源代碼app

    • 註解學習小案例,比較系統性學習註解而且應用實踐。簡單應用了運行期註解,經過註解實現了setContentView功能;簡單應用了編譯器註解,經過註解實現了防暴力點擊的功能,同時支持設置時間間隔;使用註解替代枚舉;使用註解一步步搭建簡單路由案例。結合相應的博客,在來一些小案例,今後應該對註解有更加深刻的理解……
  • 13 ARouter路由解析

    • 比較詳細地分析了阿里路由庫
  • 14 搭建路由條件

    • 爲什麼須要路由?實現路由方式有哪些,這些方式各有何優缺點?使用註解實現路由須要具有的條件以及簡單原理分析……
  • 15 經過註解去實現路由跳轉

    • 自定義Router註解,Router註解裏有path和group,這即是仿照ARouter對路由進行分組。而後看看註解生成的代碼,手寫路由跳轉代碼。
  • 16 自定義路由Processor編譯器

    • Processor介紹,重要方法,Element的做用,修飾方法的註解和ExecutableElement
  • 17 利用apt生成路由映射文件

    • 在Activity類上加上@Router註解以後,即可經過apt來生成對應的路由表,那麼到底是如何生成的代碼呢?
    • 在組件化開發中,有多個module,爲什麼要在build.gradle配置moduleName,又是如何經過代碼拿到module名稱?
    • process處理方法如何生成代碼的,又是如何寫入具體的路徑,寫入文件的?
    • 看完這篇文章,應該就可以理解上面這些問題呢!
  • 18 路由框架的設計和初始化

    • 編譯期是在你的項目編譯的時候,這個時候尚未開始打包,也就是你沒有生成apk呢!路由框架在這個時期根據註解去掃描全部文件,而後生成路由映射文件。這些文件都會統一打包到apk裏,app運行時期作的東西也很多,但總而言之都是對映射信息的處理,如執行執行路由跳轉等。那麼如何設計框架呢?
    • 生成的註解代碼,又是如何把這些路由映射關係拿到手,或者說在何時拿到手比較合適?爲什麼註解須要進行初始化操做?
    • 如何獲得獲得路由表的類名,如何獲得全部的routerAddress---activityClass映射關係?
  • [19 路由框架設計注意要點]()

    • 須要注意哪些要點?
  • 20 爲什麼須要依賴注入

    • 有哪些注入的方式能夠解耦,你能想到多少?路由框架爲什麼須要依賴注入?路由爲什麼用註解進行依賴注入,而不是用反射方式注入,或者經過構造方法注入,或者經過接口方式注入?
  • 21 Activity屬性注入

    • 在跳轉頁面時,如何傳遞intent參數,或者如何實現跳轉回調處理邏輯?

01.建立項目步驟

1.1 項目搭建

  • 首先建立一個Android項目。而後給咱們的項目增長一個module,必定要記得是Java Library。由於APT須要用到jdk下的 【 javax.~ 】包下的類,這在AndroidSdk中是沒有的。
  • 必定要注意:須要說明的是:咱們的目的是寫一個Android庫,APT Moudle是java Library,不能使用Android API。因此還須要建立一個Android Library,負責框架主體部分. 而後由Android Library引用APT jar包。
  • 項目目錄結構如圖:

    • app:Demo
    • AptAnnotation:java Library主要放一些項目中須要用到的自定義註解及相關代碼
    • AptApi:Android Library. OnceClick是咱們真正對外發布並交由第三方使用的庫,它引用了apt-jar包
    • AptCompiler:java Library主要是應用apt技術處理註解,生成相關代碼或者相關源文件,是核心所在。

1.2 項目功能

  • 在必定時間內,按鈕點擊事件只能執行一次。未到指定時間,不執行點擊事件。

02.自定義註解

  • 建立Annotation Module,須要建立一個Java Library,名稱可爲annotation,主要放一些項目中須要用到的自定義註解及相關代碼
  • 新建一個類,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();
}
```

03.建立Processor

  • 建立Compiler Module,須要再建立一個Java Library,名稱可爲compiler,主要是應用apt技術處理註解,生成相關代碼或者相關源文件,是核心所在。
  • 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();
    }
}
```

04.compiler配置文件

  • build.gradle文件配置

    • auto-service的做用是向系統註冊processor(自定義註解處理器),執行編譯時使用processor進行處理。
    • javapoet提供了一套生成java代碼的api,利用這些api處理註解,生成新的代碼或源文件。
    • OnceClickAnnotation是上文建立的註解module。
    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"

05.編譯jar

  • 這裏有一個坑,主Module是不能夠直接引用這個java Module的。(直接引用,能夠成功運行一次~修改代碼之後就不能運行了)而如何單獨編譯這個java Module呢?在編譯器Gradle視圖裏,找到Module apt下的build目錄下的Build按鈕。雙擊運行。

    • 代碼沒有問題編譯經過的話,會有BUILD SUCCESS提示。生成的jar包在 apt 下的build目錄下的libs下。將apt.jar拷貝到app下的libs目錄,右鍵該jar,點擊Add as Library,添加Library

06.如何使用

  • 代碼以下所示

    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");
        }
    }

07.編譯生成代碼

  • 編譯以後生成的代碼路徑,在項目中的build文件夾,如圖所示

    • image
  • 編譯以後生成的代碼

    // 編譯生成的代碼,不要修改
    // 更多內容: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);
                    }
                }});
            }
      }
    
    }

08.部分源碼說明

8.1 Process類-process方法

  • 當某個類Activity使用了@OnceClick註解以後,咱們就應該爲其生成一個對應的代理類,代理類實現咱們框架的功能:爲某個View設置點擊事件,而且這個點擊事件必定時間內只能執行一次。因此,一個代理類可能有多個須要處理的View。
  • 先看process代碼:

    • ProxyInfo對象:存放生成代理類的必要信息,並生成代碼。
    • getProxyMap方法:使用參數roundEnv,遍歷全部@OnceClick註解,並生成代理類ProxyInfo的Map。
    • writeCode方法:真正生成代碼的方法。
    • 總結一下:編譯時,取得全部須要生成的代理類信息。遍歷代理類集合,根據代理類信息,生成代碼。
    @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;
    }

8.2 OnceProxyInfo代理類

  • 其實這個類,纔是這個框架的重中之重,由於生成什麼代碼,全靠這個類說了算。這個類也沒什麼好講的,就是用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);
        }
    }

8.3 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;
        }
    
    }

關於其餘內容介紹

01.關於博客彙總連接

02.關於個人博客

關於apt實踐與總結開源庫地址

https://github.com/yangchong2...

相關文章
相關標籤/搜索