11

我编写了一个简单的注释处理器(只是为了好玩),它将生成一些我在以前的项目中编写的样板代码。它实际上通过收集 Activity 类上的注释来生成如下所示的模块

@Module
abstract class ActivityInjectorModule {
  @ContributesAndroidInjector
  abstract fun providesMain2Activity(): Main2Activity

  @ContributesAndroidInjector
  abstract fun providesMainActivity(): MainActivity
}

但是,当我使用 dagger 运行它时,dagger 似乎无法找到由我的注释处理器生成的类。虽然类已生成并存在于生成的目录中,但我可以在我的源代码中使用它,但在编译时,dagger 会产生以下异常。有什么专家建议吗?

error: cannot find symbol
@dagger.Component(modules = {dagger.android.AndroidInjectionModule.class, com.mallaudin.daggietest.di.AppModule.class, ActivityInjectorModule.class})
                                                                                                                       ^
  symbol: class ActivityInjectorModule

这是主要的应用程序组件。

@Singleton
@Component(
    modules = [
        AndroidInjectionModule::class,
        AppModule::class,
        ActivityInjectorModule::class
    ]
)
interface AppComponent : AndroidInjector<App> {


    @Component.Builder
    interface Builder {

        fun addContext(@BindsInstance ctx: Context): Builder

        fun build(): AppComponent
    }
}

ActivityInjectorModule类由注解处理器生成,存在于生成目录中。

应用类

class App : DaggerApplication() {
    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.builder().addContext(this).build()
    }
}

如果我自己创建生成的类,一切都会完美运行。在编译时不知何故,当我的注释处理器生成时,dagger 无法找到该类。

在 Yuriy Kulikov 的回答之后,

尤里的回答

您可以看到生成的文件在同一个包中,但也使用完全限定名称引用。仍然匕首报告错误。

如果有人想进行实验,这里是github 存储库的链接

4

3 回答 3

10

解决方案:

  1. 生成java代码。Kapt 不支持多轮
  2. 在尽可能早的回合写入生成的文件。

解释:

Javac注释处理器使用轮次而不是定义处理器顺序。所以通常简化的算法是这样的:

  1. 收集所有 java 源
  2. 运行所有注释处理器。任何注释处理器都可以使用Filer生成新文件。
  3. 收集所有生成的文件,如果有,请再次运行第 2 步。
  4. 如果没有生成文件,则在RoundEnvironment.processingOver()返回的地方再运行一轮true,表示这是最后一轮。

这是对该过程的一个很好的解释

现在稍微介绍一下kaptKapt 使用 javac运行注释处理器。为了使它成为可能,它首先运行 kotlin 编译器以生成 java 存根文件javac在它们上运行。目前kapt 不支持多轮,这意味着它不会为注释处理器生成的 kotlin 类生成 java 存根。 注意:javac仍然使用多轮,它只是无法拾取生成的 kotlin 源。

所以,回到你的问题。一种可能的选择是将生成的类移动到一个单独的模块中,就像这里描述的那样。

但最简单的选择是直接生成 java 代码,您生成的 java 类将被自动拾取javac,启动第二轮注释处理,dagger 将在其中处理它们。

还有一些注意事项:

  • 时不要生成你的代码RoundEnvironment.processingOver() == true,它不会触发另一轮。在您看到注释的同一轮中生成它。
  • 要使生成的代码对注释处理器可见,请使用Filer编写它。
于 2019-08-04T19:50:55.900 回答
4

我不知何故错过了您正在使用 kapt 的新答案。如果您将其添加到 build.gradle 中,Kapt 可以处理您的类,即使没有完整的限定名(这很了不起):

kapt {
    arguments {
        arg("argumentIncremental", 'true')
    }

    correctErrorTypes = true

}

有关此的更多信息:https ://kotlinlang.org/docs/reference/kapt.html#non-existent-type-correction


以前的答案可能很有用,因为有人对 gradle 中的 annotationProcessor (apt) 有同样的问题。

简短回答:使用 ActivityInjectorModule 的完全限定名称:

@dagger.Component(modules = {dagger.android.AndroidInjectionModule.class, com.mallaudin.daggietest.di.AppModule.class, com.mallaudin.daggietest.di.ActivityInjectorModule.class})

或者将两个文件放在同一个包中。

长答案:Dagger 是一个注释处理器,它在您的代码编译之前运行,并且(可能)在您的其他注释处理器运行之前运行。处理器运行的顺序没有定义。

Dagger注解处理器将处理带有@dagger.Component注解的TypeElement,它会尝试查找包括“ActivityInjectorModule.class”在内的所有模块。问题是,ActivityInjectorModule 可能还没有生成。因此“ActivityInjectorModule”此时将没有包。Dagger 将假定 ActivityInjectorModule 与 Component 类位于同一个包中,并且不会添加导入。通常的解决方法是对生成的类使用全限定名称,如果它们被其他注释处理器使用的话。有时将注释处理移动到差异 gradle 模块是有意义的,但我不认为这就是你想要的。

于 2019-08-03T12:38:28.323 回答
0

可能有一个更优雅的方法来解决这个问题,但最简单和最可靠的解决方案是执行两次传递javac- 一次只运行您的注释处理器,第二次执行它通常所做的所有事情。

javac 文档指定了两个可以帮助您的选项。

-proc:{无,仅}

控制是否完成注释处理和/或编译。-proc:none 表示编译在没有注释处理的情况下进行。-proc:only 表示只进行注解处理,不进行任何后续编译。

-处理器类 1 [,类 2,类 3...]

要运行的注释处理器的名称。这绕过了默认的发现过程。

第一遍(仅运行您自己的注释处理器)是

javac -proc:only -processor com.foo.bar.MyProcessor MyProject/src/*

第二遍(常规构建)是

javac MyProject/src/*

如果您使用的是 Ant 或 Maven 之类的东西,您应该能够更新构建指令以使编译器通过两次,而只需最少的工作量。

编辑:这是我对 Gradle 指令的尝试

我没有使用 Gradle 的经验,但似乎你需要做这样的事情。

在您的 Gradle 构建脚本中,您需要定义预处理任务并将对您的任务的依赖添加到 javaCompile 任务。

javaCompile.dependsOn myAnnotationTask

task myAnnotationTask(type: JavaCompile) {
    options.compilerArgs << '-proc:only' << '-processors com.foo.bar.MyAnnotationProcessor'
}
于 2019-08-02T05:19:44.443 回答