Android组件化开发

组件化设置

config.gradle(组件化统一配置文件)

image-config.png

不熟悉groovy语法的可以简单了解下


image-module.png

这里记得在项目根目录的build.gradle中添加配置文件config.gradle否则在项目中无法引用config.gradle中定义的属性

module中build.gradle配置

/**
*isRelease是config.gradle定义的组件化开关
*这里非常重要组件化的关键就是各个module是否可以单独运行
**/
if (isRelease) {
    //当需要集成化编译运行是isRelease为true各module变为android library依赖,否则为application
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}
//config.gradle定义的各个属性
def androidID = rootProject.ext.androidID//android配置
def applicationID = rootProject.ext.applicationID//应用唯一标识
def dependenciesSupport = rootProject.ext.dependencies//依赖
android {
    compileSdkVersion androidID.compileSdkVersion
    buildToolsVersion androidID.buildToolsVersion

    defaultConfig {
        if (!isRelease) {
            applicationId applicationID.personal
        }
        minSdkVersion androidID.minSdkVersion
        targetSdkVersion androidID.targetSdkVersion
        versionCode androidID.versionCode
        versionName androidID.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        // 这个方法接收三个非空的参数,第一个:确定值的类型,第二个:指定key的名字,第三个:传值(必须是String)
        // 为什么需要定义这个?因为src代码中有可能需要用到跨模块交互,如果是组件化模块显然不行
        // 切记:不能在android根节点,只能在defaultConfig或buildTypes节点下
        buildConfigField("boolean", "isRelease", String.valueOf(isRelease))

        // 在gradle文件中配置选项参数值(用于APT传参接收)
        // 切记:必须写在defaultConfig节点下
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName(), packageNameForAPT: packageNameForAPT]
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility javaVersion
        targetCompatibility javaVersion
    }
}

dependencies {
    //循环依赖第三方库
    //这里用了groovy中的foreach一句话依赖config.gradle定义的依赖集合
    dependenciesSupport.each { k, v -> implementation v }
    testImplementation testJunit
    androidTestImplementation androidTestJunit
    androidTestImplementation androidTestEspresso

    implementation project(':common')
    implementation project(':arouter_annotation')
    annotationProcessor project(':arouter_compiler')
}

组件化路由(跨模块交互)

自定义路由注解@ARouter

阅读阿里ARouter框架源码后发现太过于繁琐并且性能低下(使用了线程池遍历所有dex分包下的apt生成文件很low)而且很久也没有更新维护了,自己写了一个路由框架
package com.test.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)//注解作用域类、接口上
@Retention(RetentionPolicy.CLASS)//运行时为预编译
public @interface ARouter {
    String path();//详细路径
    String group() default "";//组名,可不填从详细路径截取要求开发者按照规范定义详细路径
}

使用@ARouter注解实例:

//必须按照统一格式来定义path=/module名/类名,group=module名(可省略)
@ARouter(path ="/app/MainActivity",group = "app")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (BuildConfig.isRelease) {
            Log.e(Cons.TAG, "当前为:集成化模式,除app可运行,其他子模块都是Android Library");
        } else {
            Log.e(Cons.TAG, "当前为:组件化模式,app/order/personal子模块都可独立运行");
        }
    }
}

自定义路由注解处理器ARouterProcessor

package com.test.compiler;

import com.google.auto.service.AutoService;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import com.test.annotation.ARouter;
import com.test.annotation.model.RouterBean;
import com.test.compiler.utils.Constants;
import com.test.compiler.utils.EmptyUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
@SupportedAnnotationTypes({Constants.AROUTER_ANNOTATION_TYPES})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})
public class ARouterProcessor extends AbstractProcessor {
    Elements elementUtils;
    Types typeUtils;
    Filer filer;
    Messager messager;
    String moduleName;
    String packageNameForAPT;
    // 临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
    // key:组名"app", value:"app"组的路由路径"ARouter$$Path$$app.class"
    private Map> tempPathMap = new HashMap<>();

    // 临时map存储,用来存放路由Group信息,生成路由组类文件时遍历
    // key:组名"app", value:类名"ARouter$$Path$$app.class"
    private Map tempGroupMap = new HashMap<>();
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        typeUtils = processingEnvironment.getTypeUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
        // 通过ProcessingEnvironment去获取对应的参数
        Map options = processingEnvironment.getOptions();
        if (!EmptyUtils.isEmpty(options)) {
            moduleName = options.get(Constants.MODULE_NAME);
            packageNameForAPT = options.get(Constants.APT_PACKAGE);
            // 有坑:Diagnostic.Kind.ERROR,异常会自动结束,不像安卓中Log.e
            messager.printMessage(Diagnostic.Kind.NOTE, "moduleName >>> " + moduleName);
            messager.printMessage(Diagnostic.Kind.NOTE, "packageName >>> " + packageNameForAPT);
        }

        // 必传参数判空(乱码问题:添加java控制台输出中文乱码)
        if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
            throw new RuntimeException("注解处理器需要的参数moduleName或者packageName为空,请在对应build.gradle配置参数");
        }
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        if (!EmptyUtils.isEmpty(set)) {
            Set elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);

            if (!EmptyUtils.isEmpty(elements)) {
                // 解析元素
                try {
                    parseElements(elements);
                    return true;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
//             坑:必须写返回值,表示处理@ARouter注解完成
            return true;
        }
        return false;
    }

    private void parseElements(Set elements) throws IOException {
        // 通过Element工具类,获取Activity、Callback类型
        TypeElement activityType = elementUtils.getTypeElement(Constants.ACTIVITY);
        TypeElement callType = elementUtils.getTypeElement(Constants.CALL);

        // 显示类信息(获取被注解节点,类节点)这里也叫自描述 Mirror
        TypeMirror activityMirror = activityType.asType();
        TypeMirror callMirror = callType.asType();
        // 遍历节点
        for (Element element : elements) {
            // 获取每个元素类信息,用于比较
            TypeMirror elementMirror = element.asType();
            messager.printMessage(Diagnostic.Kind.NOTE, "遍历元素信息:" + elementMirror.toString());

            // 获取每个类上的@ARouter注解中的注解值
            ARouter aRouter = element.getAnnotation(ARouter.class);

            // 路由详细信息,最终实体封装类
            RouterBean bean = new RouterBean.Builder()
                    .setGroup(aRouter.group())
                    .setPath(aRouter.path())
                    .setElement(element)
                    .build();

            // 高级判断:ARouter注解仅能用在类之上,并且是规定的Activity
            // 类型工具类方法isSubtype,相当于instance一样
            if (typeUtils.isSubtype(elementMirror, activityMirror)) {
                bean.setType(RouterBean.Type.ACTIVITY);
            } else if (typeUtils.isSubtype(elementMirror, callMirror)) {
                bean.setType(RouterBean.Type.CALL);
            } else {
                // 不匹配抛出异常,这里谨慎使用!考虑维护问题
                throw new RuntimeException("@ARouter注解目前仅限用于Activity类之上");
            }

            // 赋值临时map存储,用来存放路由组Group对应的详细Path类对象
            valueOfPathMap(bean);
        }
        // routerMap遍历后,用来生成类文件

        // 获取ARouterLoadGroup、ARouterLoadPath类型(生成类文件需要实现的接口)
        TypeElement groupLoadType = elementUtils.getTypeElement(Constants.AROUTER_GROUP); // 组接口
        TypeElement pathLoadType = elementUtils.getTypeElement(Constants.AROUTER_PATH); // 路径接口

        // 第一步:生成路由组Group对应详细Path类文件,如:ARouter$$Path$$app
        createPathFile(pathLoadType);

        // 第二步:生成路由组Group类文件(没有第一步,取不到类文件),如:ARouter$$Group$$app
        createGroupFile(groupLoadType, pathLoadType);
    }

    /**
     * 生成路由组Group对应详细Path,如:ARouter$$Path$$app
     *
     * @param pathLoadType ARouterLoadPath接口信息
     */
    private void createPathFile(TypeElement pathLoadType) throws IOException {
        // 判断是否有需要生成的类文件
        if (EmptyUtils.isEmpty(tempPathMap)) return;

        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class), // Map
                ClassName.get(String.class), // Map
        );

        // 遍历分组,每一个分组创建一个路径类文件,如:ARouter$$Path$$app
        for (Map.Entry> entry : tempPathMap.entrySet()) {
            // 方法配置:public Map loadPath() {
            MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME) // 方法名
                    .addAnnotation(Override.class) // 重写注解
                    .addModifiers(Modifier.PUBLIC) // public修饰符
                    .returns(methodReturns); // 方法返回值

            // 遍历之前:Map pathMap = new HashMap<>();
            methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(RouterBean.class),
                    Constants.PATH_PARAMETER_NAME,
                    HashMap.class);

            // 一个分组,如:ARouter$$Path$$app。有很多详细路径信息,如:/app/MainActivity、/app/OtherActivity
            List pathList = entry.getValue();
            // 方法内容配置(遍历每个分组中每个路由详细路径)
            for (RouterBean bean : pathList) {
                // 类似String.format("hello %s qpl123 %d", "qpl", 123)通配符
                // pathMap.put("/app/MainActivity", RouterBean.create(
                //        RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
                methodBuidler.addStatement(
                        "$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
                        Constants.PATH_PARAMETER_NAME, // pathMap.put
                        bean.getPath(), // "/app/MainActivity"
                        ClassName.get(RouterBean.class), // RouterBean
                        ClassName.get(RouterBean.Type.class), // RouterBean.Type
                        bean.getType(), // 枚举类型:ACTIVITY
                        ClassName.get((TypeElement) bean.getElement()), // MainActivity.class
                        bean.getPath(), // 路径名
                        bean.getGroup() // 组名
                );
            }

            // 遍历之后:return pathMap;
            methodBuidler.addStatement("return $N", Constants.PATH_PARAMETER_NAME);

            // 最终生成的类文件名
            String finalClassName = Constants.PATH_FILE_NAME + entry.getKey();
            messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
                    packageNameForAPT + "." + finalClassName);

            // 生成类文件:ARouter$$Path$$app
            JavaFile.builder(packageNameForAPT, // 包名
                    TypeSpec.classBuilder(finalClassName) // 类名
                            .addSuperinterface(ClassName.get(pathLoadType)) // 实现ARouterLoadPath接口
                            .addModifiers(Modifier.PUBLIC) // public修饰符
                            .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
                            .build()) // 类构建完成
                    .build() // JavaFile构建完成
                    .writeTo(filer); // 文件生成器开始生成类文件

            // 非常重要一步!!!!!路径文件生成出来了,才能赋值路由组tempGroupMap
            tempGroupMap.put(entry.getKey(), finalClassName);
        }
    }

    /**
     * 生成路由组Group文件,如:ARouter$$Group$$app
     *
     * @param groupLoadType ARouterLoadGroup接口信息
     * @param pathLoadType ARouterLoadPath接口信息
     */
    private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
        // 判断是否有需要生成的类文件
        if (EmptyUtils.isEmpty(tempGroupMap) || EmptyUtils.isEmpty(tempPathMap)) return;

        TypeName methodReturns = ParameterizedTypeName.get(
                ClassName.get(Map.class), // Map
                ClassName.get(String.class), // Map
                // 某某Class是否属于ARouterLoadPath接口的实现类
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))
        );

        // 方法配置:public Map> loadGroup() {
        MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME) // 方法名
                .addAnnotation(Override.class) // 重写注解
                .addModifiers(Modifier.PUBLIC) // public修饰符
                .returns(methodReturns); // 方法返回值

        // 遍历之前:Map> groupMap = new HashMap<>();
        methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
                ClassName.get(Map.class),
                ClassName.get(String.class),
                ParameterizedTypeName.get(ClassName.get(Class.class),
                        WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
                Constants.GROUP_PARAMETER_NAME,
                HashMap.class);

        // 方法内容配置
        for (Map.Entry entry : tempGroupMap.entrySet()) {
            // 类似String.format("hello %s qpl123 %d", "qpl", 123)通配符
            // groupMap.put("main", ARouter$$Path$$app.class);
            methodBuidler.addStatement("$N.put($S, $T.class)",
                    Constants.GROUP_PARAMETER_NAME, // groupMap.put
                    entry.getKey(),
                    // 类文件在指定包名下
                    ClassName.get(packageNameForAPT, entry.getValue()));
        }

        // 遍历之后:return groupMap;
        methodBuidler.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);

        // 最终生成的类文件名
        String finalClassName = Constants.GROUP_FILE_NAME + moduleName;
        messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
                packageNameForAPT + "." + finalClassName);

        // 生成类文件:ARouter$$Group$$app
        JavaFile.builder(packageNameForAPT, // 包名
                TypeSpec.classBuilder(finalClassName) // 类名
                        .addSuperinterface(ClassName.get(groupLoadType)) // 实现ARouterLoadGroup接口
                        .addModifiers(Modifier.PUBLIC) // public修饰符
                        .addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
                        .build()) // 类构建完成
                .build() // JavaFile构建完成
                .writeTo(filer); // 文件生成器开始生成类文件
    }

    /**
     * 赋值临时map存储,用来存放路由组Group对应的详细Path类对象,生成路由路径类文件时遍历
     *
     * @param bean 路由详细信息,最终实体封装类
     */
    private void valueOfPathMap(RouterBean bean) {
        if (checkRouterPath(bean)) {
            messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());

            // 开始赋值Map
            List routerBeans = tempPathMap.get(bean.getGroup());
            // 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map
            if (EmptyUtils.isEmpty(routerBeans)) {
                routerBeans = new ArrayList<>();
                routerBeans.add(bean);
                tempPathMap.put(bean.getGroup(), routerBeans);
            } else { // 找到了key,直接加入List集合
                routerBeans.add(bean);
            }

        } else {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
        }
    }

    /**
     * 校验@ARouter注解的值,如果group未填写就从必填项path中截取数据
     *
     * @param bean 路由详细信息,最终实体封装类
     */
    private boolean checkRouterPath(RouterBean bean) {
        String group = bean.getGroup();
        String path = bean.getPath();

        // @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
        if (EmptyUtils.isEmpty(path) || !path.startsWith("/")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
            return false;
        }

        // 比如开发者代码为:path = "/MainActivity",最后一个 / 符号必然在字符串第1位
        if (path.lastIndexOf("/") == 0) {
            // 架构师定义规范,让开发者遵循
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
            return false;
        }

        // 从第一个 / 到第二个 / 中间截取,如:/app/MainActivity 截取出 app 作为group
        String finalGroup = path.substring(1, path.indexOf("/", 1));

        // @ARouter注解中的group有赋值情况
        if (!EmptyUtils.isEmpty(group) && !group.equals(moduleName)) {
            // 架构师定义规范,让开发者遵循
            messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
            return false;
        } else {
            bean.setGroup(finalGroup);
        }

        return true;
    }
}

这里使用了apt+javapoet(超级利刃)在预编译阶段生成代码,不了解的同学可以自行百度了解下,有时间的话会把完整的ARouter上传github

你可能感兴趣的:(Android组件化开发)