如何解决库冲突(apache commons-codec)问题。

8
我遇到了一个关于Android库的问题。
我想要使用org.apache.commons.codec.binary.Hex库(版本1.6)中的Hex.encodeHexString(Byte Array)方法。
在我的Android平台(SDK 2.3.1)上,commons-codec库的版本为1.3,但该版本中还没有此方法(只有encodeHex()方法)。
我将版本为1.6的jar库添加到我的Eclipse项目中(位于/libs目录下),但当我在模拟器上运行该项目时,我遇到了以下问题:
E/AndroidRuntime(1632): FATAL EXCEPTION: main
E/AndroidRuntime(1632): java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString

我如何指示操作系统使用正确的库?
我在Mac OS X上使用Java 1.6.0的Eclipse Juno。
抱歉我的英语不好,谢谢!
编辑:我的问题似乎可以用jarjar工具来解决。 http://code.google.com/p/google-http-java-client/issues/detail?id=75 有人可以帮我使用这个工具吗?我不知道如何创建Ant清单或jar文件。
谢谢!

正如您所提到的 - 解决此问题的一种方法是使用jarjar创建一个新的jar文件,该文件不会与Android的类发生冲突。解释+解决方案+已创建的jar文件(为我解决了问题)- http://priyanka-tyagi.blogspot.co.il/2013/03/dealing-with-java-hell-using-jarjar.html(感谢bianca节省了我们的时间...) - Dror Fichman
5个回答

10

回复晚了,但对某些人可能有用。

通过使用Maven Shade插件解决了问题。

该插件允许在编译时重命名冲突库的包名称。

用法:

   <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <executions>
            <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
                </goals>
            <configuration>
                    <relocations>
                        <relocation>
                                <pattern>org.apache.commons</pattern>
                                    <shadedPattern>com.example.shaded.org.apache.commons</shadedPattern>
                        </relocation>
                    </relocations>
                        <promoteTransitiveDependencies>true</promoteTransitiveDependencies>
            </configuration>
            </execution>
        </executions>
    </plugin>

这看起来是一个很好的答案。但是,当我将其添加到Maven构建脚本中时,生成的APK无法安装。它显示[INSTALL_PARSE_FAILED_NO_CERTIFICATES],并且当我使用jarsigner验证构建时,它会显示“jarsigner:java.lang.SecurityException:Manifest主属性的签名文件摘要无效”。有任何想法为什么会这样? - Aiden Fry
你的Maven构建配置没有为APK签名。我使用maven-jarsigner-plugin来完成此操作: <plugin> <artifactId>maven-jarsigner-plugin</artifactId> <version>1.2</version> <inherited>true</inherited> </plugin>通过正确配置maven-android-plugin,您还应该能够使用调试密钥对其进行签名。 - nbe_42
我已经配置了jarsigner,这个构建脚本已经运行了几个月了。我在构建执行中将Shade插件包含在我的maven-jarsigner-plugin下面,现在输出的SHADED apk是未签名的。 - Aiden Fry
好的,你配置了jarsigner插件吗?是否添加了正确的包含内容? < includes > < include > $ {project.build.directory} / $ {project.artifactId} .apk </ include > < include > $ {project.build.directory} / $ {project.artifactId} -SHADED.apk </ include > </ includes > - nbe_42
感谢您的指引 - 我已尝试了此方法,并尝试使用终端中的jarsigner工具手动签名文件,但仍在安装时遇到相同的问题。查看logcat日志,似乎无法找到我的assets/mydatabase.sqlite文件的任何证书而导致失败。不确定这是否相关或只是它检查的第一件事。我会在弄清楚后回来的。感谢您的帮助。 - Aiden Fry
最后我放弃了追逐Maven插件,而是按照这里的方法手动重新创建Jar。然后,我不得不将这个新的jar文件放入我的.m2/repositary中,并更新maven依赖项以指向这个新文件。5分钟搞定。再次感谢nbe,如果我能让它工作,你的解决方案就是正确的。但我没有几天时间来花在构建脚本上;-) - Aiden Fry

2

nbe_42的回答已经被扩展,完整的文档如下:

commons-codec-shaded jar项目:

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>commons-codec</groupId>
    <artifactId>commons-codec-shaded</artifactId>
    <name>Apache Commons Codec (shaded)</name>
    <!-- The version of this project specifies the Apache Commons Codec version which will
         be used, it must therefore match an existing (and preferably current) version. -->
    <version>1.9</version>
    <packaging>jar</packaging>

    <!--
      *************************************************************
       Rationale for this "shaded" version of Apache Commons Codec
      *************************************************************

      Context:
        Android includes an outdated version (v1.3) of commons-codec as an internal library.
        This library is not exposed in the Android SDK so app developers who want to rely on
        commons-codec need to treat it as an addition dependency and include it in the APK
        of their app. However, at runtime Android will always favour its internal version of
        the library which causes trouble when app code tries to call methods that don't
        exist in v1.3 but do exist in the version the developer expected to be using.

      Solution:
        After experimenting with many different variations the current (and final) solution
        to this problem is implemented in this project and does not require big hacks or
        changes in projects which depend on commons-codec, expect for declaring dependency
        on commons-codec-shaded (i.e. this project) instead of the original commons-codec.
        What we do here is take the "original" commons-codec library (currently version 1.9)
        and use the maven-shade-plugin to "shade" it, which means we modify the package name
        of the library (both in the compiled classes and the sources jar) in order to avoid
        the clash with Android's version. The package name is changes from
        "org.apache.commons.codec" to "shaded.org.apache.commons.codec". The result is
        published to the local Maven repository for other projects to use by simple
        dependency declaration on this project. Because we only apply the shading to
        commons-codec itself (and not to other classes using it; which is possible using the
        shade plug-in but doesn't work in combination with android-maven-plugin) any client
        classes which make use of commons-codec will have to import the new "shaded" package
        name instead of the old one.

      Issue on android-maven-plugin github which I posted to discuss all this:
        https://github.com/jayway/maven-android-plugin/issues/487
    -->

    <description>
     The Apache Commons Codec package contains simple encoder and decoders for
     various formats such as Base64 and Hexadecimal.  In addition to these
     widely used encoders and decoders, the codec package also maintains a
     collection of phonetic encoding utilities.
    </description>
    <url>http://commons.apache.org/proper/commons-codec/</url>
    <organization>
        <name>The Apache Software Foundation</name>
        <url>http://www.apache.org/</url>
    </organization>
    <licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
            <distribution>repo</distribution>
        </license>
    </licenses>

    <contributors>
        <contributor>
            <name>Matthias Stevens</name>
            <email>m.stevens {at} ucl.ac.uk</email>
            <roles>
                <role>Shading for use on Android</role>
            </roles>
        </contributor>
        <!-- see commons-codec:commons-codec pom for original contributors/developers -->
    </contributors>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.6</maven.compiler.source>
        <maven.compiler.target>1.6</maven.compiler.target>
        <commons-codec-package>org.apache.commons.codec</commons-codec-package>
        <shading.prefix>shaded</shading.prefix>
        <shaded-commons-codec-package>${shading.prefix}.${commons-codec-package}</shaded-commons-codec-package>
        <commons-codec-src-folder>${project.build.directory}/commons-codec-src</commons-codec-src-folder>
        <commons-codec-res-folder>${project.build.directory}/commons-codec-res</commons-codec-res-folder>
        <manifest.path>${project.build.directory}/MANIFEST.MF</manifest.path>
        <!-- plugin versions -->
        <dependency-plugin-version>2.9</dependency-plugin-version>
        <compiler-plugin-version>3.2</compiler-plugin-version>
        <antrun-plugin-version>1.7</antrun-plugin-version>
        <jar-plugin-version>2.5</jar-plugin-version>
        <source-plugin-version>2.4</source-plugin-version>
        <shade-plugin-version>2.3</shade-plugin-version>
        <bundle-plugin-version>2.5.3</bundle-plugin-version>
        <!-- taken/modified from: http://svn.apache.org/repos/asf/commons/proper/commons-parent/trunk/pom.xml -->
        <commons.osgi.symbolicName>${shaded-commons-codec-package}</commons.osgi.symbolicName>
        <commons.osgi.export>${shaded-commons-codec-package}.*;version=${project.version};-noimport:=true</commons.osgi.export>
        <commons.osgi.import>*</commons.osgi.import>
        <commons.osgi.dynamicImport />
        <commons.osgi.private />
    </properties>

    <build>
        <finalName>${project.artifactId}</finalName>
        <sourceDirectory>${commons-codec-src-folder}</sourceDirectory>      
        <resources>
            <resource>
                <!-- txt files in shaded\org\apache\commons\codec\language\bm -->
                <directory>${commons-codec-res-folder}</directory>
                <includes>
                    <include>${shading.prefix}/**/*.txt</include>
                </includes>
            </resource>
            <resource>
                <!-- LICENSE & NOTICE files -->
                <directory>${commons-codec-res-folder}/META-INF</directory>
                <targetPath>META-INF</targetPath>
                <includes>
                    <include>*.txt</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <!-- fetch & unpack commons-codec sources and resources -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>${dependency-plugin-version}</version>
                <executions>
                    <execution>
                        <id>unpack_commons-codec_sources_and_resources</id>
                        <phase>process-sources</phase>
                        <goals>
                            <goal>unpack</goal>
                        </goals>
                        <configuration>
                            <artifactItems>
                                <!-- commons-codec sources -->
                                <artifactItem>
                                    <groupId>commons-codec</groupId>
                                    <artifactId>commons-codec</artifactId>
                                    <!-- the project version specifies the commons-codec version to use: -->
                                    <version>${project.version}</version>
                                    <classifier>sources</classifier>
                                    <overWrite>true</overWrite>
                                    <excludes>**/*.txt,META-INF/*</excludes>
                                    <outputDirectory>${commons-codec-src-folder}</outputDirectory>
                                </artifactItem>
                                <!-- commons-codec resources (in package) -->
                                <artifactItem>
                                    <groupId>commons-codec</groupId>
                                    <artifactId>commons-codec</artifactId>
                                    <!-- the project version specifies the commons-codec version to use: -->
                                    <version>${project.version}</version>
                                    <classifier>sources</classifier>
                                    <overWrite>true</overWrite>
                                    <includes>org/**/*.txt</includes>
                                    <!-- apply shading: -->
                                    <outputDirectory>${commons-codec-res-folder}/${shading.prefix}</outputDirectory>
                                </artifactItem> -->
                                <!-- commons-codec resources (in META-INF) -->
                                <artifactItem>
                                    <groupId>commons-codec</groupId>
                                    <artifactId>commons-codec</artifactId>
                                    <!-- the project version specifies the commons-codec version to use: -->
                                    <version>${project.version}</version>
                                    <classifier>sources</classifier>
                                    <overWrite>true</overWrite>
                                    <includes>META-INF/*.txt</includes>
                                    <outputDirectory>${commons-codec-res-folder}</outputDirectory>
                                </artifactItem> -->
                            </artifactItems>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <!-- compile commons-codec sources -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin-version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>${jar-plugin-version}</version>
                <executions>
                    <execution>
                        <!-- jar unshaded classes (& resources) -->
                        <id>jar-unshaded</id>
                        <phase>package</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                    <execution>
                        <!-- rejar shaded classes (& resources), with proper manifest partially generated by bundle plugin -->
                        <id>jar-shaded</id>
                        <!-- runs after bundle plugin has done its work to generate bundle manifest -->
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                        <configuration>
                            <archive>
                                <manifestFile>${manifest.path}</manifestFile>
                                <manifestEntries>
                                    <Specification-Title>${project.name}</Specification-Title>
                                    <Specification-Version>${project.version}</Specification-Version>
                                    <Specification-Vendor>${project.organization.name}</Specification-Vendor>
                                    <Implementation-Title>${project.name}</Implementation-Title>
                                    <Implementation-Version>${project.version}</Implementation-Version>
                                    <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
                                    <Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
                                    <Implementation-Build>${implementation.build}</Implementation-Build>
                                    <X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
                                    <X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
                                </manifestEntries>
                            </archive>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <!-- attach sources jar -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>${source-plugin-version}</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                            <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                        </manifest>
                    </archive>
                </configuration>
                <executions>
                    <execution>
                        <!-- jar unshaded sources -->
                        <id>attach-unshaded-sources</id>
                        <!-- <phase>package</phase> (default) -->
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                    <execution>
                        <!-- rejar shaded sources -->
                        <id>attach-shaded-sources</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <!-- apply the shading to main jar and sources jar -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>${shade-plugin-version}</version>
                <executions>
                    <execution>
                        <id>shading-main-jar-and-sources-jar</id>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <!-- (not needed as it is the one and only artifact/dependency)
                            <artifactSet> 
                                <includes>
                                    <include>commons-codec:*</include>
                                </includes>
                            </artifactSet>
                            -->
                            <relocations>
                                <relocation>
                                    <pattern>${commons-codec-package}</pattern>
                                    <shadedPattern>${shaded-commons-codec-package}</shadedPattern>
                                </relocation>
                            </relocations>
                            <createDependencyReducedPom>false</createDependencyReducedPom>
                            <!-- (only needed when dependency reduced pom is generated)
                            <dependencyReducedPomLocation>${project.build.directory}/dependency-reduced-pom.xml</dependencyReducedPomLocation> 
                            <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope> 
                            <promoteTransitiveDependencies>true</promoteTransitiveDependencies>
                            -->
                            <createSourcesJar>true</createSourcesJar>
                            <shadeSourcesContent>true</shadeSourcesContent>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-antrun-plugin</artifactId>
                <version>${antrun-plugin-version}</version>
                <executions>
                    <execution>
                         <!-- unpack shaded classes & sources for manifest generation and re-jarring -->
                        <id>post-shading-tasks</id>
                        <phase>package</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                        <configuration>
                            <target>
                                <!-- Unjar shaded classes for generation of manifest -->
                                <echo>Deleting unshaded classes...</echo>
                                <delete dir="${project.build.directory}/classes"/>
                                <echo>Unjarring shaded main jar...</echo>
                                <unzip src="${project.build.directory}/${project.artifactId}.jar" dest="${project.build.directory}/classes"/>
                                <!-- delete to prevent dual inclusion in new main jar -->
                                <delete dir="${project.build.directory}/classes/META-INF/maven"/>
                                <!-- Unjar shaded sources -->
                                <echo>Deleting unshaded sources...</echo>
                                <delete dir="${commons-codec-src-folder}"/>
                                <echo>Unjarring shaded sources jar...</echo>
                                <unzip src="${project.build.directory}/${project.artifactId}-sources.jar" dest="${commons-codec-src-folder}"/>
                                <!-- delete to prevent dual inclusion in new sources jar -->
                                <delete dir="${commons-codec-src-folder}/META-INF"/>
                            </target>
                        </configuration>
                    </execution>
                    <execution>
                            <id>delete-orginals</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>run</goal>
                            </goals>
                            <configuration>
                                <target>
                                    <echo>Deleting unshaded jar files...</echo>
                                    <delete>
                                        <fileset dir="${project.build.directory}" includes="**/original-*.jar" />
                                    </delete>
                                </target>
                            </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <!-- taken/modified from: http://svn.apache.org/repos/asf/commons/proper/commons-parent/trunk/pom.xml -->
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>${bundle-plugin-version}</version>
                <configuration>
                    <archive>
                        <forced>true</forced>
                    </archive>
                    <excludeDependencies>true</excludeDependencies>
                    <manifestLocation>${project.build.directory}</manifestLocation>
                    <instructions>
                        <!-- stops the "uses" clauses being added to "Export-Package" manifest entry -->
                        <_nouses>true</_nouses>
                        <!-- Stop the JAVA_1_n_HOME variables from being treated as headers by Bnd -->
                        <_removeheaders>JAVA_1_3_HOME,JAVA_1_4_HOME,JAVA_1_5_HOME,JAVA_1_6_HOME,JAVA_1_7_HOME,JAVA_1_8_HOME</_removeheaders>
                        <Bundle-SymbolicName>${commons.osgi.symbolicName}</Bundle-SymbolicName>
                        <Export-Package>${commons.osgi.export}</Export-Package>
                        <Private-Package>${commons.osgi.private}</Private-Package>
                        <Import-Package>${commons.osgi.import}</Import-Package>
                        <DynamicImport-Package>${commons.osgi.dynamicImport}</DynamicImport-Package>
                        <Bundle-DocURL>${project.url}</Bundle-DocURL>
                    </instructions>
                </configuration>
                <executions>
                    <execution>
                        <id>bundle-manifest</id>
                        <!-- runs after the unjarring of the shaded classes -->
                        <phase>integration-test</phase><!--  default is: process-classes -->
                        <goals>
                            <goal>manifest</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

在您想要使用混淆库的(apk/apklib/aar/jar)项目中:
<!-- ... -->
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec-shaded</artifactId>
    <version>1.9</version>
</dependency>
<!-- ... -->

真的是一个非常棒的开箱即用解决方案。以前我一直在手动“着色”,虽然效果还不错,但这个方案更加灵活。对话继续在这里进行:https://github.com/simpligility/android-maven-plugin/issues/487 Google 应该处理这个问题(比如编译器补丁?),或者至少提供像这样的解决方案。 - Atorian

1
这是由于Android捆绑的旧版(1.2)Commons Codec与您的新版发生了命名空间冲突导致的。虽然 shading 是一个不错的解决方法,但我认为从长远来看它并不可持续。这是一个系统性问题,可能会出现在任何打包在Android中的开源库中。我已向Google提交了一个问题。如果您同意,请给它点个“星”,以便引起足够的关注。这是链接 - https://code.google.com/p/android/issues/detail?id=160578

不幸的是,Commons-codec并不是Android中唯一使用的遗留库。还有很多其他的库(XML、JSON、Apache HTTP Client、Javax、bouncycastle等)。在我的情况下,阴影处理是最好的解决方案,因为我已经将软件模块化,以便在我的服务器和Android之间拥有公共代码。我可以在两个平台上运行相同的代码,并具有相同的行为。 - nbe_42

0

你是否已将库添加到项目的构建路径中?完成后,您应该能够从jar文件中调用该方法。

如果在不添加库的情况下构建成功,则可能正在针对较新版本的Android进行构建,但实际上部署到较旧版本。我曾经发现 String.isEmpty() 不在2.1中,但当我编写代码时,它可以构建成功,因为我是针对4.1进行构建的。

您可能需要将jar与项目一起导出(同样,您可以在配置构建路径时执行此操作)。

希望这可以帮助到您。


2
该库已添加到构建路径中。 构建工作正常,但我的应用在运行时崩溃了。我已经尝试使用JB或ICS的SDK,但问题仍然存在。谢谢。 - nbe_42

0
关于nbe_42的Maven Shade插件,将插件块放在pom.xml文件中的位置非常重要,否则Maven会忽略它。
对我有效的方法是将其放在pom.xml中<build> <plugins>块的末尾。
<build>
<plugins>
{all other plugins}
...
{shade plugin}
</plugins>
</build>

最初我把它放在块的开头,但Maven没有运行它。

要创建.jar文件,请从apache.org下载commons-codec-1.8-src.zip源代码.zip归档文件。解压缩它。 pom.xml文件将位于存档的基本目录中。按照上述说明在pom.xml文件中插入nbe_42的插件块,然后运行:

mvn install

这将为您构建、测试、替换并安装插件。

成功输出应该类似于这样:

[INFO] --- maven-shade-plugin:2.2:shade (default) @ commons-codec ---
[INFO] Replacing original artifact with shaded artifact.
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...`

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接