NoSuchMethodError: org.apache.co
一、问题描述
在Android项目中使用到了org.apache.commons.codec.jar包下的DigestUtils.md5Hex()获取文件的md5值,结果编译通过,运行时总是报java.lang.NoSuchMethodError:org.apache.commons.codec.binary.Hex.encodeHexString([B)Ljava/lang/String;错误
我在APP中使用的是common-codec-1.9.jar
调用代码如下:
//直接传入文件输入流就可以得到文件的标准md5值
String cacheDirMd5Hex = DigestUtils.md5Hex(new FileInputStream(file));
String assetMd5Hex = DigestUtils.md5Hex(FileUtils.getBytes(context.getAssets().open(SkinConfig.SKIN_DIR_NAME + File.separator + fileName)));
DDLog.i(getClass(), "assetMd5Hex=" + assetMd5Hex + ",cacheDirMd5Hex=" +cacheDirMd5Hex);
if (!TextUtils.equals(assetMd5Hex, cacheDirMd5Hex)){ //如果缓存路径中与assets中的文件不相同
DDLog.i(getClass(), "skin资源文件校验错误");
file.delete();
SkinFileUtils.copySkinAssetsToDir(context, fileName, SkinFileUtils.getSkinDir(context));
}
栈信息如下 :
System.err: java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Hex.encodeHexString
System.err: at org.apache.commons.codec.digest.DigestUtils.md5Hex(DigestUtils.java:310)
System.err: at com.skin.loader.SkinManager.setUpSkinFile(SkinManager.java:106)
System.err: at com.skin.loader.SkinManager.access$100(SkinManager.java:38)
System.err: at com.skin.loader.SkinManager$1.run(SkinManager.java:73)
二、先上解决办法
既然找不到该方法,那么让它找到这个方法或者使用其它方法就可以解决此问题。
思路一是:保证AndroidStudio中调用的API与系统中的API版本一致,这样就不会导致方法找不到的问题。
思路二是:去掉系统中的相关API,这样就应用就会只调用我们自己APP中的API,但是这样未免代价太大,而且系统中其它地方可能对这些API有依赖,不推荐。
方法一、依据思路一:在AndroidStudio中使用1.3或者更低版本的API
另寻途径,改用其它方法。高版本的API使用起来是更方便快捷的,更换到低版本后就需要自己手动做一些工作了
1.3版本的DigestUtils.md5Hex(byte[] data)
只能传入字节数组,所以需要自己把文件读出转换成字节数组才行
String cacheDirMd5Hex = DigestUtils.md5Hex(FileUtils.getBytes(file));
String assetMd5Hex = DigestUtils.md5Hex(FileUtils.getBytes(context.getAssets().open(SkinConfig.SKIN_DIR_NAME + File.separator + fileName)));
DDLog.i(getClass(), "assetMd5Hex=" + assetMd5Hex + ",cacheDirMd5Hex=" +cacheDirMd5Hex);
if (!TextUtils.equals(assetMd5Hex, cacheDirMd5Hex)){ //如果缓存路径中与assets中的文件不相同
DDLog.i(getClass(), "skin资源文件校验错误");
file.delete();
SkinFileUtils.copySkinAssetsToDir(context, fileName, SkinFileUtils.getSkinDir(context));
}
//没办法,自己写个工具类吧
public class FileUtils {
/**
* 将文件转换成字节数组
*
* @param filePath
* @return
*/
public static byte[] getBytes(String filePath) {
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
fis = new FileInputStream(filePath);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read = 0;
while ((read = fis.read(buffer)) != -1) {
baos.write(buffer, 0, read);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new byte[0];
}
/**
* 将文件转换成字节数组
*
* @param file
* @return
*/
public static byte[] getBytes(File file) {
FileInputStream fis = null;
ByteArrayOutputStream baos = null;
try {
fis = new FileInputStream(file);
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read = 0;
while ((read = fis.read(buffer)) != -1) {
baos.write(buffer, 0, read);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new byte[0];
}
/**
* 将文件转换成字节数组
*
* @param is
* @return
*/
public static byte[] getBytes(InputStream is) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int read = 0;
while ((read = is.read(buffer)) != -1) {
baos.write(buffer, 0, read);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return new byte[0];
}
}
方法二、依据思路一:在更新系统中的common-codec库版本为1.4以上
因为common库会兼容低版本,所以不会引入更多问题,而且是一种更值得做的方法,开历史倒车总是不好的。当然该方法仅限于你是做系统开发的,可以修改系统源码,否则只有依据方法一。
需要懂一些Android Build相关知识。
//TODO 有空再更
三、问题原因
通过网络查找相关文章,发现根本原因如下:
其根本原因在于android内置了一个Codec库,但是版本过老,如果使用了外部引入的新版本的codec.jar中的DigestUtils执行方法的时候,DigestUtils优先调用的是系统自带的老版本的codec库中相应的方法,当老版本的安卓自带codec.jar中不包含新版本DigestUtil需要的方法时,就产生了异常!
由于我使用的是Android4.4版本,于是在代码中搜索了一番,发现在源码编译后的out目录下:
out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes/org/apache/commons/codec/
编译了系统使用的common-codec库,其对应的源码中的jar包为common.jar,于是我打开了common-codec-1.9.jar,common-codec-1.4.jar,common-codec-1.3.jar和common.jar,对比发现在common.jar和common-codec-1.3.jar中的Hex类中没有对应的方法Hex.encodeHexString,如下图:
1.4版本:
image.png
1.9版本:
image.png
1.3版本:
image.png
Android4.4系统中使用的版本(我的测试机)
image.png
通过对比得出结论:common-codec.jar在1.4及之后的版本中才有Hex.encodeHexString方法,在AndroidStudio中编译的时候使用的是我引入的1.9版本的jar包,不会出现编译错误,但是一旦安装到Android4.4系统中,会首先寻找系统中是否有相同的API,结果找到了系统中低版本(1.3或者更低)的Hex类,然后就找不到encodeHexString方法了。