android中使用codec总是报NoSuchMethodError的问题根源

本文详细解析了在Android应用中使用外部库时因版本冲突引发NoSuchMethodError的原因,并提供了解决方案。通过分析版本管理与类加载机制,解释了为何在调用特定方法时出现异常,以及如何避免此类问题发生。

该问题在StackOverflow上有人给出了解决方案,但是更重要的是,下面有一个哥们给出了造成问题的原因分析!

原问题的地址是:http://stackoverflow.com/questions/9126567/method-not-found-using-digestutils-in-android

给出的问题解决方法是:

I ran into the same issue trying to use DigestUtils in my Android app. This was the best answer I could find by searching, but I was reluctant to rebuild the .jar file with the namespace changed. After spending some time on this issue, I found an easier way to solve the problem for my case. The problem statement for my code was

String s = DigestUtils.md5Hex(data);

Replace this statement with the following and it will work:

String s = new String(Hex.encodeHex(DigestUtils.md5(data)));

Similarly, for shaHex exampl, you can change it to

String hash = new String(Hex.encodeHex(DigestUtils.sha("textToHash");

This works because even though Android does not have encodeHexString(), it does have encodeHex(). Hope this would help others who run into the same issue.


然后底下纷纷有人留言疑惑虽然解决了问题,但是原因是啥?终于大神出现了,给出了他的回答:

Since there's no clear answer for the root cause of this problem, I'd like to clarify what's happening here.

Why the NoSuchMethodError is thrown in the first place?

According to exception stack trace, the line that causes the fault is 226 in DigestUtils#md5hex method. Let's see what we have there (I'm assuming you have used version 1.4, since this is the only release where Hex#encodeHexString method is being invoked in line 226):

public static String md5Hex(String data) {
    return Hex.encodeHexString(md5(data));
}

The exception says java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString.Let's understand why.

First of all, Android framework already includes the Commons Codec library (except the DigestUtils class). Yes, it is not exposed as part of the Android SDK and you cannot use it directly. But you still want to use it. So what you do? You add Commons Codec library as part of your application. The compiler doesn't complain - from his point of view everything was fine.

But what happens at runtime? Let's follow your exception stack trace:
First, you're calling DigestUtils#md5Hex from your Activity's onCreate method. As I wrote above, the framework doesn't include that class, so DigestUtils (from Commons Codec version 1.4) is loaded from your dex.
Next, md5hex method tries to invoke Hex#encodeHexString method. Hex class is part of the Commons Codec library that included in framework. The thing is that its version is 1.3 (ancient release from July 2004). Hex class exists in boot classpath, which means that the runtime will always favor it instead of the Hex class that packaged inside your dex. You can see warnings about it in your application logs when you start your app (with Dalvik runtime):

D/dalvikvm? DexOpt: 'Lorg/apache/commons/codec/binary/Hex;' has an earlier definition; blocking out
I/dalvikvm? DexOpt: not resolving ambiguous class 'Lorg/apache/commons/codec/binary/Hex;'
D/dalvikvm? DexOpt: not verifying/optimizing 'Lorg/apache/commons/codec/binary/Hex;': multiple definitions
I/dalvikvm? Could not find method org.apache.commons.codec.binary.Hex.encodeHexString, referenced from method org.apache.commons.codec.digest.DigestUtils.md5Hex

Hex#encodeHexString method was introduced in version 1.4 of Commons Codec library and therefore it doesn't exist in framework's Hex class. The runtime can't find this method and thus throws NoSuchMethodError exception.

Why the accepted answer's solution works?

String s = new String(Hex.encodeHex(DigestUtils.md5(data)));

First, DigestUtils#md5 method is called. As I already stated, DigestUtils class that will be used is the one that packaged in your dex. This method doesn't use any other Commons Codec classes, so no problem with it.

Next, Hex#encodeHex will be called. The Hex class that will be used is the framework's one (version 1.3). The encodeHex method (that takes a single parameter - byte array) exists in version 1.3 of Commons Codec library, and therefore this code will work fine.

What would I suggest?

My suggested solution is to rename the classes namespace/package. By doing so I'm explicitly specifying which code is going to execute, and prevent bizarre behavior that may occur because of versioning issues.

You can do it manually (as Caumons wrote in his answer), or automatically with jarjar tool.

See this issue summary and tips for using jarjar in my blogpost.

其根本原因在于android内置了一个Codec库,但是版本过老(1.03),如果使用了外部引入的新版本的codec.jar中的DigestUtils执行方法的时候,DigestUtils优先加载的是系统自带的老版本的codec库中相应的方法,当老版本的安卓自带codec.jar中不包含新版本DigestUtil需要的方法时,就产生了异常!

### 问题分析 当 Spring Boot 项目被打成 WAR 包并部署到外部 Tomcat 容器后,调用某些接口时出现 `java.lang.NoSuchMethodError` 错误,这通常是由类路径冲突或版本不一致导致的。具体而言,应用中依赖的某些库(如 Tomcat 相关的类)可能与外部 Tomcat 提供的类存在版本差异,导致运行时方法签名不匹配,从而抛出 `NoSuchMethodError` 异常。 Spring Boot 默认使用内嵌的 Tomcat 容器,其依赖管理机制会自动引入兼容的 Tomcat 版本。然而,当项目以 WAR 包形式部署到外部 Tomcat 时,如果未正确配置依赖作用域,可能会导致 Spring Boot 项目中自带的 Tomcat 库与外部容器提供的库发生冲突。例如,`StringManager.getManager(Class)` 方法在不同版本的 Tomcat 中可能存在差异,若应用中存在多个版本的 Tomcat 依赖,就会导致方法找不到的问题[^4]。 ### 解决方案 #### 1. 排除内嵌 Tomcat 依赖 在 `pom.xml` 中,应将内嵌的 Tomcat 依赖设置为 `provided` 作用域,以避免与外部 Tomcat 冲突。具体配置如下: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> ``` 这样可以确保项目在打包时不会包含内嵌的 Tomcat 库,从而避免类路径冲突。 #### 2. 确保 WAR 包正确打包 使用 Maven 打包时,需确保 `pom.xml` 中设置了正确的打包方式为 `war`,并配置了 Spring Boot 的 WAR 插件: ```xml <packaging>war</packaging> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ``` 执行以下命令进行打包: ```bash mvn clean package ``` 打包完成后,将生成的 `.war` 文件部署到 Tomcat 的 `webapps` 目录下,启动 Tomcat 即可完成部署[^3]。 #### 3. 检查依赖版本一致性 确保项目中所有涉及 Tomcat 的依赖版本与外部 Tomcat 容器版本一致。例如,若部署到 Tomcat 9,则所有 Tomcat 相关的依赖也应为 9.x 版本。可通过 `mvn dependency:tree` 命令查看依赖树,排查是否有旧版本的 Tomcat 类库被引入。 #### 4. 避免重复依赖 某些第三方库可能间接引入了 Tomcat 或其他 Servlet API 的依赖,这可能导致运行时冲突。可在 `pom.xml` 中通过 `<exclusion>` 标签排除这些依赖,确保只使用外部 Tomcat 提供的类库。 ### 示例配置 以下是一个完整的 `pom.xml` 配置片段,用于构建可部署到外部 Tomcat 的 WAR 包: ```xml <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>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project> ``` ###
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值