Método no encontrado usando DigestUtils en Android

Estoy tratando de usar la biblioteca DigestUtils en Android 2.3.1 usando JDK 1.6, sin embargo recibo el siguiente error al ejecutar la aplicación:

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

Aquí tienes la stacktrace:

 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) 

La línea de código que causa la excepción es:

String hash = DigestUtils.shaHex("textToHash");

¡He ejecutado el mismo código en una clase de Java fuera de Android y funciona! Por lo tanto, no sé por qué cuando se trabaja con Android no funciona … Puse el libraty dentro de una nueva carpeta libs / en mi aplicación y actualicé el BuildPath para usarlo. Si trato de usar md5 en lugar de sha1, obtengo la misma excepción. ¡Cualquier ayuda sería apreciada! Gracias.

ACTUALIZAR:

Como esta es una pregunta muy activa, he cambiado la respuesta aceptada a favor de @ DA25, ya que su solución es sencilla y el elevado número de votos positivos demuestra que funciona.

Me encontré con el mismo problema al tratar de usar DigestUtils en mi aplicación de Android. Esta fue la mejor respuesta que pude encontrar buscando, pero me resistí a reconstruir el archivo .jar con el espacio de nombres modificado. Después de pasar algún tiempo en este tema, encontré una manera más fácil de resolver el problema en mi caso. La statement del problema para mi código era

 String s = DigestUtils.md5Hex(data); 

Reemplace esta statement con lo siguiente y funcionará:

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

Del mismo modo, para shaHex ejempl, puede cambiarlo a

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

Esto funciona porque aunque Android no tiene encodeHexString (), sí tiene encodeHex (). Espero que esto ayude a otros que se encuentran con el mismo problema.

Como no hay una respuesta clara para la causa raíz de este problema, me gustaría aclarar lo que está sucediendo aquí.

¿Por qué se lanza el NoSuchMethodError en primer lugar?

Según el seguimiento de stack de excepción, la línea que causa el error es 226 en DigestUtils#md5hex método DigestUtils#md5hex . Veamos qué tenemos ahí (supongo que ha usado la versión 1.4, ya que esta es la única versión en la que se Hex#encodeHexString método Hex#encodeHexString en la línea 226):

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

La excepción dice java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString . Vamos a entender por qué.

En primer lugar, Android Framework ya incluye la biblioteca de Commons Codec (excepto la clase DigestUtils ). Sí, no está expuesto como parte del Android SDK y no puede usarlo directamente. Pero aún quieres usarlo. ¿Entonces, que haces? Usted agrega la biblioteca Commons Codec como parte de su aplicación. El comstackdor no se queja, desde su punto de vista todo estaba bien.

Pero, ¿qué sucede en tiempo de ejecución? Sigamos su rastro de stack de excepción:
En primer lugar, llama a DigestUtils#md5Hex desde el método DigestUtils#md5Hex de su actividad. Como escribí anteriormente, el framework no incluye esa clase, por lo que DigestUtils (del Commons Codec versión 1.4) se carga desde tu dex.
A continuación, el método md5hex intenta invocar Hex#encodeHexString método Hex#encodeHexString . Hex clase Hex es parte de la biblioteca de Commons Codec que se incluye en el marco. Lo que sucede es que su versión es 1.3 (versión antigua de julio de 2004). Hex clase Hex existe en el classpath de arranque, lo que significa que el tiempo de ejecución siempre lo favorecerá en lugar de la clase Hex que se incluye dentro de su dex. Puede ver advertencias al respecto en los registros de su aplicación cuando inicie su aplicación (con el tiempo de ejecución 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 

El método Hex # encodeHexString se introdujo en la versión 1.4 de la librería Commons Codec y, por lo tanto, no existe en la clase Hex de framework. El tiempo de ejecución no puede encontrar este método y arroja la excepción NoSuchMethodError .

¿Por qué funciona la solución de la respuesta aceptada?

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

Primero, se DigestUtils#md5 método DigestUtils#md5 . Como ya DigestUtils , la clase DigestUtils que se usará es la que se empaqueta en su DigestUtils . Este método no utiliza ninguna otra clase de Commons Codec , por lo que no hay problema.

A continuación, se Hex#encodeHex . La clase Hex que se utilizará es la del framework (versión 1.3). El método encodeHex (que toma un único parámetro – matriz de bytes) existe en la versión 1.3 de la biblioteca de Commons Codec , y por lo tanto este código funcionará bien.

¿Qué te sugiero?

Mi solución sugerida es cambiar el nombre del espacio de nombres / paquete de las clases. Al hacerlo, especifico explícitamente qué código se va a ejecutar y evito el comportamiento extraño que puede ocurrir debido a problemas de control de versiones.

Puedes hacerlo manualmente (como escribió Caumons en su respuesta), o automáticamente con la herramienta jarjar .

Vea este resumen y consejos para usar jarjar en mi blogpost .

Finalmente recibo la respuesta y funciona bien. Como se describe en No hay tal error de método en el códec Apache para otro tipo de encriptación (Base64) intenté reproducir el mismo problema y recibo exactamente el mismo error. Así que estaba en el caso de la pregunta adjunta. Como dicen, parece ser una colisión de nombre interno con el nombre del paquete org.apache.commons.codec y según lo indicado por @Don lo cambié a com.apache.commons.codec y funcionó bien! ¿Cómo lo hice?

Descargué el código fuente y cambié los 3 directorios org a com . También reemplacé todas las apariciones del nombre del paquete en los archivos donde aparecen y también cambié las referencias en los documentos a com/apache/commons/codec/ . (No intente rehacerlos manualmente o pasará el día del agujero). Luego compilé la biblioteca y generé el contenedor con Ant, que llamé commons-codec-1.6-android.jar . Puse el flask en la carpeta libs/ de mi aplicación de Android y lo agregué al buildpath. Además, adjunté las fonts como la carpeta que contiene todos los archivos. ¡Así que ahora tengo la biblioteca lista para usar con Android!

Espero que ayude a alguien más!

Gracias @ DA25

Esto está funcionando bien para mí

Tengo dependencia de agregar

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

ref: http://mvnrepository.com/artifact/commons-codec/commons-codec/1.9

mi función

 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; } 

Para mí, proguard eliminó la clase durante la ofuscación. Añada esto a sus reglas de Proguard.

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

Este es el método que estaba usando del paquete apache.

 Hex.encodeHex(digest) 

Agregar método

 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); }