ClassNotFoundException 当“实现”用于库的库依赖时
Posted
技术标签:
【中文标题】ClassNotFoundException 当“实现”用于库的库依赖时【英文标题】:ClassNotFoundException when "implementation" is used for library dependency of a library 【发布时间】:2018-09-08 04:58:24 【问题描述】:我刚刚创建了一个库并上传到 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 个库,它们都运行良好;但这是我第一次在我自己的库中包含另一个 3rd 方库依赖项.)
编译与实现
然后我尝试将我的库的第 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 的某种错误(但谷歌说他们将在 2018 年底之前删除 compile
...),还是我做错了什么?
v0.9.1的完整源代码可以在here找到。
请注意,我没有直接从app
访问任何方法到TransitionsEverywhere
。具体来说,ClassNotFoundException
在我点击DropDownView
时发生,DropDownView
调用expand()
这是public
内部方法。
更多信息
为了消除其他因素,以下是我在将implementation
更改为compile
之前尝试过的事情,都没有运气:
-
清理并重建
卸载应用 + 清理并重新构建
使应用程序成为 MultiDexApplication
即时运行已被禁用
【问题讨论】:
当你使用实现的时候,你还必须添加你的库的依赖库。所以在这种情况下,你必须在你的应用模块中添加这些依赖库。 @Chandrakanth 你确定是这样吗?据我所知,implementation
仅表示应用程序本身无法访问依赖项的依赖关系。不然怎么能完全取代被弃用的compile
?
@SiraLam 看看这个。 ***.com/questions/44493378/…
当您使用实现时,这意味着 lib 仅适用于正确的代码。构建代码时它不会编译。当您使用编译时,这意味着在构建项目时,lib 代码也会在您的项目中构建。现在,当您在运行时遇到错误时,代码存在但依赖库不存在。希望你现在已经清楚了。
@WarrenFaith 哦,已经一年了。现在我确切地知道为什么 - 当您的项目具有使用实现包含依赖项 B 的依赖项 A 时,您的项目将看不到依赖项 B。但是由于我已经通过在线存储库包含了依赖项 A,所以您项目的 gradle 不知道依赖项 A 正在请求依赖项 B。因此,如果您从项目中请求依赖项 B,它将解决;但最好的方法是在您的库中使用api
而不是implementation
。简而言之,imo,每个模块都应该使用api
。只有***项目应该使用implementation
。
【参考方案1】:
我现在遇到了完全相同的问题,根据您的评论,我真的怀疑这是否应该是这样。我的意思是用api
替换库中的所有implementation
对干净的抽象没有意义。如果不需要,甚至有时甚至不应该被允许使用,我为什么要将我的库使用的依赖项暴露给消费者/应用程序。
我还检查了生成的 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>
我使用以下脚本添加依赖项,并基于 implementation
或 api
为它们添加了正确的范围 (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
范围内,它仍然是类路径的一部分,使类加载器知道这些类在运行时可用
编辑
如果您要进行大量更改并测试快照,请确保已为它们禁用缓存。将此添加到您的根 build.gradle 文件中:
allprojects
configurations.all()
// to make sure SNAPSHOTS are fetched again each time
resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
// more stuff here
【讨论】:
老实说,我认为您在这个主题上的知识绝对比我多得多,而且从我的角度来看,很少有 Android 开发人员能够真正理解您的解决方案(他们中的大多数人能够了解您的关键点并进行总结)。所以我只想问你是否认为需要这样做实际上是 Gradle 的糟糕设计? 相反。我正在为我调查未知领域,我真的认为我错过了自动和正确生成 POM 条目的点。所以我所做的主要是解决我缺少的信息 @WarrenFaith 我包含了脚本来生成你提到的 pom 文件。我仍然不断收到同样的错误?这事有进一步更新吗?几周以来我一直坚持这一点 @Kaveri 您是否检查过您生成的 pom 文件现在是否包含依赖项?还要检查 gradle 缓存是否不使用旧版本(缓存默认设置为 24h)。 (检查底部我的答案的编辑)以上是关于ClassNotFoundException 当“实现”用于库的库依赖时的主要内容,如果未能解决你的问题,请参考以下文章
当dex分包遇上NoClassDefFoundError&ClassNotFoundException
InflateException 和 ClassNotFoundException 导致应用程序崩溃
ClassNotFoundException:oracle.jdbc.driver.OracleDriver
Android 组件 ClassNotFoundException