註解的理解與ARouter框架的學習

動手實現一個路由框架

前言

  • 組件化或者模塊化開發模式,已逐漸成爲熱浪的形式,使用這些模式可以讓我們程序更容易的擴展、更方便的測試與維護

  • ARouter是:阿里巴巴自研路由框架,主要解決組件間、模塊間的 界面跳轉 問題。

  • 我們將自己簡單實現一個路由

這個項目對你有什麼幫助?

讓我更加熟悉註解與註解編譯器的過程,也讓我們對以後項目也有了更大的可能延展性

構思

讓我們自己先想着如何實現,本身實現並不複雜。 新建一個Arouter類

Arouter

  1. 既然在使用Arouter的時候我們的調用方式是根據Path來的,那麼我們肯定需要一個Map<String,Class<? extend Activity> class> 來裝載這個集合
  2. 然後我們要對外開啓一個方法來把Activity Put進來。
  3. 處理跳轉,根據map.get(Path) 來跳到相應的Activity 中去。

就這三步,是不是看起來很容易。 問題就只在於如何生成一個自動添加Activity的工具類,然後自動的去執行這個方法。

項目結構

項目結構.png

分爲4個模塊

  • annotation 模塊:用來定義註解

  • compiler 模塊:用來定義Annotation Processor

  • app 模塊 : 主模塊,也是Activity測試模塊

  • Arouter模塊: 路由模塊

    爲什麼要分這麼多模塊?其中 app 模塊是用來測試的,測試新定義的 Annotation Processor 能否成功運行。兩者的目的並不一樣,annotation 是專門定義註解的,而 compiler 是處理註解的。最重要的是,annotation(RetentionPolicy.CLASS,RetentionPolicy.RUNTIME)是需要被編譯到 app 項目的 class 文件中的,而 compiler 沒有必要進入 app 中。所以在gradle中依賴爲compileOnly

  • 在annotation模塊中創建一個註解文件,通過這個註解獲取path

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface BindPath {
    String value();
}
  • 有了註解我們還需要一個compiler 替我們去生成對應的代碼, 我們在創建一個AnotationCompiler類. 編寫註解處理器,在編譯期找到加入註解的類文件,進行處理
@AutoService(Processor.class)  //註冊註解處理器
public class AnotationCompiler extends AbstractProcessor {

    private Filer mFiler;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        System.out.println("jsc");
    }

    /**
     * 聲明這個註解處理器要識別處理的註解
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindPath.class.getCanonicalName());
        return types;
    }

    /**
     * 支持java的源版本
     * @return
     */

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    /**
     * 核心方法
     * @param set
     * @param roundEnvironment
     * @return
     */

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        System.out.println("123");
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
        Map<String,String> map =new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            TypeElement typeElement = (TypeElement) element;
            BindPath bindPath = typeElement.getAnnotation(BindPath.class);
            //對應的key
            String value = bindPath.value();
            //獲取帶包名的類名
            String activityName = typeElement.getQualifiedName().toString();
            map.put(value,activityName);
        }

        if (map.size()>0) {
            Writer writer=null;
            String activityNmae = "ActivityUtil"+System.currentTimeMillis();
            try {
                JavaFileObject classFile = mFiler.createSourceFile("com.example.util." + activityNmae);
                writer = classFile.openWriter();
                writer.write("package com.example.util;\n" +
                        "\n" +
                        "import com.example.arouter2.ARouter;\n" +
                        "import com.example.arouter2.IARouter;\n" +
                        "\n" +
                        "/**\n" +
                        " * @author AlienChao\n" +
                        " * @date 2019/11/19 17:34\n" +
                        " */\n" +
                        "public class "+activityNmae+" implements IARouter {\n" +
                        "    @Override\n" +
                        "    public void putActivity() {");
                Iterator<String> iterator = map.keySet().iterator();
                while (iterator.hasNext()) {
                    String path = iterator.next();
                    String value  = map.get(path);
                    writer.write("\n ARouter.getInstance().putActivity(\""+path+"\","+value +".class);");
                }
                writer.write(" \n}\n" +
                        "}");

            } catch  (Exception e) {
                 e.printStackTrace();
            }finally {
                if (null!=writer) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
        return false;
    }
}

如代碼中所示,要想在編譯期對註解做處理,就需要AnnotationCompiler繼承自AbstractProcessor並通過@AutoService註解進行註冊,然後實現process()方法。process()方法裏的set集合就是編譯期掃描代碼得到的加入了BindPath註解的文件集合
這裏需要注意的是谷歌的 auto service, 在As 3.4- 之前是用1.0-rc3 在As3.4.1+ gradle5.1.1 之後用1.0-rc4。

image-20191120173851791.png

  • compileOnly 所需的只是 @AutoService 這個註解。表示只參與編譯過程並不打包到最終產物 jar 文件中,而註解 @AutoService 的 RetentionPolicy 就是 SOURCE,不會編譯生成到 class 文件中。即便使用 implementation依賴,最終生成的 jar 包中只會多了 auto service 提供的註解,其他 class 文件部分不受影響,因此可以使用 compileOnly 只參與編譯過程。

  • annotationProcessor 這個是新版 gradle 提供的 java plugin 內置的配置項,代替了早期第三方提供的 android-apt 插件。而且,敲重點,在 gradle 5.+ 中將 Annotation Processor 從編譯期 classpath 中去除了,javac 也就無法發現 Annotation Processor。此處如果按照 gradle 4.+ 的寫法,只寫一個 compileOnly 是無法使用 auto service 的 Annotation Processor 的。必須要使用 annotationProcessor 來配置 Annotation Processor 使其生效。

生成完了,那怎麼去執行這個代碼呢??

這裏就比較粗暴一點, 直接 根據完整路徑獲取dex,遍歷dex獲取所有帶包名的類名, 有了類名我們就好辦了,可以根據類名 判斷是否是IARouter 的實現類,去調用 putActivity() 方法

private List<String> getClassName(String packageName) {
        //創建一個class對象的集合
    List<String> classList = new ArrayList<>();
    String path = null;
    try {
       //通過包管理器   獲取到應用信息類然後獲取到APK的完整路徑
       path = context.getPackageManager().getApplicationInfo(
               context.getPackageName(), 0).sourceDir;
       //根據APK的完整路徑獲取到編譯後的dex文件目錄
       DexFile dexfile = new DexFile(path);
       // 獲得編譯後的dex文件中的所有class
       Enumeration entries = dexfile.entries();
       //然後進行遍歷
       int index = 0;

       while (entries.hasMoreElements()) {
           index ++;
           //通過遍歷所有的class的包名
           String name = (String) entries.nextElement();

           // 判斷類的包名是否符合 com.dongnao.util
           if(name.contains(packageName)){
               //如果符合 就添加到集合中
               classList.add(name);
           }
        }
    } catch (Exception e) {
       e.printStackTrace();
    }
    return classList;
}

public void init(Application application){
    context = application;
    List<String> className = getClassName("com.example.util");
    //遍歷
    for (String s : className) {
        try {
            Class<?> aClass = Class.forName(s);
            //判斷這個類是不是IRouter這個接口的子類
            if(IARouter.class.isAssignableFrom(aClass)){
                //通過接口的引用 指向子類的實例
                IARouter iRouter = (IARouter) aClass.newInstance();
                iRouter.putActivity();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

參考資料:

https://blog.csdn.net/ajey2005/article/details/100652260

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章