Android模块化

2021-11-24/2021-11-24

什么是模块化?

模块化就是将一个项目拆分成若干个组件,分而治之。

为什么需要模块化?

问题一:无论分包怎么做,随着项目增大,项目失去层次感,后面接手的人扑街

问 题二:包名约束太弱,稍有不注意,就会不同业务包直接互相调用,代码高耦合

问题三:多人开发在版 本管理中,容易出现代码覆盖冲突等问题

使用模块化可以做到不相互依赖,可以相互交互,任意组合,高度解耦,自由拆卸,自由组装,重复利用, 分层独立化

怎么实现?

业务模块之间不能横向依赖,业务模块依赖于公共基础库,并通过公共基础库进行通信

组件化环境:每个模块间可以单独调试

集成化环境:只有app模块可以运行

配置gradle文件:

github地址:OkAndGreat/ModulizationLearn: 模块化/组件化学习demo (github.com)

我创建了俩个模块 login 和personal

所有gradle文件如下:

//setting.gradle
println ("log from settings.gradle build file")

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}


rootProject.name = "ModulizationLearn"
include ':app'
include ':login'
include ':personal'
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply from : 'common.gradle'

println ("log from top-level build file")
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.3"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
//扩展块
ext {

    // 正式环境 和 测试环境
    isRelease = false

    // 正式环境 和 测试环境 服务器 URL 配置
    url = [
            "debug"  : "https://192.188.22.99/debug",
            "release": "https://192.188.22.99/release"
    ]

    //使用Map存储公共信息
    commonConfig = [
            minSdk                   : 21,
            targetSdk                : 30,
            versionCode              : 1,
            versionName              : "1.0",
            testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner",
            compileSdk               : 30
    ]

    appId = [
            app: "com.redrock.modulizationlearn",
            login: "com.redrock.login",
            personal: "com.redrock.personal"
    ]

    dependenciesConfig = [
            "core-ktx" : "androidx.core:core-ktx:1.6.0",
            "appcompat" : "androidx.appcompat:appcompat:1.3.1",
            "material" : "com.google.android.material:material:1.4.0",
            "constraintLayout" : "androidx.constraintlayout:constraintlayout:2.1.1"
    ]

}
plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

println("log from app build.gradle file")

android {
    compileSdk commonConfig.compileSdk

    defaultConfig {
        applicationId appId.app
        minSdk commonConfig.minSdk
        targetSdk commonConfig.targetSdk
        versionCode commonConfig.versionCode
        versionName commonConfig.versionName

        testInstrumentationRunner commonConfig.testInstrumentationRunner

        // 让Java代码也可以得到gradle里的信息
        // 组件化 和 集成化 的时候需要
        buildConfigField("boolean", "isRelease", String.valueOf(isRelease))
    }

    buildTypes {
        debug {
            buildConfigField("String", "debug", "\"${url.debug}\"")
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    dependenciesConfig.each { k, v -> implementation v }

    if (isRelease) {
        //这里的依赖如果模块很多也可以使用统一管理
        implementation project(':login')
        implementation project(':personal')
    }
}
plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdk commonConfig.compileSdk

    defaultConfig {
        if (isRelease) {
            applicationId appId.login
        }
        minSdk commonConfig.minSdk
        targetSdk commonConfig.targetSdk
        versionCode commonConfig.versionCode
        versionName commonConfig.versionName

        testInstrumentationRunner commonConfig.testInstrumentationRunner
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

//    implementation 'androidx.core:core-ktx:1.6.0'
//    implementation 'androidx.appcompat:appcompat:1.3.1'
//    implementation 'com.google.android.material:material:1.4.0'
//    implementation 'androidx.constraintlayout:constraintlayout:2.1.1'

    dependenciesConfig.each { k, v -> implementation v }

}
plugins {
    id 'com.android.application'
    id 'kotlin-android'
}

android {
    compileSdk commonConfig.compileSdk

    defaultConfig {
        if (!isRelease) {
            applicationId appId.personal
        }
        minSdk commonConfig.minSdk
        targetSdk commonConfig.targetSdk
        versionCode commonConfig.versionCode
        versionName commonConfig.versionName

        testInstrumentationRunner commonConfig.testInstrumentationRunner
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {
    dependenciesConfig.each { k, v -> implementation v }
}

组件间不能横向依赖,如果组件间要通信通过公共基础库中的工具来实现,掌上重邮的组件间通信的工具使用的是阿里巴巴的开源框架ARouter,接下来探索ARouter的原理。

APT与JavaPoet

APT

Arouter框架使用了APT和JavaPoet技术

APT是什么? APT(Annotation Processing Tool) 是一种处理注释的工具,它对源代码文件进行检测找出 其中的Annotation,再找到对应的注解处理器执行对应的逻辑操作。

APT四大元注解

@Target(value=ElementType)

@Target被用来指明此Annotation所修饰的对象范围(即:被描述的注解可以用在什么地方)。Java中,注解(Annotation)可被用于以下位置 :

  • package、types(类、接口、枚举、Annotation类型)
  • 类型成员(方法、构造方法、成员变量、枚举值)
  • 方法参数和本地变量(如循环变量、catch参数)

注解的使用范围,通过Target的取值来指定。指定好以后,@Target修饰的元素一定是与其取的value相匹配的,否则编译会报错。

value取值(ElementType)常见的有:

ElementType含义
CONSTRUCTOR用于描述构造器
FIELD用于描述域(包括enum常量)
LOCAL_VARIABLE用于描述局部变量
METHOD用于描述方法
PACKAGE用于描述包
PARAMETER用于描述参数
TYPE用于描述类、接口(包括注解类型) 或enum声明

注:PACKAGE,它并不是使用在一般的类中,而是用在固定的文件package-info.java中。这里需要强调命名一定是“package-info”

这里需要特殊说明的是,在以前的Java版本中,开发者只能将注解(Annotation)写在声明中。但从Java 8开始,注解可以写在使用类型的任何地方,例如声明、泛型和强制类型转换等语句:

@Encrypted String str;
List<@NonNull String> strs;
test = (@Immutable Test) tmpTest;
复制代码

针对这个拓展,JAVA8对原有的@Target的取值做了扩充,引入了新的类型(TYPE)注解,即ElmentType增加了:

ElementType含义
TYPE_PARAMETER表示注解可以用在类型变量的声明语句中(如 class Test {...})
TYPE_USE表示注解可以用在使用类型的任何语句中(如声明语句、泛型和强转)

关于类型的解释参考上文。

@Retention(value=RetentionPolicy)

@Retention,翻译为保留,指示了一个注解被保留的时间期限,一个被其修饰的注解会被保留到其value指定三个阶段的其中之一,如果注解类型声明中不存在Retention注解,则保留策略默认为CLASS

RetentionPolicy含义
RetentionPolicy.SOURCE只在源代码级别保留,编译时就会被忽略
RetentionPolicy.CLASS在编译时被保留,在class文件中存在,但JVM将会忽略
RetentionPolicy.RUNTIME被JVM保留,所以他能在运行时被JVM或其他使用反射机制的代码所读取和使用

@Documented

被@Documented修饰的注解,用来表示这个被修饰注解应该被 javadoc工具记录。默认情况下,javadoc是不包括注解的。但如果声明注解时指定了@Documented,则它会被javadoc之类的工具处理,所以注解类型信息也会被包括在生成的文档中。

@Inherited

如果一个用来修饰class的注解,希望被这个class的sub-class继承,则可以对这个注解使用@Inherited修饰。 上面这句话强调了以下两点:

  • 注解的可继承性。当自定义的注解希望被继承时,就要使用@Inherited修饰
  • @Inherited只在修饰class时有效,修饰其他类型时无效

APT提供的四个辅助工具

这四个工具,我们通过在AbstractProcessor的实现类中,通过ProcessingEnvironment即可获得:

private Filer mFiler;
    private Elements mElementUtils;
    private Messager mMessager;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mElementUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
        mFiler = processingEnv.getFiler();
    }
复制代码

Filer

从名字看得出来,与文件相关的操作会用到。一般配合JavaPoet来生成Java文件

Messager

它提供给注解处理器一个报告错误、警告信息的途径。当我们自定义的注解处理器运行时报错时,那么运行注解处理器的JVM也会崩溃,打印出一些不容易被应用开发者读懂的日志信息。这时,我们可以借助Messager输出一些调试信息,以更直观的方式提示程序运行的错误。

Types

Types是一个用来操作TypeMirror的工具。TypeMirror是Element中通过adType()方法得到的一个对象。它保存了元素的具体信息,比如Element是一个类,那么其成员详细信息就保存在TypeMirror中。

Elements

Elements是一个用来处理Element的工具。这里不详细展开了。用到的时候会提到。

JavaPoet

JavaPoet可以为我们在编译器根据我们写的代码自动生成代码,有许多框架使用了JavaPoet如DataBinding,Arouter,黄油🔪等。

具体使用方法看代码吧

JavaPoet一些基础知识

四个表达Java文件元素的常用类

这些用来表达Java文件元素的类

类名含义
MethodSpec代表一个构造函数或方法声明
TypeSpec代表一个类,接口,或者枚举声明
FieldSpec代表一个成员变量,一个字段声明
ParameterSpec代表一个参数,可用来生成参数
AnnotationSpec代表一个注解
JavaFile包含一个顶级类的Java文件

常用方法

addCodeaddStatement用来增加代码
MethodSpec main = MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "  total += i;\n"
        + "}\n")
    .build();

生成的是

void main() {
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += i;
  }
}
  • addCode用于增加极简代码。即代码中仅包含纯Java基础类型和运算。
  • addStatement用于增加一些需要import方法的代码。如上面的.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") 就需要使用.addStatement来声明。
beginControlFlow和endControlFlow,流控方法

流控方法主要用来实现一些流控代码的添加,比上面的add方法看着美观一点。比如上面的代码,可以改写为:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();

占位符

$L字面量(Literals)

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 0")
      .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
      .addStatement("result = result $L i", op)
      .endControlFlow()
      .addStatement("return result")
      .build();
}

当我们传参调用时,coputeRange("test", 0, 10, "+")它能生成的代码如下:

int test(){
    int result = 0;
    for(int i = 0; i < 10; i++) {
        result = result + i;
    }
    return result;
}

$S 字符串常量(String)

$T 类型(Types)

MethodSpec today = MethodSpec.methodBuilder("today")
    .returns(Date.class)
    .addStatement("return new $T()", Date.class)
    .build(); //创建today方法
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(today)
    .build(); //创建HelloWorld类
JavaFile javaFile = JavaFile.builder("com.xm.helloworld", helloWorld).build();
javaFile.writeTo(System.out);//写java文件

生成的代码如下,我们看到,它会自动导入所需的包。这也是我们使用占位符的好处,也是使用JavaPoet的一大好处。

package com.xm.helloworld;

import java.util.Date;

public final class HelloWorld {
  Date today() {
    return new Date();
  }
}

如果我们想要导入自己写的类怎么办?上面的例子是传入系统的class,这里也提供一种方式,通过ClassName.get(”类的路径”,”类名“),结合$T可以生成

ClassName testClass = ClassName.get("com.xm", "TestClass");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOftestClasses = ParameterizedTypeName.get(list, testClass);

MethodSpec xNpe = MethodSpec.methodBuilder("xNpe")
    .returns(listOftestClasses)
    .addStatement("$T result = new $T<>()", listOftestClasses, arrayList)
    .addStatement("result.add(new $T())", testClass)
    .addStatement("result.add(new $T())", testClass)
    .addStatement("result.add(new $T())", testClass)
    .addStatement("return result")
    .build();

生成的代码如下:

package com.xm.helloworld;

import com.xm.TestClass;
import java.util.ArrayList;
import java.util.List;

public final class HelloWorld {
  List<TestClass> xNpe() {
    List<TestClass> result = new ArrayList<>();
    result.add(new TestClass());
    result.add(new TestClass());
    result.add(new TestClass());
    return result;
  }
}

Javapoet 同样支持import static,通过addStaticImport来添加:

JavaFile.builder("com.xm.helloworld", hello)
    .addStaticImport(TestClass, "START")
    .addStaticImport(TestClass2, "*")
    .addStaticImport(Collections.class, "*")
    .build();

$N 命名(Names)

通常指我们自己生成的方法名或者变量名等等。比如这样的代码:

public String byteToHex(int b) {
  char[] result = new char[2];
  result[0] = hexDigit((b >>> 4) & 0xf);
  result[1] = hexDigit(b & 0xf);
  return new String(result);
}

public char hexDigit(int i) {
  return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}

这个例子中,我们在byteToHex中需要调用到hexDigit方法,我们就可以用$N来表示这种引用。然后通过传递方法名,达到这种调用语句的生成。

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
    .build();

MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();

自顶向下,构建Java类的元素

普通方法

我们在定义方法时,也要对方法增加一些修饰符,如Modifier.ABSTRACT。可以通过addModifiers()方法:

MethodSpec test = MethodSpec.methodBuilder("test")
    .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addMethod(test)
    .build();

将会生成如下代码:

public abstract class HelloWorld {
  protected abstract void test();
}

构造器

构造器只不过是一个特殊的方法,因此可以使用MethodSpec来生成构造器方法。使用constrctorBuilder来生成:

MethodSpec flux = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "greeting")
    .addStatement("this.$N = $N", "greeting", "greeting")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(flux)
    .build();

将会生成代码:

public class HelloWorld {
  private final String greeting;

  public HelloWorld(String greeting) {
    this.greeting = greeting;
  }
}

各种参数

参数也有自己的一个专用类ParameterSpec,我们可以使用ParameterSpec.builder()来生成参数,然后MethodSpec的addParameter去使用,这样更加优雅。

ParameterSpec android = ParameterSpec.builder(String.class, "android")
    .addModifiers(Modifier.FINAL)
    .build();

MethodSpec welcomeOverlords = MethodSpec.methodBuilder("test")
    .addParameter(android)
    .addParameter(String.class, "robot", Modifier.FINAL)
    .build();

生成的代码

void test(final String android, final String robot) {
}

字段,成员变量

可以使用FieldSpec去声明字段,然后加到类中:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(android)
    .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
    .build();

生成代码:

public class HelloWorld {
  private final String android;
  private final String robot;
}

通常Builder可以更加详细的创建字段的内容,比如javadoc、annotations或者初始化字段参数等,如:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .initializer("$S + $L", "Pie v.", 9.0)//初始化赋值
    .build();

对应生成的代码:

private final String android = "Pie v." + 9.0;

接口

接口方法必须是PUBLIC ABSTRACT并且接口字段必须是PUBLIC STATIC FINAL ,使用TypeSpec.interfaceBuilder如下:

TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(FieldSpec.builder(String.class, "KEY_START")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
        .initializer("$S", "start")
        .build())
    .addMethod(MethodSpec.methodBuilder("beep")
        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
        .build())
    .build();

生成的代码如下:

public interface HelloWorld {
  String KEY_START = "start";
  void beep();
}

枚举类型

使用TypeSpec.enumBuilder来创建,使用addEnumConstant来添加枚举值:

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK")
    .addEnumConstant("SCISSORS")
    .addEnumConstant("PAPER")
    .build();

生成的代码

public enum Roshambo {
  ROCK,
  SCISSORS,
  PAPER
}

匿名内部类

需要使用Type.anonymousInnerClass(""),通常可以使用$L占位符来指代:

TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
    .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
    .addMethod(MethodSpec.methodBuilder("compare")
        .addAnnotation(Override.class)
        .addModifiers(Modifier.PUBLIC)
        .addParameter(String.class, "a")
        .addParameter(String.class, "b")
        .returns(int.class)
        .addStatement("return $N.length() - $N.length()", "a", "b")
        .build())
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addMethod(MethodSpec.methodBuilder("sortByLength")
        .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
        .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
        .build())
    .build();

生成代码:

void sortByLength(List<String> strings) {
  Collections.sort(strings, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
      return a.length() - b.length();
    }
  });
}

定义匿名内部类的一个特别棘手的问题是参数的构造。在上面的代码中我们传递了不带参数的空字符串。TypeSpec.anonymousClassBuilder("")

注解

注解使用起来比较简单,通过addAnnotation就可以添加:

MethodSpec toString = MethodSpec.methodBuilder("toString")
    .addAnnotation(Override.class)
    .returns(String.class)
    .addModifiers(Modifier.PUBLIC)
    .addStatement("return $S", "Hello XiaoMing")
    .build();

生成代码

@Override
public String toString() {
  return "Hello XiaoMing";
}

通过AnnotationSpec.builder() 可以对注解设置属性:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(Headers.class)
        .addMember("accept", "$S", "application/json; charset=utf-8")
        .addMember("userAgent", "$S", "Square Cash")
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();

代码生成如下

@Headers(
    accept = "application/json; charset=utf-8",
    userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
复制代码
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(HeaderList.class)
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "Accept")
            .addMember("value", "$S", "application/json; charset=utf-8")
            .build())
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "User-Agent")
            .addMember("value", "$S", "Square Cash")
            .build())
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class
    .build

生成Java文件

生成Java文件,我们需要用到上文提到的FilerElements。注意下面这段代码,重要的是包名,类名的指定。这里生成的文件名,一般会遵循某个约定,以便事先写好反射代码。

//获取待生成文件的包名
public String getPackageName(TypeElement type) {
    return mElementUtils.getPackageOf(type).getQualifiedName().toString();
}

//获取待生成文件的类名
private static String getClassName(TypeElement type, String packageName) {
    int packageLen = packageName.length() + 1;
    return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
}

//生成文件
private void writeJavaFile() {
    String packageName = getPackageName(mClassElement);
    String className = getClassName(mClassElement, packageName);
    ClassName bindClassName = ClassName.get(packageName, className);
    TypeSpec finderClass = TypeSpec.classBuilder(bindClassName.simpleName() + "?Injector")
        .addModifiers(Modifier.PUBLIC)
        .addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR,
                                                     TypeName.get(mClassElement.asType())))
        .addMethod(methodBuilder.build())
        .build();
    //使用JavaFile的builder来生成java文件
    JavaFile.builder(packageName, finderClass).build().writeTo(mFiler);
}

新建三个模块

arouter_annotation

arouter_api

arouter_compiler

对应的build.gradle文件:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}


sourceCompatibility = "7"
targetCompatibility = "7"
plugins {
    id 'com.android.library'
}

android {
    compileSdk 30

    defaultConfig {
        minSdk 21
        targetSdk 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

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

dependencies {

    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // 依赖注解
    implementation project(':arouter_annotation')
}
apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'


    implementation "com.squareup:javapoet:1.10.0"

    // 引入annotation,处理@ARouter注解
    implementation project(':arouter_annotation')
}

// java控制台输出中文乱码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

sourceCompatibility = "7"
targetCompatibility = "7"

具体的看代码吧

Kotlin中使用注解处理器要注意的

如果是 kotlin 的项目,依赖注解处理器时要使用 kapt。Java项目则使用 annotationProcessor

在 app 的 gradle 中引入 注解处理器的 module 的时候一定要使用 kapt,不然无法生成 文件。

引入方式如下:

plugins {
...
    id 'kotlin-kapt'
    id 'kotlin-android'
}

....
....

dependencies {
     ....

    kapt project(':arouter_compiler')
}

如果 注解处理器使用的也是 kotlin,则还需要修改一下东西,如下:
注解处理器的gradle 文件:

apply plugin: 'java-library'
apply plugin: 'kotlin'
apply plugin: 'kotlin-android-extensions'
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(':libnavannotation')

    implementation this.rootProject.depsLibs.fastjson
    //auto service
    implementation  this.rootProject.depsLibs.autoservice
    implementation this.rootProject.depsLibs.corektx
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}

sourceCompatibility = "8"
targetCompatibility = "8"

评论
发表评论
       
       
取消