“終於懂了” 系列:組件化框架 ARouter 完全解析(二)APT技術 一、APT介紹 二、ARouter的APT 四、總結

ARouter系列文章:

“終於懂了” 系列:組件化框架 ARouter 完全解析(一)原理全解

“終於懂了” 系列:組件化框架 ARouter 完全解析(二)APT—幫助類生成

“終於懂了” 系列:組件化框架 ARouter 完全解析(二)AGP/Transform—動態代碼注入

在上一篇《“終於懂了” 系列:組件化框架 ARouter 完全解析(一) 原理詳解》中,詳細介紹了ARouter的核心原理。其中提到了“幫助類”的概念,也就是在運行時生成 用於幫助填充WareHouse路由元信息的類,這裏就涉及到了APT技術。那麼本篇就對這一技術點進行介紹,並詳細分析ARouter中是如何使用APT來生成幫助類的。

一、APT介紹

1.1 APT的理解

APT(Annotation Processing Tool),即 註解處理器,是javac中提供的編譯時掃描和處理註解的工具,它對源代碼文件進行檢測找出其中的註解,然後使用註解進行額外的處理。

註解就像是一個標籤,有很多類型,可以貼在某些元素上面進行標記,並且標籤上可以寫一些信息。APT就是用來處理標籤的工具,在編譯開始後,可以拿到自己所關心的類型的所有標籤,然後根據標籤信息和被標記的元素信息,做一些事情。做那些事呢,這就看你如何寫APT了,你讓他幹啥他就幹啥,通常都是會生成一些幫助類——幫助完成你的目的的類。 後面無論對這種標籤的使用是增加、減少了,每次編譯都會重新走這一過程,而上一次的處理結果會被清空。

宏觀上理解,APT就是javac提供給開發者在編譯時處理註解的一種技術;微觀上,具體到實例中就是指 繼承自javax.annotation.processing.AbstractProcessor 的實現類,即一個處理特定註解的處理器。(下文提到的APT都是宏觀上理解,具體的處理器簡稱爲Processor)

那不使用APT能否完成目的呢? 也是可以的,畢竟APT就是爲了幫助我完成目的,那我自己肯定也是可以完成目的的,只不過有了APT會很省事。例如,上篇提到的幫助類,目的就是爲了收集路由元信息(路由目標類class),我們如果不使用ARouter,那麼就需要自己定義一個moduleA、moduelB共同依賴的muduleX,把需要進行跳轉的XXXActivity這些類的class手工寫代碼保存到muduleX的Map中,key可以用XXXActivity的類名,value就是XXXActivity.class,這樣moduleA、moduelB之間發起跳轉時 就通過想要跳轉的Activity的類名 從muduleX的Map中獲取目標Activity的class,這樣也是能完成 無相互依賴的moduleA、moduelB之前進行頁面跳轉的。

而使用了APT,只要使用註解進行標記即可,無論使用者怎麼標記,每次編譯時都由APT統一處理,不會出錯、也不擔心有遺漏

通常用到APT技術的都是像ARouter這樣的通用能力框架:

  • 提供定義好的註解,例如@Route
  • 提供簡潔的API接口,例如ARouter.getInstance().build("/module/1").navigation()

這樣上層業務使用極爲方便,只需要使用註解進行標記,然後調用API即可。信息如何收集和存儲、如何尋找和使用,框架使用者是不用關心的。

APT還有兩個特點:

  • 獲取註解及生成代碼都是在代碼編譯時候完成的,相比反射在運行時處理註解大大提高了程序性能。
  • 注意APT並不能對源文件進行修改,只能獲取註解信息和被註解對象的信息,然後做一些自定義的處理,例如生成java類。

1.2 APT的原理

我們先來看下Java的編譯過程:

可以看到在Java源碼到class文件之間,需要經過註解處理器的處理,註解處理器生成的代碼也同樣會經過這一過程,最終一起生成class文件。在Android中,class文件還會被打進Dex文件中,最後生成APK文件。

註解處理器的執行是在編譯的初始階段,並且會有多個processor(查看所有註冊的processor:intermediates/annotation_processor_list/debug/annotationProcessors.json)。

那麼我們自定義的註解處理器,如何註冊進去呢?又如何實現一個註解處理器呢?

來看一個簡單的實例:

@AutoService(Processor.class) //把 TestProcessor註冊到編譯器中
@SupportedAnnotationTypes({"com.hfy.test_annotations.TestAnnotation"})//TestProcessor 要處理的註解
@SupportedSourceVersion(SourceVersion.RELEASE_8)//設置jdk環境爲java8
//@SupportedOptions() //一些支持的可選配置項
public class TestProcessor extends AbstractProcessor {
    public static final String ACTIVITY = "android.app.Activity";
    //Filer 就是文件流輸出路徑,當我們用AbstractProcess生成一個java類的時候,我們需要保存在Filer指定的目錄下
    Filer mFiler;
    //類型 相關的工具類。當process執行的時候,由於並沒有加載類信息,所以java文件中的類信息都是用element來代替了。
    //類型相關的都被轉化成了一個叫TypeMirror,其getKind方法返回類型信息,其中包含了基礎類型以及引用類型。
    Types types;
    //Elements 獲取元素信息的工具,比如說一些類信息繼承關係等。
    Elements elementUtils;
    //來報告錯誤、警告以及提示信息
    //用來寫一些信息給使用此註解庫的第三方開發者的
    Messager messager;
   
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnv.getFiler();
        types = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
        if (annotations == null || annotations.size() == 0){
            return false;
        }
        //獲取所有包含 @TestAnnotation 註解的元素
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(TestAnnotation.class);
        
        //每個Processor的獨自的邏輯,其他的寫法一般都是固定的
        parseAnnotation(elements)

        return true;
    }

    //解析註解並生成java文件
    private boolean parseAnnotation(Set<? extends Element> elements) {
       ...
    }
   
}

上面的TestProcessor就是一個典型的註解處理器,繼承自javax.annotation.processing.AbstractProcessor。注意到TestProcessor重寫了init()和process()兩個方法,並且添加了幾個註解。

幾個註解是處理器的註冊和配置

  1. 使用註解@AutoService進行註冊,這樣在編譯這段就會執行這個處理器了。需要依賴com.google.auto.service:auto-service:1.0-rc4' 才能使用@AutoService
  2. 使用註解@SupportedAnnotationTypes 設置 TestProcessor 要處理的註解,要使用目標註解全類名
  3. 使用註解@SupportedSourceVersion設置支持的Java版本、使用註解@SupportedOptions()配置一些支持的可選配置項

重寫的兩個方法:init()、process()是Processor的初始化和處理過程:

  1. init() 方法是初始化處理器,每個註解處理器被初始化的時候都會被調用,通常是這裏使用 處理環境-ProcessingEnvironment 獲取一些工具實例,如上所示是一般通用的寫法:
    • mFiler,就是文件流輸出路徑,當我們用AbstractProcess生成一個java類的時候,我們需要保存在Filer指定的目錄下
    • types,類型 相關的工具類。當process執行的時候,由於並沒有加載類信息,所以java文件中的類信息都是用element來代替了。類型相關的都被轉化成了一個叫TypeMirror,其getKind方法返回類型信息,其中包含了基礎類型以及引用類型。
    • elementUtils,獲取元素信息的工具,比如說一些類信息繼承關係等。
    • messager,來報告錯誤、警告以及提示信息,用來寫一些信息給使用此註解庫的第三方開發者的
  2. process() 方法,註解處理器實際處理方法,在這裏寫處理註解的代碼,以及生成Java文件。它有兩個參數:
    • annotations,是@SupportedAnnotationTypes聲明的註解 和 未被其他Processor消費的註解的子集,也就是剩下的且是本Processor關注的註解,類型是TypeElement
    • roundEnvironment,有關當前和之前處理器的環境信息,可以讓你查詢出包含特定註解的被註解元素
    • 返回值,true表示@SupportedAnnotationTypes中聲明的註解 由此 Processor 消費掉,不會傳給下個Processor。註解不斷向下分發,每個processor都可以決定是否消費掉自己聲明的註解。

在編譯流程進入Processor前,APT會對整個Java源文件進行掃描,這樣就會獲取到 所有添加了的註解和對應被註解的類。 註解和被註解的類,一起被視爲一個元素,即TypeElement,就是process()方法參數annotations的數據類型。 通過TypeElement,我們可以獲取註解的所有信息、被註解類的所有信息,這樣就可以根據這些信息來生成 我們需要的幫助類了

這裏重點是理解Processor的原理,關於註解和Element的知識這裏不過多介紹,可自行了解。

到這裏,Processor的工作流程我們瞭解了,並且其 定義、配置、註冊 這些都是固定的寫法,一般無需過多關注。最重要的就是 process()方法的實現:拿到所有關注的註解元素後,就是每個Processor的獨自的邏輯——解析註解並生成需要的java文件。

二、ARouter的APT

2.1 ARouter工程介紹

ARouter是一個典型的 APT+AGP 框架,有4個module:

  • annotation,定義共業務側使用的註解,例如@Route
  • api,框架的核心邏輯實現 並提供便於業務側使用的Api,即上一篇介紹的內容
  • compiler,定義註解處理器,用於生Java類,就是對APT的使用,即本篇的內容
  • gradle-plugin,定義Android Gradle 插件,用於編譯時在class轉爲dex文件前掃描目標幫助類 並進行代碼注入,將在第三篇中介紹

接下來就來詳細分析 compiler,看看ARouter是如何解析註解並生成幫助類的。

2.2 RouteProcessor

2.2.1 BaseProcessor

有4個Processor,其中 BaseProcessor 是直接繼承自 AbstractProcessor 的,其他都是繼承BaseProcessor:

  • RouteProcessor,處理註解@Route、@Autowired註解,用於生成路由幫助類
  • InterceptorProcessor,處理@Interceptor註解,用於生成攔截器幫助列
  • AutowiredProcessor,處理@Autowired註解,用戶生成處理activity/fragment變量的幫助類

這裏我們重點關注RouteProcessor。先來看下 BaseProcessor:

public abstract class BaseProcessor extends AbstractProcessor {
    Filer mFiler;
    Logger logger;
    Types types;
    Elements elementUtils;
    TypeUtils typeUtils;
    //此Module的名字
    String moduleName = null;
    // 是否生成router doc
    boolean generateDoc;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        types = processingEnv.getTypeUtils();
        elementUtils = processingEnv.getElementUtils();
        typeUtils = new TypeUtils(types, elementUtils);
        logger = new Logger(processingEnv.getMessager());

        // 獲取配置: moduleName、generateDoc
        Map<String, String> options = processingEnv.getOptions();
        if (MapUtils.isNotEmpty(options)) {
            moduleName = options.get(KEY_MODULE_NAME);
            generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
        }

        if (StringUtils.isNotEmpty(moduleName)) {
            moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
            ...
        } else {
            //沒有配置 moduleName,就報錯!
            logger.error(NO_MODULE_NAME_TIPS);
            throw new RuntimeException("ARouter::Compiler >>> No module name, for more information, look at gradle log.");
        }
    }
...
    
    //設置關注的配置項,其中KEY_MODULE_NAME就是在.gradle中配置的
    @Override
    public Set<String> getSupportedOptions() {
        return new HashSet<String>() {{
            this.add(KEY_MODULE_NAME);
            this.add(KEY_GENERATE_DOC_NAME);
        }};
    }
}

BaseProcessor主要是在init()中做了各種工具的初始化,同時獲取了key爲 AROUTER_MODULE_NAME 的配置項 moduleName——module名字。我們注意重寫的getSupportedOptions()方法,它和前面的介紹的@SupportedOptions作用是一致的。 AROUTER_MODULE_NAME 就是路由所在module的gradle文件中配置的:

拿到moduleName有什麼作用呢?上一篇中提到的 根幫助類Provider幫助類攔截器幫助類 的類名就需要moduleName構成:

2.2.2 整體流程

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
    private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta.
    private Map<String, String> rootMap = new TreeMap<>();  // Map of root metas, used for generate class file in order.

    private TypeMirror iProvider = null;
    private Writer docWriter;       // Writer used for write doc

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        ...
        iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();
        ...
    }
    
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                this.parseRoutes(routeElements);
            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

定義了兩個Map:

  • groupMap,此module所有路由組的信息。key爲路由的group,value是此group下所有路由元信息。用於生成 組幫助類
  • rootMap,key是group,value是組幫助類的類名,用於生成 根幫助類

在init()中使用elementUtils獲取了 IProvider接口的類型,用於後面判斷一個類元素是否是IProvider的實現類。

在process()中獲取了添加了@Route的所有Element,然後調用parseRoutes()開始解析。

2.2.3 核心邏輯

在上篇中介紹了 根幫助類的loadInfo方法體內,是用接收的map來put當前module所有路由組幫助類的class。所以我們可以猜想應該是先創建了所有的組幫助類之後,才創建的根幫助類,並且一個module只有一個根幫助類。而組幫助類,則應該是先遍歷此module所有 @Route註解類,找到相同group的路由創建組幫助類。

另外這裏有個問題是,Java文件要如何生成呢?這裏就要介紹 javepoet 這個庫了:

  • JavaPoet是一款可以自動生成Java文件的第三方依賴
  • 簡潔易懂的API,上手快
  • 讓繁雜、重複的Java文件,自動化生成,提高工作效率,簡化流程

JavaPoet的常用類:

  • TypeSpec,用於生成類、接口、枚舉對象的類
  • MethodSpec,用於生成方法對象的類
  • ParameterSpec,用於生成參數對象的類
  • FieldSpec,用於配置生成成員變量的類
  • ClassName,通過包名和類名生成的對象,在JavaPoet中相當於爲其指定Class
  • ParameterizedTypeName,通過MainClass和IncludeClass生成包含泛型的Class
  • JavaFile,控制生成的Java文件的輸出的類

通常APT框架中生成Java類都是使用javepoet。這裏也不展開介紹,下面結合我添加的註釋即可理解。

下面就來看parseRoutes()方法:

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {

------------ 一、準備工作:遍歷routeElements並創建對應RouteMeta,接着按group分組後 存入groupMap ---------------

    if (CollectionUtils.isNotEmpty(routeElements)) {
        // 清空rootMap
        rootMap.clear();

        //準備好會用到的元素類型,用於後面的判斷
        TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
        TypeMirror type_Service = elementUtils.getTypeElement(SERVICE).asType();
        TypeMirror fragmentTm = elementUtils.getTypeElement(FRAGMENT).asType();
        TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType();
        // ARouter的相關接口
        TypeElement type_IRouteGroup = elementUtils.getTypeElement(IROUTE_GROUP);
        TypeElement type_IProviderGroup = elementUtils.getTypeElement(IPROVIDER_GROUP);
        ClassName routeMetaCn = ClassName.get(RouteMeta.class);
        ClassName routeTypeCn = ClassName.get(RouteType.class);

        //幫助類:就是使用javaPoet生成的所有用於 收集所有路由等信息的類。

        //創建一個參數類型:用於存所有 組幫助類class 的Map,Map<String, Class<? extends IRouteGroup>>
        ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(
                        ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
                )
        );

        //創建一個參數類型:用於存某個分組內的所有路由,Map<String, RouteMeta>
        ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouteMeta.class)
        );

        //創建輸入參數的名字,也就是幾種幫助類loadInto方法的參數名
        ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build();
        ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
        ParameterSpec providerParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "providers").build();

        //創建方法builder:根幫助類loadInto方法,loadInto(Map<String, Class<? extends IRouteGroup>> routes)
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(rootParamSpec);

        //遍歷routeElements,創建對應類型的RouteMeta,並分組後存入 groupMap 中,統計後最後生成根幫助類
        for (Element element : routeElements) {
            TypeMirror tm = element.asType();
            Route route = element.getAnnotation(Route.class);
            RouteMeta routeMeta;

            //是 Activity 或者 Fragment
            if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                // 獲取被@Autowired註解的變量
                Map<String, Integer> paramsType = new HashMap<>();
                Map<String, Autowired> injectConfig = new HashMap<>();
                injectParamCollector(element, paramsType, injectConfig);
                
                //對應類型的RouteMeta
                if (types.isSubtype(tm, type_Activity)) {
                    routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else {
                    routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                }
                routeMeta.setInjectConfig(injectConfig);
            } else if (types.isSubtype(tm, iProvider)) {
                //是IProvider實現類
                routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
            } else if (types.isSubtype(tm, type_Service)) {
                //是Service
                routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
            } else {
                throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
            }

            //按group分類 然後存入groupMap,一個value就是同group的所有路由
            categories(routeMeta);
        }

        //Provider幫助類的 的 loadInto 方法:loadInto(Map<String,RouteMeta> providers)
        MethodSpec.Builder loadIntoMethodOfProviderBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(providerParamSpec);

        Map<String, List<RouteDoc>> docSource = new HashMap<>();

------------ 二、遍歷groupMap:創建 組幫助類文件 並把類名存入rootMap、構建Provider幫助類方法體語句 ---------------
                
        //遍歷groupMap:創建 組幫助類文件 並把類名存入rootMap、構建Provider幫助類方法體語句
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
            String groupName = entry.getKey();
            //組幫助類的 的 loadInto 方法
            MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(groupParamSpec);
            ...
            Set<RouteMeta> groupData = entry.getValue(); //groupData,一個組 的所有路由
            
            //遍歷同組路由:構建Provider幫助類loadInto方法體語句、組幫助類 的方法體語句
            for (RouteMeta routeMeta : groupData) {                
                ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());

                switch (routeMeta.getType()) {
                    case PROVIDER:
                        List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                        for (TypeMirror tm : interfaces) {
                            if (types.isSameType(tm, iProvider)) {//直接實現自IProvider接口
                                loadIntoMethodOfProviderBuilder.addStatement(
                                        "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                        (routeMeta.getRawType()).toString(),
                                        routeMetaCn,
                                        routeTypeCn,
                                        className,
                                        routeMeta.getPath(),
                                        routeMeta.getGroup());
                            } else if (types.isSubtype(tm, iProvider)) {
                                // //直接實現自IProvider的子接口
                                loadIntoMethodOfProviderBuilder.addStatement(
                                        "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                        tm.toString(),    // So stupid, will duplicate only save class name.
                                        routeMetaCn,
                                        routeTypeCn,
                                        className,
                                        routeMeta.getPath(),
                                        routeMeta.getGroup());
                            }
                        }
                        break;
                    default:
                        break;
                }

                //參數
                ...

                //組幫助類 的方法體內的語句
                loadIntoMethodOfGroupBuilder.addStatement(
                        "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                        routeMeta.getPath(),
                        routeMetaCn,
                        routeTypeCn,
                        className,
                        routeMeta.getPath().toLowerCase(),
                        routeMeta.getGroup().toLowerCase());
            ...
            }

            //創建 實現自IRouteGroup的 組幫助類文件(有多個,每個分組一個幫助類,每個幫助內 含有同組的activity、fragment、IProvider)
            String groupFileName = NAME_OF_GROUP + groupName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(groupFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IRouteGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfGroupBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

            //rootMap 存入了所有的組幫助類
            rootMap.put(groupName, groupFileName); 
        }
        
---------------------  三、創建根幫助類、創建Provider幫助類  ------------------------

        //遍歷rootMap,創建 根幫助類 方法體語句
        if (MapUtils.isNotEmpty(rootMap)) {
            for (Map.Entry<String, String> entry : rootMap.entrySet()) {
                loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
            }
        }
        ...

        //創建 Provider幫助類 文件(只有一個,這個是包含所有服務的幫助類)
        String providerMapFileName = NAME_OF_PROVIDER + SEPARATOR + moduleName;
        JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(providerMapFileName)
                        .addJavadoc(WARNING_TIPS)
                        .addSuperinterface(ClassName.get(type_IProviderGroup))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfProviderBuilder.build())
                        .build()
        ).build().writeTo(mFiler);

        //創建 根幫助類文件(只有一個)
        String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
        JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(rootFileName)
                        .addJavadoc(WARNING_TIPS)
                        .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfRootBuilder.build())
                        .build()
        ).build().writeTo(mFiler);

    }
}

(以上定義參數、創建方法體語句、生成Java類 就是使用的javapoet相關API)

如上所示,(忽略了參數處理以及路由Doc邏輯)總共有三個步驟:

  • 準備工作,遍歷routeElements並創建對應RouteMeta,接着按group分組後 存入 groupMap
  • 遍歷groupMap,創建 組幫助類文件 並把類名存入rootMap、構建Provider幫助類方法體語句
  • 遍歷rootMap生成方法體語句後創建根幫助類,創建Provider幫助類

整體邏輯和我們的猜想是一致的,邏輯也比較清晰。(對照實際生成的幫助類會更好理解)

到這裏,RouteProcessor就分析完了。 InterceptorProcessor、AutowiredProcessor,這裏不在分析,大家可以自行查看。

四、總結

本文首先介紹了對ARouter中使用的APT技術的理解——編譯時解析註解並生成Java文件,以及ARouter中的RouteProcessor是如何處理@Route註解並生成各種幫助類。

重點掌握對APT技術的理解,以及學習ARouter中幫助類的生成邏輯。這樣以後再遇到其他使用到APT技術的框架時就更容易掌握,更深入地,能夠在業務中嘗試使用APT技術解決問題。

另外,文中有三個未展開介紹的內容:註解的用法、Element相關知識、javapoet用法,它們是掌握和使用APT技術的基礎,但由於知識點比較固定,且不是本章重點,就未能詳細介紹。大家可在理解本篇內容的基礎上再去自行學習。

使用APT生成了幫助類,那麼要如何使用幫助類呢?這將在下一篇中介紹幫助類的使用以及涉及的AGP相關知識。

好了本篇就到這裏,歡迎繼續關注~

你的 點贊、評論,是對我的巨大鼓勵!

歡迎關注我的 公衆號 胡飛洋 ,文章更新可第一時間收到;

如果有問題或者想進羣,號內有加我微信的入口,我拉你進技術討論羣。在技術學習、個人成長的道路上,我們一起前進!

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