Class"实现"时的ClassNotFoundException用于库

时间:2018-03-29 09:34:53

标签: android android-studio gradle android-gradle dependencies

我刚创建了一个库并上传到bintray和jcenter。

在我的测试应用中,此库作为模块添加:

implementation project(':dropdownview')

一切都很好。

将库模块上传到jcenter后,我改为使用它:

implementation 'com.asksira.android:dropdownview:0.9.1

当库试图调用依赖于另一个库的方法时,会发生运行时错误

Caused by: java.lang.ClassNotFoundException: Didn't find class "com.transitionseverywhere.TransitionSet" on path: DexPathList[[zip file "/data/app/com.asksira.dropdownviewdemo-6fj-Q2LdwKQcRAnZHd2jlw==/base.apk"],nativeLibraryDirectories=[/data/app/com.asksira.dropdownviewdemo-6fj-Q2LdwKQcRAnZHd2jlw==/lib/arm64, /system/lib64, /system/vendor/lib64]]

(我正在关注this guide发布库。我在使用相同的方法之前发布了3个库,它们都运行得很好;但这是我第一次在我的程序中包含了另一个第三方库依赖项自己的图书馆。)

编译与实现

然后我尝试从

更改我的库的第三方库依赖项

implementation 'com.andkulikov:transitionseverywhere:1.7.9'

compile 'com.andkulikov:transitionseverywhere:1.7.9'

(请注意,这不是应用程序对我的库的依赖,而是我的库到另一个库)

然后再次上传到版本0.9.2的bintray。

implementation 'com.asksira.android:dropdownview:0.9.2

这次工作了吗?!

我的问题

这是Android Studio / Gradle的某种错误(但Google表示他们将在2018年底删除compile ...),或者我做错了什么?

可以找到v0.9.1的完整源代码here

请注意,我没有直接从appTransitionsEverywhere访问任何方法。具体来说,当我点击ClassNotFoundExceptionDropDownView调用DropDownView这是一个expand()内部方法时,会发生public

更多信息

为了消除其他因素,以下是我在将implementation更改为compile之前尝试过的事情,一切都没有运气:

  1. 清理并重建
  2. 卸载app + clean并重新构建
  3. 使应用程序成为MultiDexApplication
  4. 即时停止已被停用

1 个答案:

答案 0 :(得分:2)

我现在有完全相同的问题,根据您的评论,我真的怀疑这是应该的方式。我的意思是用implementation替换库中的所有api对于干净的抽象是没有意义的。为什么在不需要/有时甚至不允许使用我的库的已使用依赖项给使用者/应用程序的情况下

我还检查了生成的APK是否确实包含它抱怨找不到的类。

由于我之前遇到依赖性问题,所以我记得自己为该库改进了生成的POM。

在我对其进行改进之前,生成的pom看起来像这样:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>tld.yourdomain.project</groupId>
    <artifactId>library-custom</artifactId>
    <version>1.2.0-SNAPSHOT</version>
    <packaging>aar</packaging>
    <dependencies/>
</project>

我使用以下脚本添加了依赖关系,并基于implementationapi向其添加了正确的作用域(based on that nice info

apply plugin: 'maven-publish'

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    archiveClassifier = "sources"
}

task listDependencies() {
    // Curious, "implementation" also contains "api"...
    configurations.implementation.allDependencies.each { dep -> println "Implementation: ${dep}" }
    configurations.api.allDependencies.each { dep -> println "Api: ${dep}" }
}

afterEvaluate {
    publishing {
        publications {
            mavenAar(MavenPublication) {
                groupId libraryGroupId
                artifactId libraryArtefactId
                version versionName

                artifact sourceJar
                artifact bundleReleaseAar

                pom.withXml {
                    def dependenciesNode = asNode().appendNode('dependencies')
                    configurations.api.allDependencies
                            .findAll { dependency -> dependency.name != "unspecified" }
                            .each { dependency ->
                        addDependency(dependenciesNode.appendNode('dependency'), dependency, "compile")
                    }

                    configurations.implementation.allDependencies
                            .findAll { dependency -> !configurations.api.allDependencies.contains(dependency) }
                            .findAll { dependency -> dependency.name != "unspecified" }
                            .each { dependency ->
                        addDependency(dependenciesNode.appendNode('dependency'), dependency, "runtime")
                    }
                }
            }
        }

        repositories {
            maven {
                def snapshot = "http://repo.yourdomainname.tld/content/repositories/snapshots/"
                def release = "http://repo.yourdomainname.tld/content/repositories/releases/"
                url = versionName.endsWith("-SNAPSHOT") ? snapshot : release
                credentials {
                    username nexusUsername
                    password nexusPassword
                }
            }
        }
    }
}

def addDependency(dependencyNode, dependency, scope) {
    dependencyNode.appendNode('groupId', dependency.group)
    dependencyNode.appendNode('artifactId', dependency.name)
    dependencyNode.appendNode('version', dependency.version)
    dependencyNode.appendNode('scope', scope)
}

您需要了解的关键部分:

  • 如果您未定义范围,则将假定为“编译”
  • implementation依赖项也包含api个依赖项,只需运行任务listDependencies()来查看输出
  • 范围为runtime的API在应用程序/消费者中不可用,但是类路径的一部分。这样,使用者就无法直接访问那些依赖项,只能通过您自己的库提供的方法使那些依赖项“不可见”,但它们将成为类路径的一部分,因此当这些“不可见”依赖项的类被使用时,应用程序不会崩溃由类加载器加载。

上面的脚本现在生成以下pom:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>tld.yourdomain.project</groupId>
    <artifactId>library-custom</artifactId>
    <version>1.2.0-SNAPSHOT</version>
    <packaging>aar</packaging>
    <dependencies>
        <dependency>
            <groupId>tld.dependency</groupId>
            <artifactId>android-sdk</artifactId>
            <version>1.2.3</version>
            <scope>compile</scope> <!-- From api -->
        </dependency>
        <dependency>
            <groupId>tld.dependency.another</groupId>
            <artifactId>another-artifact</artifactId>
            <version>1.2.3</version>
            <scope>runtime</scope> <!-- From implementation -->
        </dependency>
        <!-- and much more -->
    </dependencies>
</project>

总结一下:

  • api交付了类,也使依赖项也可供消费者使用
  • implementation也提供了这些类,但没有使消费者可以使用该依赖项,但是,在定义了runtime范围的情况下,它仍将是类路径的一部分,从而使类加载器知道那些类是在运行时可用