Android Studio中的多模块注解处理

13
我在Android Studio中有一个包含多个模块的项目。一个模块可能依赖于另一个模块,例如:
模块PhoneApp -> 模块FeatureOne -> 模块Services
我已经将我的注解处理器包含在根模块中,但是android-apt注解处理器只会在最顶层(PhoneApp)进行,因此理论上它应该在编译时访问所有模块。然而,在生成的Java文件中,我只看到了在PhoneApp中注释的类,而没有其他模块中的类。
PhoneApp/build/generated/source/apt/debug/.../GeneratedClass.java

在其他模块中,我发现 intermediates 目录中有一个生成的文件,其中只包含该模块的已注释文件。
FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.class
FeatureOne/build/intermediates/classes/debug/.../GeneratedClass.java

我的目标是在PhoneApp中拥有一个单一的生成文件,使我能够访问所有模块的注释文件。 我不确定为什么每个代码生成过程都在运行并未能将所有注释聚合到PhoneApp中。 感谢任何帮助。
代码相当简单,直接了当,checkIsValid()被省略因为它可以正常工作。
注解处理器:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    try {

        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GuiceModule.class)) {
            if (checkIsValid(annotatedElement)) {
                AnnotatedClass annotatedClass = new AnnotatedClass((TypeElement) annotatedElement);
                if (!annotatedClasses.containsKey(annotatedClass.getSimpleTypeName())) {
                    annotatedClasses.put(annotatedClass.getSimpleTypeName(), annotatedClass);
                }
            }
        }

        if (roundEnv.processingOver()) {
            generateCode();
        }

    } catch (ProcessingException e) {
        error(e.getElement(), e.getMessage());
    } catch (IOException e) {
        error(null, e.getMessage());
    }

    return true;
}

private void generateCode() throws IOException {
    PackageElement packageElement = elementUtils.getPackageElement(getClass().getPackage().getName());
    String packageName = packageElement.isUnnamed() ? null : packageElement.getQualifiedName().toString();

    ClassName moduleClass = ClassName.get("com.google.inject", "Module");
    ClassName contextClass = ClassName.get("android.content", "Context");
    TypeName arrayOfModules = ArrayTypeName.of(moduleClass);

    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("juice")
            .addParameter(contextClass, "context")
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
            .returns(arrayOfModules);

    methodBuilder.addStatement("$T<$T> collection = new $T<>()", List.class, moduleClass, ArrayList.class);

    for (String key : annotatedClasses.keySet()) {

        AnnotatedClass annotatedClass = annotatedClasses.get(key);
        ClassName className = ClassName.get(annotatedClass.getElement().getEnclosingElement().toString(),
                annotatedClass.getElement().getSimpleName().toString());

        if (annotatedClass.isContextRequired()) {
            methodBuilder.addStatement("collection.add(new $T(context))", className);
        } else {
            methodBuilder.addStatement("collection.add(new $T())", className);
        }

    }

    methodBuilder.addStatement("return collection.toArray(new $T[collection.size()])", moduleClass);

    TypeSpec classTypeSpec = TypeSpec.classBuilder("FreshlySqueezed")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .addMethod(methodBuilder.build())
            .build();

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

这只是一个与Guice一起使用的注解处理演示,如果有人感兴趣。

那么我该如何使所有模块中的所有带注解的类都包含在生成的PhoneApp .java文件中呢?


这是一个很好的问题。我认为很多人都会欣赏你在找到解决方案时分享它,但我还没有找到。在dbFlow项目问题部分有关处理器限制的讨论可能被认为是证明无法实现所需跨模块注释处理器的证据。 - konata
由于注解处理器会为每个模块单独运行,因此您可以尝试增量方法,但这略微取决于您的情况。如果在处理下一个模块之后不需要更改整个类,而只需向现有类添加新行,则可能有效:
  1. 在处理第一个模块时,在指定位置(PhonApp模块的generated树中的某个位置)生成您的类;
  2. 在处理下一个模块时,检查生成的类是否存在,并将新代码添加到其中。
- konata
由于这只是演示中的一个演示版本,我没有继续寻找解决方案。(尽管确实出现了一个问题,为什么生成的文件没有包括所有模块数据) 我考虑过使用Gradle任务来复制每个模块的生成文件,但更希望有一种不需要依赖构建工具的解决方案。 - fakataha
你找到解决方案了吗? - Aman Singhal
请查看我在10月20日上面的评论。 - fakataha
你找到了这个问题的解决方案吗? - savepopulation
2个回答

2

永远不晚回答SO上的问题,所以...

我在工作中遇到了一个非常相似的问题。

而且我解决了它。

简短版本

有关moduleB中生成的类在moduleA中的所有信息,您需要知道的只是包名和类名。这可以存储在放置在已知包中的某种MyClassesRegistrar生成的类中。使用后缀来避免名称冲突,通过包获取注册表。实例化它们并使用其中的数据。

长版本

首先-您将无法仅在最顶层模块(让我们称其为“应用”模块,因为您的典型Android项目结构如此)中包含仅在编译时依赖项。注释处理就是这样,据我所知-不能做任何事情。

现在到细节。我的任务是这样的: 我有人工编写的带注释的类。我将它们命名为“事件”。在编译时,我需要为这些事件生成辅助类,以包含它们的结构和内容(静态可用(注释值、常量等)和运行时可用(当我使用后者时,我将事件对象传递给这些帮助程序)。帮助程序类名取决于事件类名及其后缀,因此在代码生成完成之前我不知道它。

因此,在生成辅助程序之后,我创建了一个工厂,并生成了代码以根据提供的MyEvent.class提供新的帮助程序实例。问题在于:我只需要一个工厂在应用程序模块中,但它应该能够为库模块中的事件提供帮助程序-这不能直接完成。

我所做的是:

  1. 跳过为我的应用程序模块所依赖的模块生成工厂;

  2. 在非应用程序模块中生成所谓的HelpersRegistrar实现:

    -它们都共享相同的包(稍后您将知道原因);

    -它们的名称不会发生冲突,因为有后缀(请参见下文);

    -通过javac "-Amylib.suffix=MyModuleName"参数进行应用程序模块和库模块之间的区分,用户必须设置此限制,但这是一个小限制。对于应用程序模块,不必指定后缀;

    -HelpersRegistrar生成的实现可以为将来的工厂代码生成提供我需要的所有内容:事件类名、帮助程序类名、包(这两个共享包以实现帮助程序和事件之间的包可见性)-所有字符串,都包含在POJO中;

  3. 在应用程序模块中,我像往常一样生成帮助程序,然后通过它们的包获取HelperRegistrars,实例化它们,运行其内容以使用其他模块提供来丰富我的工厂的代码。我所需要的只是类名和包。

  4. 哇!我的工厂现在可以提供来自应用程序模块和其他模块的帮助程序实例。

唯一的不确定性是在应用程序模块和其他模块中创建和运行处理器类实例的顺序。我没有找到任何可靠的信息,但运行我的示例显示编译器(因此,代码生成)首先在我们依赖的模块中运行,然后在应用程序模块中运行(否则,应用程序模块的编译将出问题)。这使我们有理由期望在不同模块中执行代码处理器的已知顺序。
另一种稍微类似的方法是:跳过注册器,在所有模块中生成工厂,并编写应用程序模块中的工厂以使用您获取并以与上面的注册器相同的方式命名的其他工厂。
可以在此处查看示例:https://github.com/techery/janet-analytics - 这是一个库,我在其中应用了这种方法(没有注册器,因为我有工厂,但您可能不是这种情况)。
P. S .:后缀参数可以切换为更简单的“-Amylibraryname.library=true”,并且工厂/注册器名称可以自动生成/递增。

有人如何在KSP中实现它? - David
@David 我之前没有听说过KSP,所以我无法评估它是否足以缓解这样的任务。我猜你会成为这里的开拓者) - Den Drobiazko

0

不要使用Filer来保存生成的文件,而是使用常规的Java文件写入。在处理过程中,您需要将对象序列化到临时文件中,因为即使是静态变量也无法在模块之间保存。在编译之前,配置Gradle以删除临时文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接