【问题标题】:How to integrate the Reflections library in android studio using gradle with save and collect如何使用 gradle 将 Reflections 库集成到 android studio 中并保存和收集
【发布时间】:2017-12-21 04:50:50
【问题描述】:

我的问题与@ronmamo 在 github 上的 Reflections 库有关,并将其集成到我的 Android 项目中以动态访问从某个接口继承的所有类。

我对 gradle 或 maven 不太熟悉,所以这对我来说是一个学习过程,但我遇到了一个障碍,不知道如何调试/找到这个问题的答案。

正如@ronmamo 建议的here,我想在构建时生成一个包含所有扫描元数据的 xml 文件,并让 Reflections 稍后在我的代码中使用它时收集它:

虽然扫描可以在您的引导时轻松完成 应用程序 - 不应该花很长时间,有时这是一个好主意 将反射集成到您的构建生命周期中。用简单 Maven/Gradle/SBT/可以保存所有扫描的任何配置 在编译后将元数据转换为 xml/json 文件。后来,当 您的项目正在引导您可以让 Reflections 收集所有 这些资源并为您重新创建该元数据,使其成为 在运行时可用而无需重新扫描类路径 - 从而减少 引导时间。

我不确定我是否完全理解这种“引导”发生在整个过程中的确切位置(就 android 应用程序生命周期等甚至构建时间而言?)所以我不确定在哪里调用 Reflections.collect ()。目前,当用户到达程序中的某个点时,我稍后会在我的应用程序中调用它。

从几个 stackoverflow 帖子和 git 自述文件中,我现在想出了这个:([...] 表示删除了不相关的代码)

build.gradle(模块:app):

dependencies {
    [...]
    compile 'org.reflections:reflections:0.9.11'
}

build.gradle(项目:MyProject):

buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'org.reflections:reflections:0.9.11'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task runReflections {
    doLast {        
        org.reflections.Reflections("f.q.n").save("${sourceSet.main.output.classesDir}/META-INF/reflections/myproject-reflections.xml")
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

稍后在我的代码中(这个类在某个时候通过用户输入到达,而不是在应用启动时加载):

Reflections reflections = Reflections.collect(); 
Set<Class<? extends MyInterface>> allClasses = reflections.getSubTypesOf(MyInterface.class);

这会产生以下异常,因为“reflections”未实例化且值为“null”:

Attempt to invoke virtual method 'java.util.Set org.reflections.Reflections.getSubTypesOf(java.lang.Class)' on a null object reference

我知道生成的 .xml 文件位于进行构建的计算机上,我不确定这是否也传输到了 android 设备,所以我猜这就是失败的原因。但是在 apk 传输并在我的 android 设备上运行之前,我的 Java 代码在什么时候可以访问此文件?

我尝试从不同的角度以多种不同的方式在谷歌上搜索它,但我似乎找不到使反射在 Android 中工作的解决方案。我了解此处解释的原理,并且在构建时在 xml 文件中生成信息以在运行时提供类信息似乎更好。但是我该如何正确设置呢?

谢谢

【问题讨论】:

    标签: android gradle reflection android-gradle-plugin


    【解决方案1】:

    这里有一点先有鸡还是先有蛋的问题要解决

    1. 您希望Reflections API 访问从src/main/java 编译的类
    2. Gradle 任务和 Reflections 类由 Gradle 的 buildscript 类加载器加载
    3. src/main/java 中的类是在定义 buildscript 类加载器之后编译的

    您需要引入另一个可以访问已编译类的类加载器来打破循环依赖。然后可以将其传递给Reflections。例如:

    buildscript {
        classpath 'org.reflections:reflections:0.9.11'
    }
    task doReflectyStuff {
        dependsOn compileJava
        doLast {
            URL[] urls = sourceSets.main.runtimeClasspath.files.collect {
                it.toURI().toURL()
            }
            ClassLoader classLoader = new URLClassLoader(urls, null)
            Configuration config = new ConfigurationBuilder("com.mypackage", classLoader)
            Reflections reflections = new ReflectionsBuilder(config)
            ...
        }
    }
    

    类似问题请见here

    【讨论】:

    • 非常感谢!不幸的是,因为我的分数低于 15 分,所以我没有给你投票,但我接受了答案。
    【解决方案2】:

    这就是我所做的:任务是使用 Android 上的反射来处理提供依赖项的类(即在 JAR 文件中)。这个解决方案对我有用:

    顶级构建.gradle:

    dependencies {
        classpath 'org.reflections:reflections:0.9.10'
    }
    

    项目 build.gradle:

    afterEvaluate {
        android.applicationVariants.each { variant ->
            variant.javaCompiler.doLast {
                // get JAR file that contains the classes
                def collection = project.configurations.compile*.toURI().find { URI uri -> new File(uri).name.startsWith("startOfJarFileNameHere") }
                URL[] urls = collection.collect {
                    println "Collecting classes using Reflections from " + it
                    it.toURL()
                }
    
                // collect all classes
                ClassLoader classLoader = new URLClassLoader(urls, ClassLoader.systemClassLoader)
                org.reflections.Configuration config = org.reflections.util.ConfigurationBuilder
                        .build("package.name.of.interest.here")
                        .addClassLoader(classLoader)
                        .setUrls(urls)
                org.reflections.Reflections reflections = new org.reflections.Reflections(config)
    
                // save as JSON file into the assets folder
                // (a) generate file for current debug or release build
                reflections.save(
                    "${variant.javaCompiler.destinationDir}/../../assets/${variant.buildType.name}/reflections/my-reflections.json",
                        new org.reflections.serializers.JsonSerializer())
                // (b) always update fall-back file for debug (used when running app from Android Studio or IntelliJ)
                reflections.save(
                        "${variant.javaCompiler.destinationDir}/../../../../src/debug/assets/reflections/my-reflections.json",
                        new org.reflections.serializers.JsonSerializer())
            }
        }
    }
    

    Android 上的 Java 代码:

    InputStream iStream = getAssets().open("reflections/my-reflections.json");
    Configuration config = ConfigurationBuilder.build().setSerializer(new JsonSerializer());
    Reflections reflections = new Reflections(config);
    reflections.collect(iStream);
    Set<Class<? extends MyType>> myTypes = reflections.getSubTypesOf(MyType.class);
    

    【讨论】:

      【解决方案3】:

      我已经尝试在 Android 中使用 Reflections 已经有几天了,这就是我目前所取得的成就。我在项目的 build.gradle 中创建了一个任务:

      task myTask(dependsOn: compileJava) {
          doLast {
              URL[] urls = sourceSets.main.runtimeClasspath.files.collect {
                  it.toURI().toURL()
              }
              ClassLoader classLoader = new URLClassLoader(urls, ClassLoader.systemClassLoader)
              org.reflections.Configuration config = new ConfigurationBuilder()
                      .addClassLoader(classLoader)
                      .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("com.company.project")))
                      .addScanners(new SubTypesScanner(false))
              Reflections reflections = new Reflections(config)
              reflections.save("${sourceSets.main.output.classesDirs}/META-INF/reflections/mcommerce-reflections.json", new JsonSerializer())
          }
      }
      

      稍后在项目的一个类中,我实例化 Reflections,就像在 GitHub 的示例中所做的那样(我使用 Kotlin):

      val reflections = Reflections.collect(
                              "META-INF/reflections",
                              FilterBuilder().include(".*-reflections.json"),
                              JsonSerializer()
                      ) 
      

      如果 myTask 在终端上运行,则构建成功,但我收到此消息“给定扫描 url 为空。在配置中设置 url”,我在 Google 中搜索但没有发现任何有用的信息。

      我尝试了在 gradle 文件上配置反射的不同方法,但是当我收集它们时,我总是收到一个空实例。

      希望我的回答对大家有用。

      【讨论】:

      • 静态Reflections.collect(...) 方法假定目标类由与Reflections 本身相同的类加载器加载,但此处并非如此。您需要在知道自定义类加载器的Reflections 实例上使用非静态Reflections 方法。或者也许有一些静态方法也接受类加载器?
      猜你喜欢
      • 2014-01-01
      • 2013-05-13
      • 1970-01-01
      • 2015-03-14
      • 1970-01-01
      • 1970-01-01
      • 2015-04-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多