在Android中使用DigestUtils时找不到方法

87

我正试图在Android 2.3.1中使用JDK 1.6版本的库DigestUtils,但是当我执行应用程序时出现以下错误:

Could not find method org.apache.commons.codec.binary.Hex.encodeHexString, referenced from method org.apache.commons.codec.digest.DigestUtils.shaHex

下面是错误的堆栈跟踪信息:

02-03 10:25:45.153: I/dalvikvm(1230): Could not find method org.apache.commons.codec.binary.Hex.encodeHexString, referenced from method org.apache.commons.codec.digest.DigestUtils.shaHex
02-03 10:25:45.153: W/dalvikvm(1230): VFY: unable to resolve static method 329: Lorg/apache/commons/codec/binary/Hex;.encodeHexString ([B)Ljava/lang/String;
02-03 10:25:45.153: D/dalvikvm(1230): VFY: replacing opcode 0x71 at 0x0004
02-03 10:25:45.153: D/dalvikvm(1230): VFY: dead code 0x0007-0008 in Lorg/apache/commons/codec/digest/DigestUtils;.shaHex ([B)Ljava/lang/String;
02-03 10:25:45.163: D/AndroidRuntime(1230): Shutting down VM
02-03 10:25:45.163: W/dalvikvm(1230): threadid=1: thread exiting with uncaught exception (group=0x40015560)
02-03 10:25:45.173: E/AndroidRuntime(1230): FATAL EXCEPTION: main
02-03 10:25:45.173: E/AndroidRuntime(1230): java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString
02-03 10:25:45.173: E/AndroidRuntime(1230):     at org.apache.commons.codec.digest.DigestUtils.md5Hex(DigestUtils.java:226)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at com.caumons.trainingdininghall.ConnectionProfileActivity.onCreate(ConnectionProfileActivity.java:20)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1586)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1638)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.access$1500(ActivityThread.java:117)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:928)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.os.Handler.dispatchMessage(Handler.java:99)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.os.Looper.loop(Looper.java:123)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at android.app.ActivityThread.main(ActivityThread.java:3647)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at java.lang.reflect.Method.invokeNative(Native Method)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at java.lang.reflect.Method.invoke(Method.java:507)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
02-03 10:25:45.173: E/AndroidRuntime(1230):     at dalvik.system.NativeStart.main(Native Method)
引发异常的代码行是:
String hash = DigestUtils.shaHex("textToHash");
我在 Android 之外的 Java 类中执行了相同的代码,它可以正常工作!因此,我不知道为什么在 Android 上它不能工作...... 我将库放到了应用程序的新 libs/ 文件夹中,并更新了 BuildPath 以使用它。如果我尝试使用 md5 而不是 sha1,则会出现相同的异常。任何帮助将不胜感激!谢谢。
更新:
由于这是一个非常活跃的问题,我已经更改了接受的答案,支持 @DA25 的解决方案,因为他的解决方案直截了当,并且高投票数证明它有效。

你编辑了所有的源文件吗?对我来说,最好的方法是如何编辑所有的源文件?如果可能的话,你能分享一下你创建的新的jar文件吗? - Hemant
用Eclipse打开源代码,将包名更改为所需的名称。然后,使用一些命令替换引用原始包名的旧字符串。我可以在哪里分享生成的JAR文件? - Caumons
8个回答

156

我在尝试在我的Android应用中使用DigestUtils时遇到了同样的问题。这是我通过搜索找到的最好的答案,但我不愿意重新构建带有命名空间更改的.jar文件。在花费了一些时间解决这个问题后,我找到了一个更简单的方法来解决我的情况下的问题。我的代码的问题陈述为

String s = DigestUtils.md5Hex(data);

将这个语句替换为以下内容,它就可以工作:

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

同样地,对于shaHex,你可以将其更改为

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

这能够起作用的原因是尽管Android没有encodeHexString()函数,但它确实有encodeHex()函数。希望这能够帮助遇到同样问题的其他人。


7
我不明白为什么只是调用Hex.encodeHex()内有问题的方法,并将结果设置为字符串构造函数就能解决问题。你能详细说明一下吗?encodeHex()方法是来自DigestUtils库吗?你尝试过直接使用new String(DigestUtils.md5(data));吗? - Caumons
5
我知道这是一个旧问题,但对于任何感兴趣的人:这是由于Android捆绑了其自己的commons-codec 1.2版本所致。任何比这个版本更新的版本都无法在设备上使用。 - KennethJ
这个不起作用。我在Android API 22级上进行了检查。 - lovesh
1
@lovesh,你具体遇到了什么问题?我刚刚检查了一下——API 22 没有任何变化,和以前一样可以正常工作。 - Alex Lipov
@Caumons,有问题的方法是DigestUtils.md5Hex,但他没有调用那个方法,而是调用了DigestUtils.md5,并使用不同的方法encodeHex将其转换为十六进制。 - Fran Marzoa
请注意,DigestUtils.sha()已被弃用。在我的情况下,我只是使用了以下代码:String hash = String(Hex.encodeHex(DigestUtils.sha1(data))); - K. Stopa

43

由于这个问题的根本原因没有明确的答案,我想澄清一下这里发生了什么。

为什么首先会抛出NoSuchMethodError错误?

根据异常堆栈跟踪,在DigestUtils#md5hex方法中,导致故障的行是226。让我们看看这里的代码(我假设您使用的是版本1.4,因为这是唯一一个在第226行调用Hex#encodeHexString方法的版本):

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

异常信息显示:java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString

首先,Android框架已经包含了Commons Codec库(除了DigestUtils类)。是的,它没有作为Android SDK的一部分被公开,也无法直接使用。但你仍然想要使用它。那你该怎么做呢?你需要将Commons Codec库作为应用程序的一部分添加进去。编译器不会出错-从他的角度来看一切都很好。

但在运行时会发生什么呢?让我们跟随异常堆栈追踪:
首先,在你的Activity的onCreate方法中调用了DigestUtils#md5Hex。如我上面所写,框架不包括该类,因此DigestUtils(来自Commons Codec版本1.4)会从你的dex文件中加载。
接下来,md5hex方法尝试调用Hex#encodeHexString方法。Hex类是包含在框架中的Commons Codec库的一部分。问题在于它的版本是1.3(2004年7月发布的版本)。Hex类存在于引导类路径中,这意味着运行时始终会优先选择它,而不是打包在你的dex文件中的Hex类。当你启动应用程序(使用Dalvik运行时)时,你可以在应用程序日志中看到有关此问题的警告信息:

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方法是在版本1.4的Commons Codec库中引入的,因此它不存在于框架的Hex类中。运行时找不到此方法,因此会抛出NoSuchMethodError异常。

为什么被接受的答案的解决方案有效?

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

首先,调用了DigestUtils#md5方法。如我所述,所使用的DigestUtils类是打包在您的dex中的那个。此方法不使用任何其他Commons Codec类,因此没有问题。

接下来,将调用Hex#encodeHex方法。所使用的Hex类是框架自带的(版本1.3)。encodeHex方法(带有单个参数 - 字节数组)存在于版本1.3的Commons Codec库中,因此这段代码可以正常工作。

我的建议是什么?

我建议重命名类的命名空间/包。通过这样做,我明确指定了要执行的代码,并防止由于版本问题而导致的奇怪行为。

您可以手动完成此操作(如Caumons在他的答案中所写),也可以使用jarjar工具自动完成。

请参见我的博客文章中关于此问题的总结和使用jarjar的提示。


3
非常感谢你!我也遇到了同样的问题,使用Apache Commons Lang jar(版本为3.2及以上)时,在所有来自小米厂商的手机上都会出现问题。显然,这些手机的系统运行时包含旧版本的Apache Commons Lang。因此,我以前会遇到诸如以下异常:STACK_TRACE=java.lang.NoSuchMethodError: org.apache.commons.lang3.mutable.MutableBoolean.setTrueSTACK_TRACE=java.lang.NoSuchMethodError: org.apache.commons.lang3.StringEscapeUtils.escapeXml10根据你的建议,我使用了jarjar工具将包命名空间重命名为对我的应用程序独有的名称。 - La Machine
2
谢谢,你激发了我写这个简单的展示项目 https://github.com/allpaykz/digest-utils - c0rp

20

最终我得到了答案,而且它已经很好地运行了。如No such method error in Apache codec所述,针对另一种类型的加密(Base64),我尝试重现相同的问题,并且确实遇到了同样的错误。所以我就处于这个问题的情况下。正如他们所说,这似乎是与包名org.apache.commons.codec存在内部名称冲突的问题,正如@Don所述,我将其更改为com.apache.commons.codec,然后就可以正常工作了!我是怎么做到的呢?

我下载了源代码并将3个目录中的org更改为com。我还替换了出现该包名的所有文件中的所有出现次数,并修改了文档中的引用为com/apache/commons/codec/。(不要尝试手动重命名它们,否则你将花费整整一天的时间)。然后,我使用Ant编译库并生成了一个名为commons-codec-1.6-android.jar的jar文件。我将该jar文件放入我的Android应用程序的libs/文件夹中,并将其添加到buildpath中。此外,我还将包含所有文件的文件夹作为源附加。因此,现在我有了一份可供Android使用的库!

希望它能对其他人有所帮助!


2
这是正确的答案,但似乎开发人员不太可能更改摘要源。应该向Apache报告此问题。在我的开发中,我选择了@DA25给出的另一个选项。 - Snicolas
1
对于使用Maven的人来说,无需手动操作。有一个插件可以完成这个任务,请参见此处的说明:https://dev59.com/6WjWa4cB1Zd3GeqPnRly#16916552 - Risadinha
@Caumons,你能分享一下这个库吗?我也遇到了同样的问题。 - Nitin Misra
@NitinMisra,你看到Risadinha上面的更新或评论了吗? - Caumons
@Caumons,我按照你之前的建议使用了new String(DigestUtils.md5(data));。这样安全吗? - Nitin Misra
@NitinMisra,高数量的赞同似乎证明了这一点 ;) - Caumons

3

感谢@DA25

这对我来说很好用

我已经添加了依赖项

compile 'commons-codec:commons-codec:1.9'

参考: http://mvnrepository.com/artifact/commons-codec/commons-codec/1.9

我的函数

public String encode(String key, String data) {
    try {

        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);

        return new String(Hex.encodeHex(sha256_HMAC.doFinal(data.getBytes("UTF-8"))));

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    return null;
}

1

对于我来说,Proguard在混淆期间删除了该类。请将以下内容添加到您的Proguard规则中。

-keep class org.apache.commons.** { *; }

这是我使用Apache软件包的方法。
Hex.encodeHex(digest)

0

添加方法

public static String byteArrayToHexString(byte[] bytes) {
    final char[] toDigits = "0123456789abcdef".toCharArray();
    int l = bytes.length;
    char[] out = new char[l << 1];

    int i = 0; for (int j = 0; i < l; ++i) {
        out[(j++)] = toDigits[((0xF0 & bytes[i]) >>> 4)];
        out[(j++)] = toDigits[(0xF & bytes[i])];
    }
    return new String(out);
}

0
我们使用了以下代码,它起作用了:
  HmacUtils hmacUtils = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, keyString);
  String digest = new String( Hex.encodeHex(hmacUtils.hmac(msg)));

0

重命名 DigestUtils 类的另一种方法是使用 Proguard。 如果您没有使用 Proguard,您可以启用它并添加这一行代码,它将只混淆 DigestUtils 类,并保留其他所有内容。

-keep class !org.apache.commons.codec.digest.DigestUtils,com.** { *; }

并将此添加到您的应用程序的build.gradle

buildTypes {
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

或者选项2 在您的代码中使用旧版本的库:

implementation("commons-codec:commons-codec:1.3"){
        force = true
    }

如果common-codec依赖来自第三方库,需要使用force=true,否则Gradle将默认解析为更高版本。


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