組件化的架構設計(二):路由架構設計及編碼實現

博客主頁java

組件化路由架構設計思路

上一篇中講解了類加載全局Map記錄實現組件化模塊之間的交互,慢慢衍生APT技術。android

那麼在組件化架構中,咱們須要思考經過APT+javapoet技術生成什麼樣的類文件呢?
segmentfault

從組件化路由架構設計圖須要思考,使用APT生成文件爲何須要分組? 生成這些文件幹什麼用?api

設計思路:在初始化只加載分組表數據,在使用的時候,若是使用A分組就去加載A分組下的全部路由信息,而不去加載B分組數據。好比:A組有100個路由信息,B組有200個路由信息,若是不分組,Map中就須要加載300個路由信息,當用戶可能根本就不須要進入B分組頁面,加載B分組的路由信息,會致使內存和時間的浪費。緩存

todo_modular項目:使用路由組件化項目架構

  • app: 主工程(application)
  • common:基礎模塊(library)
  • shop:購物shop功能模塊
  • personal:個人personal功能模塊

路由框架app

  • annotations(java library):註解模塊,自定義註解給api和compiler依賴
  • compiler:APT模塊(java library)編譯時生成java文件
  • api:核心模塊,項目中須要引入依賴的模塊,使用compiler模塊生成的java類完成路由表的建立

設計Coding實現

RouteMeta 路由信息封裝類,路由地址,路由分組名,原始的類元素等。框架

import javax.lang.model.element.Element;
public class RouteMeta {

    public enum RouteType {
        ACTIVITY
    }

    // 路由類型,支持Activity
    private RouteType type;
    // 原始的類元素
    private Element element;
    // 註解使用的類對象
    private Class<?> clazz;
    // 路由地址
    private String path;
    // 路由分組名
    private String group;
}

思考:爲何Element類節點須要存起來?ide

由於要在註解處理器中,循環拿到每一個類節點,方便賦值和調用。Element它是javax.lang包下的,不屬於Android Library。函數

IRouteRoot接口

Map<String, Class<? extends IRouteGroup>>
key: 組名,如:app , value:該組名下全部的路徑文件名,如:ARouter$$Group$$app.class

注意:須要生成路由路徑文件後ARouter$$Group$$app.class,才能生成路由組文件,使用接口方式容易擴展

/**
 * 路由組加載數據接口
 */
public interface IRouteRoot {

    /**
     *  加載路由組數據
     *  如:key:"app", value: ARouter$$Group$$app.class(實現了IRouteGroup接口)
     */
    void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}

IRouteGroup接口

Map<String, RouteMeta>
key:路徑名,如:"/app/MainActivity", value:該路徑名下對應的路由信息

OOP思想,讓單純的targetClass變成更靈活的RouteMeta對象,接口方式容易擴展

/**
 * 路由組對應的詳細Path路徑加載數據接口
 * 如:app組下對應有哪些類須要加載
 */
public interface IRouteGroup {

    /**
     *  加載路由組中的Path詳細數據
     *  如:key:"/app/MainActivity", value:MainActivity信息封裝到RouteMeta對象中
     */
    void loadInto(Map<String, RouteMeta> atlas);
}

ARouter$$Group$$XX 與 ARouter$$Root$$XX 文件生成

下面以購物模塊shop爲例,生成ARouter$$Group$$shopARouter$$Root$$shop文件,涉及到的技術點:APT + javapoet

在使用APT + javapoet 生成咱們想要的代碼以前,首先要設計好咱們想要的代碼模版,如:ARouter$$Root$$shop 分組的模版代碼

public final class ARouter$$Root$$shop implements IRouteRoot {
  @Override
  public void loadInto(final Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("shop", ARouter$$Group$$shop.class);;
  }
}

ARouter$$Group$$shop 該分組下的全部的路由信息代碼模版

public class ARouter$$Group$$shop implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/shop/ShopActivity", RouteMeta.build(RouteMeta.RouteType.ACTIVITY, ShopActivity.class, "/shop/ShopActivity", "shop"));;
    atlas.put("/shop/ShopDetailActivity", RouteMeta.build(RouteMeta.RouteType.ACTIVITY, ShopDetailActivity.class, "/shop/ShopDetailActivity", "shop"));;
  }
}

爲了生成上面的模版代碼,首先第一個@Router註解。實現Activity跳轉,就要用到該註解標記Activity,經過註解處理器處理這些註解,而後在編譯器生成代碼:

@Target(ElementType.TYPE) // 該註解做用在類之上
@Retention(RetentionPolicy.CLASS) // 要在編譯時進行一些預處理操做。註解會在class文件中存在
public @interface Router {

    // 詳細路由路徑(必填),如:"app/MainActivity"
    String path();

    // 路由組名(選填,若是不填寫,能夠從path中截取)
    String group() default "";
}

而後建立路由註解處理器,類名爲ARouterProcessor,該類繼承AbstractProcessor。註解處理器須要註冊,藉助AutoService能夠幫助咱們自動註冊到META-INF中。同時還須要指定註解處理器支持的註解類型、JDK編譯的版本、註解處理器接收的參數等。

// 容許/支持的註解類型,讓註解處理器處理(新增annotation module)
@SupportedAnnotationTypes({"com.example.modular.annotations.Router"})
// 指定JDK編譯版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 註解處理器接收的參數
@SupportedOptions({"moduleName"})
// AutoService則是固定的寫法,加個註解便可
// 經過auto-service中的@AutoService能夠自動生成AutoService註解處理器,用來註冊
// 用來生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
public class ARouterProcessor extends AbstractProcessor { }

接下來實現process抽象方法,該方法至關於main方法,註解處理器的入口方法,用來處理被@Router標記的元素。

/**
 * 至關於main函數,開始處理註解
 * 註解處理器的核心方法,處理具體的註解,生成Java文件
 *
 * @param set              支持註釋類型的集合,如:@ARouter註解
 * @param roundEnvironment 當前或是以前的運行環境,能夠經過該對象查找找到的註解
 * @return true 表示後續處理器不會再處理(已經處理完成)
 */
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    // set集合,就是支持的註解集合,如:ARouter註解
    if (set.isEmpty()) return false;

    // 獲取全部被@ARouter註解註釋的元素
    Set<? extends Element> elementsAnnotatedWithARouter = roundEnvironment.getElementsAnnotatedWith(Router.class);

    if (elementsAnnotatedWithARouter != null && !elementsAnnotatedWithARouter.isEmpty()) {

        try {
            parseElements(elementsAnnotatedWithARouter);
        } catch (IOException e) {
            messager.printMessage(Diagnostic.Kind.NOTE, "異常發生:" + e.getMessage());
        }
    }
    return true;
}

在處理被@Router註解標記的元素以前,須要初始化一些工具,重寫AbstractProcessor類的init方法,經過processingEnvironment獲取輔助工具,如:打印Log的工具、 文件生成工具等。

// 操做Element的工具類(如:類、函數、屬性都是Element)
private Elements elementUtils;

// Messager用來報告錯誤,警告和其餘提示信息
private Messager messager;

// type(類信息)工具類,包含用於操做TypeMirror的工具方法
private Types typeUtils;

// 文件生成器,類/資源,Filter用來建立新的源文件,class文件以及輔助文件
private Filer filer;

// 模塊名,經過getOptions獲取build.gradle傳過來
private String moduleName;

// 緩存路由信息,key:組名 value:該組名下的全部路由信息
private Map<String, List<RouteMeta>> cacheRouteMetaMap = new HashMap<>();

// 緩存分組的信息,key:組名 value:該組名下對應的路由類名,如:Router$$Group$$shop
private Map<String, String> cacheGroupMap = new HashMap<>();

/**
 * 該方法主要用於一些初始化工做,經過該方法的processingEnvironment參數能夠獲取一些工具
 */
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
    super.init(processingEnvironment);

    elementUtils = processingEnvironment.getElementUtils();
    messager = processingEnvironment.getMessager();
    typeUtils = processingEnvironment.getTypeUtils();
    filer = processingEnvironment.getFiler();

    messager.printMessage(Diagnostic.Kind.NOTE, "<<<<< ARouterProcessor::init >>>>>");

    Map<String, String> options = processingEnvironment.getOptions();
    if (options != null && !options.isEmpty()) {
        moduleName = options.get("moduleName");
        messager.printMessage(Diagnostic.Kind.NOTE, "moduleName=" + moduleName);
    }

    if (Utils.isEmpty(moduleName)) {
        throw new RuntimeException("ARouter註解處理器傳入moduleName參數不能爲空,清查看build.gradle配置文件配置是否正確");
    }
}

解析全部被@Router註解標記的元素,將知足條件元素中路由信息封裝到RouteMeta並緩存起來。用於生成分組文件和該組下的路由文件。

private void parseElements(Set<? extends Element> elements) throws IOException {

    TypeElement activityElement = elementUtils.getTypeElement("android.app.Activity");
    TypeMirror activityMirror = activityElement.asType();

    for (Element element : elements) {
        TypeMirror elementMirror = element.asType();
        messager.printMessage(Diagnostic.Kind.NOTE, "遍歷元素信息:" + elementMirror.toString());
        // 遍歷元素信息:com.example.modular.shop.ShopActivity

        // 經過類節點獲取包節點,(全路徑名,如:com.example.modular.shop)
        String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();

        // 獲取被@ARouter註解的簡單類名
        String simpleName = element.getSimpleName().toString();

        // 注: 包名:com.example.modular.shop 被註解的類名:ShopActivity
        messager.printMessage(Diagnostic.Kind.NOTE, "包名:" + packageName + " 被註解的類名:" + simpleName);


        Router aRouter = element.getAnnotation(Router.class);

        RouteMeta routeMeta = new RouteMeta(element, aRouter.group(), aRouter.path());

        // 判斷第一類型是不是第二類型的子類型,若是是返回true
        if (typeUtils.isSubtype(elementMirror, activityMirror)) {
            // 知足條件:被@ARouter註解的元素是Activity的子類型
            routeMeta.setType(RouteMeta.RouteType.ACTIVITY);
        } else {
            throw new RuntimeException("@ARouter註解目前只能應用與Activity類上");
        }

        fillMapWithRouteMeta(routeMeta);

    }

    createGroupFile();

    createRootFile();
}

fillMapWithRouteMeta方法用於緩存路由信息

// 緩存分組的信息,key:組名 value:該組名下對應的路由類名,如:Router$$Group$$shop
private Map<String, String> cacheGroupMap = new HashMap<>();

private void fillMapWithRouteMeta(RouteMeta routeMeta) {
    if (checkRouterPath(routeMeta)) {
        messager.printMessage(Diagnostic.Kind.NOTE, "routeMeta>>>> " + routeMeta.toString());

        List<RouteMeta> routeMetas = cacheRouteMetaMap.get(routeMeta.getGroup());

        if (routeMetas == null) {
            routeMetas = new ArrayList<>();
            cacheRouteMetaMap.put(routeMeta.getGroup(), routeMetas);
        }
        routeMetas.add(routeMeta);
    } else {
        messager.printMessage(Diagnostic.Kind.NOTE, "routeMeta檢查失敗,不知足規範");
    }
}

先建立路由文件,createGroupFile具體實現以下:

private void createGroupFile() throws IOException {
    if (cacheRouteMetaMap.isEmpty()) return;

    TypeElement routeGroupElement = elementUtils.getTypeElement("com.example.modular.api.IRouteGroup");

    for (Map.Entry<String, List<RouteMeta>> entry : cacheRouteMetaMap.entrySet()) {

        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ClassName.get(RouteMeta.class)
        );

        // Map<String, RouteMeta> atlas
        ParameterSpec atlasParameter = ParameterSpec.builder(parameterizedTypeName, "atlas").build();

        // public void loadInto(Map<String, RouteMeta> atlas)
        MethodSpec.Builder loadIntoMethodBuilder = MethodSpec.methodBuilder("loadInto")
                .addAnnotation(Override.class)
                .addParameter(atlasParameter)
                .addModifiers(Modifier.PUBLIC);


        List<RouteMeta> routeMetas = entry.getValue();
        for (RouteMeta routeMeta : routeMetas) {
            // atlas.put("/shop/ShopActivity", RouteMeta.build(RouteMeta.RouteType.ACTIVITY, ShopActivity.class, "/shop/ShopActivity", "shop"));
            loadIntoMethodBuilder.addStatement(
                    "$N.put($S, $T.build($T.$L, $T.class, $S, $S));",
                    "atlas",
                    routeMeta.getPath(),
                    ClassName.get(RouteMeta.class),
                    ClassName.get(RouteMeta.RouteType.class),
                    routeMeta.getType(),
                    ClassName.get((TypeElement) routeMeta.getElement()),
                    routeMeta.getPath(),
                    routeMeta.getGroup()
            );
        }

        MethodSpec loadIntoMethod = loadIntoMethodBuilder.build();

        String groupName = entry.getKey();

        String packageName = "com.example.android.arouter.routers";
        String finalClassName = "ARouter$$Group$$" + groupName;
        messager.printMessage(Diagnostic.Kind.NOTE, "最終生成的路徑的文件名:" + packageName + "." + finalClassName);

        // public class Router$$Group$$shop implements IRouteGroup
        TypeSpec finalClass = TypeSpec.classBuilder(finalClassName)
                .addMethod(loadIntoMethod)
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ClassName.get(routeGroupElement))
                .build();

        // 利用文件生成器生成文件
        JavaFile.builder(packageName, finalClass).build().writeTo(filer);

        cacheGroupMap.put(groupName, finalClassName);
    }
}

再建立分組文件,createRootFile方法具體實現以下:

private void createRootFile() throws IOException {
    if (cacheGroupMap.isEmpty()) return;

    TypeElement routeRootElement = elementUtils.getTypeElement("com.example.modular.api.IRouteRoot");
    TypeElement routeGroupElement = elementUtils.getTypeElement("com.example.modular.api.IRouteGroup");

    // Map<String, Class<? extends IRouteGroup>>
    ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
            ClassName.get(Map.class),
            ClassName.get(String.class),
            ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(routeGroupElement)))
    );

    // Map<String, Class<? extends IRouteGroup>> routes
    ParameterSpec routesParameter = ParameterSpec.builder(parameterizedTypeName, "routes", Modifier.FINAL).build();

    // public void loadInto(Map<String, Class<? extends IRouteGroup>> routes)
    MethodSpec.Builder loadIntoMethodBuilder = MethodSpec.methodBuilder("loadInto")
            .addModifiers(Modifier.PUBLIC)
            .addAnnotation(Override.class)
            .addParameter(routesParameter);

    String packageName = "com.example.android.arouter.routers";

    for (Map.Entry<String, String> entry : cacheGroupMap.entrySet()) {

        // 如:shop
        String groupName = entry.getKey();
        // 如:Router$$Group$$shop
        String finalGroupClassName = entry.getValue();

        // routes.put("shop", Router$$Group$$shop.class);
        loadIntoMethodBuilder.addStatement(
                "$N.put($S, $T.class);",
                "routes",
                groupName,
                ClassName.get(packageName, finalGroupClassName)
        );
    }

    MethodSpec loadIntoMethod = loadIntoMethodBuilder.build();

    String finalClassName = "ARouter$$Root$$" + moduleName;
    messager.printMessage(Diagnostic.Kind.NOTE, "最終生成的組文件名:" + packageName + "." +finalClassName);

    // public final class Router$$Root$$shop implements IRouteRoot
    TypeSpec finalClass = TypeSpec.classBuilder(finalClassName)
            .addMethod(loadIntoMethod)
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addSuperinterface(ClassName.get(routeRootElement)).build();

    JavaFile.builder(packageName, finalClass).build().writeTo(filer);
}

XX$$ARouter$$Parameter

好比Activity跳轉,須要攜帶了參數,咱們須要經過Activity.getIntent()獲取參數。這些模版代碼也能夠註解處理器幫咱們生成。

public final class ShopActivity$$ARouter$$Parameter implements IParameter {
  @Override
  public void inject(final Object object) {
    ShopActivity target = (ShopActivity) object;;
    target.name = target.getIntent().getStringExtra("name");;
    target.age = target.getIntent().getIntExtra("shopAge", target.age);;
    target.isOpen = target.getIntent().getBooleanExtra("isOpen", target.isOpen);;
  }
}

建立ParameterProcessor註解處理器,用來生成獲取參數的模板代碼,該類一樣繼承AbstractProcessor

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Parameter {
    String name() default "";
}

// 容許/支持的註解類型,讓註解處理器處理(新增annotation module)
@SupportedAnnotationTypes({"com.example.modular.annotations.Parameter"})
// 指定JDK編譯版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 註解處理器接收的參數
@SupportedOptions({"moduleName"})
// AutoService則是固定的寫法,加個註解便可
// 經過auto-service中的@AutoService能夠自動生成AutoService註解處理器,用來註冊
// 用來生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
public class ParameterProcessor extends AbstractProcessor {}

實現process方法,處理被@Parameter標記的元素。

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    if (set.isEmpty()) return false;

    Set<? extends Element> elementsAnnotatedWithParameter = roundEnvironment.getElementsAnnotatedWith(Parameter.class);

    if (elementsAnnotatedWithParameter != null && !elementsAnnotatedWithParameter.isEmpty()) {

        fillMapWithElement(elementsAnnotatedWithParameter);
        try {
            createParameterFile();
        } catch (IOException e) {
            messager.printMessage(Diagnostic.Kind.NOTE,
                    "ParameterProcessor發生異常:" + e.getMessage());
        }

        return true;
    }
    return false;
}

其中fillMapWithElement用於緩存全部知足條件的元素

private final Map<TypeElement, List<Element>> cacheMap = new HashMap<>();

private void fillMapWithElement(Set<? extends Element> elementsAnnotatedWithParameter) {
    for (Element element : elementsAnnotatedWithParameter) {
        // @Parameter註解做用在字段上,獲取字段的父元素
        TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
        messager.printMessage(Diagnostic.Kind.NOTE,
                "@Parameter遍歷父元素信息:" + enclosingElement.getSimpleName() + " && 遍歷元素信息: " + element.getSimpleName());

        List<Element> elements = cacheMap.get(enclosingElement);
        if (elements == null) {
            List<Element> fields = new ArrayList<>();
            fields.add(element);
            cacheMap.put(enclosingElement, fields);
        } else {
            elements.add(element);
        }
    }
}

接下來用於建立生成文件

private void createParameterFile() throws IOException {
    if (cacheMap.isEmpty()) return;

    TypeElement activityElement = elementUtils.getTypeElement("android.app.Activity");

    ParameterSpec objectParameter = ParameterSpec.builder(TypeName.OBJECT, "object", Modifier.FINAL).build();

    TypeElement parameterType = elementUtils.getTypeElement("com.example.modular.api.IParameter");

    for (Map.Entry<TypeElement, List<Element>> entry : cacheMap.entrySet()) {

        TypeElement typeElement = entry.getKey();

        if (!typeUtils.isSubtype(typeElement.asType(), activityElement.asType())) {
            throw new RuntimeException("@Parameter註解目前只能應用於Activity上");
        }

        ClassName className = ClassName.get(typeElement);

        // public void inject(Object object)
        MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder("inject")
                .addAnnotation(Override.class)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(objectParameter);

        // MainActivity target = (MainActivity) object;
        injectMethodBuilder.addStatement(
                "$T target = ($T) $N;",
                className,
                className,
                "object"
        );

        List<Element> elements = entry.getValue();
        for (Element element : elements) {
            TypeMirror typeMirror = element.asType();

            int type = typeMirror.getKind().ordinal();

            String fieldName = element.getSimpleName().toString();

            // 獲取註解中定義的字段名
            String name = element.getAnnotation(Parameter.class).name();

            // 若是註解中沒有定義,則使用默認的字段名
            String finalName = Utils.isEmpty(name) ? fieldName : name;

            String finalValue = "target." + fieldName;

            String format = finalValue + " = target.getIntent().";

            if (type == TypeKind.INT.ordinal()) {
                // target.age = target.getIntent().getIntExtra("appAge", target.age);
                format += "getIntExtra($S, " + finalValue + ");";
            } else if (type == TypeKind.BOOLEAN.ordinal()) {
                // target.isOpen = target.getIntent().getBooleanExtra("isOpen", target.isOpen);
                format += "getBooleanExtra($S, " + finalValue + ");";
            } else {

                if (typeMirror.toString().equals("java.lang.String")) {
                    // target.name = target.getIntent().getStringExtra("name");
                    format += "getStringExtra($S);";
                }
            }

            if (format.endsWith(";")) {
                // target.name = target.getIntent().getStringExtra("name");
                injectMethodBuilder.addStatement(format, finalName);
            } else {
                messager.printMessage(Diagnostic.Kind.ERROR, "目前僅僅支持String int Boolean類型參數");
            }
        }

        MethodSpec injectMethod = injectMethodBuilder.build();

        String finalClassName = typeElement.getSimpleName() + "$$ARouter$$Parameter";
        messager.printMessage(Diagnostic.Kind.NOTE,
                "最終生產的參數類文件:" + className.packageName() + "." + finalClassName);

        // public final class MainActivity$$Router$$Parameter implements IParameter
        TypeSpec finalClass = TypeSpec.classBuilder(finalClassName)
                .addMethod(injectMethod)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addSuperinterface(ClassName.get(parameterType))
                .build();

        JavaFile.builder(className.packageName(), finalClass).build().writeTo(filer);
    }
}

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索