SickWorm的博客

Android工程使用org.apache.commons.codec(commons-codec)库,运行时提示Base64.encodeBase64URLSafeString“java.lang.NoSuchMethodError”解决方法

Android  ·  

最近工作是开发FIDO UAF项目。FIDO UAF是一个旨在提供身份验证通用方案,以代替繁杂密码记忆的一个方案,Google,阿里这些大头都是核心成员,感觉是用来未来代替密码的1号方案。可能是定义的太通用了,UAF目前应用面还不广。

UAF还有个兄弟叫U2F,是个更精简的方案。U2F是一个用硬件外设(token)代替密码的方案,有点像u盾。外国已经有相关设备,Google登录已经可以通过U2F设备进行验证。使用很方便:你在电脑插入一个U2F设备(初次需要绑定账户),再打开网页,Google就自动为你登录。腾讯也有做一个叫QKey的,很快就会面市了。U2F实用性比较强。

回到正题,我在开发过程中,同事找到了eBay的UAF实现开源代码。我稍微看了一下,看到他们的Base64编码是使用了Base64.encodeBase64URLSafeString这个方法。我试了一下,和普通的Base64编码不同的是把编码末尾的”=“号去掉了。这样的话Http的Get参数就不会出现问题。虽然UAF要求使用POST发送请求,但我看到大佬都这么做,我就跟着用吧。

这个方法在org.apache.commons.codec(commons-codec)中定义。我也下了这个库,放在我自己的项目中,并替换了原来的Base64实现。而稍后在我进行单元测试的时候出问题了。Android单元测试提示我找不到“Base64.encodeBase64URLSafeString”这个方法。

这就奇怪了,编译期没有报错,运行时报错?我一下子没想明白。我反编译commons-codec库,里面是有这个方法的。我在运行时用反射打印出来,没有这个方法。然后我用Java单元测试试了一下,Java单元测试通过。

那看来是Android运行环境的问题了。我上网找了一下,居然有和我一样问题的人:
java.lang.NoSuchMethodError: org.apache.commons.codec.binary.Base64.encodeBase64

里面写的很清楚,Android的framework引用了一个旧版的commons-codec(而这个旧版本没有这个方法)。这会导致后加载的同一名称的包无效(无法加载并覆盖)。所以我在运行时就找不到这个方法了。

解决方法

没办法。Java的类加载安全机制决定了我们没办法使用后加载的类。我想起在“码农翻身”公众号里有看过相关的文章。我找了一下,节选如下:

我感到前途未卜, 但也不能坐以待毙, 一定得多了解信息。
"大哥, 你叫什么名字" , 我看小个子还算和气。
"我就是大名鼎鼎的文件验证器了, 能管很多事"
"那刚才他为啥还得请示上级呢" , 我用眼神指了一下开车的ClassLoader
文件验证器的声音一下子就压低了:
"你不知道,说来话长, 我们之前出现过事故,有个黑客写了个类java.lang.String, 和我们老板手下有一个干活最卖力的员工名字一模一样,只是这个黑客类里边竟然有格式化硬盘的代码,我们的小兵Classloader 不明就里,就把 这个黑客类给先装载了,也执行了, 最后的结果,唉,很惨的... "

"那后来怎么办?"
"后来我们老板就定下了规矩:他的骨干员工像String, ArrayList等只能由他自己的心腹去装载, 我听说老板的心腹都是分层级的,像传销一样, 每个都有上线, 最顶层的叫Bootstrap Classloader , 下一次级叫Extension Classloader, 现在开车的这位其实叫App Classloader,位于最底层, 咱这位Classloader 在装载一个类之前,一定要问一问这几位权利极高的大爷,请他们先装载,这几位爷装载不了,才由我们这些小兵来出马。“

其实只要先加载了就没办法再重新加载了。我们普通app环境做不到上述黑客的环境,上面的文章只是给大家加深记忆。

不过如果你的类是你自己用Classloader 加载的话,还是可以重新加载的,相关文章:
JAVA动态重新加载Class机制

有办法。其实你把里面的实现拷出来,换个包名也可以。但是类之间的互相依赖可能会有点多。

但我突然发现,

为什么eBay的代码却用了这个库呢????他们是不是没有实际跑过呢???【黑人问号】

总结

Android工程使用org.apache.commons.codec(commons-codec)库,运行时提示“java.lang.NoSuchMethodError”的原因是:Android内部已经加载过同名的旧类库,导致项目中引用的库无法加载,而旧库的Base64类又没有这个方法。

解决方法是:
1. 换一个库,使用Android自带的Base64方法,并加上Base64.URL_SAFE标记位。(最好是Base64.NO_WARP | Base64.NO_PADDING | Base64.URL_SAFE)
2. 把实现拷出来(依赖过多,不现实)

版权所有,转载请注明出处:
https://sickworm.com/?p=175

# # # # #