草庐IT

android - ClassNotFoundException 当 "implementation"用于库的库依赖时

coder 2023-11-26 原文

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

在我的测试应用中,这个库被添加为一个模块:

实现项目(':dropdownview')

一切都很好。

库模块上传到jcenter后,我改用这个:

实现 '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 个库,它们都运行良好;但这是我第一次在我自己的库中包含另一个第三方库依赖项.)

编译与实现

然后我尝试更改我的库的第 3 方库依赖项

实现 'com.andkulikov:transitionseverywhere:1.7.9'

编译 'com.andkulikov:transitionseverywhere:1.7.9'

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

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

实现'com.asksira.android:dropdownview:0.9.2

这次成功了吗?!

我的问题

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

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

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

更多信息

为了排除其他因素,下面是我在将 implementation 更改为 compile 之前尝试过的事情,但都没有成功:

  1. 清理并重建
  2. 卸载应用 + 清理并重新构建
  3. 使应用程序成为 MultiDexApplication
  4. 即时运行已被禁用

最佳答案

我现在遇到了完全相同的问题,根据您的评论,我真的怀疑这是否应该是这样。我的意思是用 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>

我使用以下脚本添加依赖项,并基于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 范围内,它仍然是类路径的一部分,从而类加载器知道这些类在运行时可用

编辑

如果您要进行大量更改并测试您的快照,请确保您已禁用它们的缓存。将此添加到您的根 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 - ClassNotFoundException 当 "implementation"用于库的库依赖时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49552607/

有关android - ClassNotFoundException 当 "implementation"用于库的库依赖时的更多相关文章

  1. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  2. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  3. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  4. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  5. ruby-on-rails - 在 ruby​​ .gemspec 文件中,如何指定依赖项的多个版本? - 2

    我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这

  6. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  7. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  8. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  9. ruby-on-rails - 相关表上的范围为 "WHERE ... LIKE" - 2

    我正在尝试从Postgresql表(table1)中获取数据,该表由另一个相关表(property)的字段(table2)过滤。在纯SQL中,我会这样编写查询:SELECT*FROMtable1JOINtable2USING(table2_id)WHEREtable2.propertyLIKE'query%'这工作正常:scope:my_scope,->(query){includes(:table2).where("table2.property":query)}但我真正需要的是使用LIKE运算符进行过滤,而不是严格相等。然而,这是行不通的:scope:my_scope,->(que

  10. 使用 ACL 调用 upload_file 时出现 Ruby S3 "Access Denied"错误 - 2

    我正在尝试编写一个将文件上传到AWS并公开该文件的Ruby脚本。我做了以下事情:s3=Aws::S3::Resource.new(credentials:Aws::Credentials.new(KEY,SECRET),region:'us-west-2')obj=s3.bucket('stg-db').object('key')obj.upload_file(filename)这似乎工作正常,除了该文件不是公开可用的,而且我无法获得它的公共(public)URL。但是当我登录到S3时,我可以正常查看我的文件。为了使其公开可用,我将最后一行更改为obj.upload_file(file

随机推荐