我在Android Studio中有一个包含多个模块的项目。一个模块可能依赖于另一个模块,例如:
模块PhoneApp->模块功能一->模块服务
我已经在根模块中包含了我的注释处理,但android apt注释处理只发生在最顶层(PhoneApp),因此理论上它应该可以在编译时访问所有模块。然而,我在生成的java文件中看到的只是PhoneApp中注释的类,而不是其他模块中的类。
PhoneApp/build/generated/source/apt/debug/.../GeneratedClass.java
在其他模块中,我在中间目录中找到了一个生成的文件,该文件只包含该模块中的注释文件。
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文件中的所有带注释的类呢?
回答SO问题永远不会太迟,所以…
在工作中的一项任务中,我也遇到过类似的复杂情况。
我解决了这个问题。
短版本
关于从moduleA中的moduleB生成的类,您只需要知道包和类名。它可以存储在放置在已知包中的某种MyClassesRegistrar
生成的类中。使用后缀以避免名称冲突,按包获取注册器。实例化它们并使用其中的数据。
Lond版本
首先,你将不能只在最顶层的模块中包含你的编译时依赖项(让我们像你典型的android项目结构一样称之为"应用程序"模块)。注释处理就是不能以这种方式工作,而且据我所知,对此无能为力。
现在来看细节。我的任务是:我有人工编写的注释类。我将它们命名为"事件"。在编译时,我需要为这些事件生成助手类,以合并它们的结构和内容(静态可用(注释值、常量等)和运行时可用(使用后者时,我将事件对象传递给这些助手)。Helper类名依赖于带有后缀的事件类名,所以直到代码生成完成我才知道。
因此,在生成助手之后,我创建了一个工厂并生成代码,以提供基于MyEvent.class
提供的新助手实例。问题是:我只需要一个工厂内的应用程序模块,但它应该能够为库模块中的事件提供帮助——这不能简单地完成。
我所做的是:
-
跳过为我的应用程序模块所依赖的模块生成工厂;
-
在非应用程序模块中生成所谓的HelpersRegistrar实现:
–它们共享相同的包(稍后您会知道原因);
–它们的名称不会因为后缀而发生冲突(见下文);
–应用程序模块和库模块之间的区别是通过用户必须设置的javac
"-Amylib.suffix=MyModuleName"
参数来实现的——这是一个限制,但很小。不必为应用程序模块指定后缀;–HelpersRegistrar生成的实现可以提供未来工厂代码生成所需的一切:事件类名、助手类名、包(这两个共享包用于助手和事件之间的包可见性)-所有字符串,包含在POJO中;
-
在应用程序模块中,我生成助手-像往常一样,然后我通过他们的包获得HelperRegistrations,实例化他们,运行他们的内容,用从其他模块提供助手的代码丰富我的工厂。我所需要的只是类名和一个包。
-
瞧!我的工厂可以从应用程序模块和其他模块提供助手的实例。
剩下的唯一不确定性是在应用程序模块和其他模块中创建和运行处理器类实例的顺序。我还没有找到任何关于这方面的可靠信息,但运行我的例子表明,编译器(因此,代码生成)首先在我们依赖的模块中运行,然后在应用程序模块中运行(否则,应用程序模块的编译将被取消)。这让我们有理由期待不同模块中代码处理器执行的已知顺序。
另一种稍微相似的方法是:跳过注册器,在所有模块中生成工厂,并在应用程序模块中编写工厂以使用其他工厂,您可以通过与上面的注册器相同的方式获得和命名这些工厂。
示例如下:https://github.com/techery/janet-analytics-这是我应用这种方法的库(因为我有工厂,所以没有注册器,但你可能不是这样)。
第页。S.:后缀param可以切换到更简单的"-Amylibraryname.library=true",工厂/注册商名称可以自动生成/递增
与其使用Filer保存生成的文件,不如使用常规的java文件写入。处理时,您需要将对象序列化为临时文件,因为即使是静态变量也不会保存在模块之间。配置gradle以在编译前删除临时文件。