在上个月编写一个测试代码的时候,遇到一个奇怪的问题。
在XML布局中指定一个View的 android:onClick=”test” 事件函数。在4.0中运行程序没问题,而在低版本(低于2.3)手机中运行会出错,错误内容为 NoSuchMethodException(找不到这个test函数)。 当时也没详细研究具体原因是啥,只是在Java代码中通过View的setOnClickListener函数来设置点击事件接口在低版本运行就没问题了。
今天看到Square 的一篇博文才明白为啥会出现这个诡异的Bug。
下面来详细介绍下这个情况。
如果您在Activity中覆写了高版本的API,比如 在 API 16(Jelly Bean)中引入的API
Java
@Override
public void onPrepareNavigateUpTaskStack(TaskStackBuilder builder) {
super.onPrepareNavigateUpTaskStack(builder);
}
1
2
3
4
@Override
publicvoidonPrepareNavigateUpTaskStack(TaskStackBuilderbuilder){
super.onPrepareNavigateUpTaskStack(builder);
}
如果程序运行在低版本的系统中,由于不会调用该函数, 所以应该是没有问题的。
如果您在该Activity的Layout xml文件中,设置了View 的 android:onClick=”test” 属性,则这个时候运行在低于2.3版本的手机上就会异常 — 就是上面提到的,该点击事件函数找不到。
导致该异常的原因
由于系统处理XML 中 onClick 事件函数的方式为反射,就如同您通过代码
TestActivity.class.getMethod(“test”, View.class) 来查找这个点击事件处理函数,在不同版本的Dalvik虚拟机中对反射的处理方式是不一样的:
在ICS中,Dalvik虚拟机调用一些C代码来查询.dex文件,看看有没有 test 函数,如果有的话就返回一个 Method对象。
而在 Froyo(2.2)中,Dalvik虚拟机调用一些C代码来查询.dex文件,然后查找TestActivity中的所有函数,把该函数集合返回到Java代码中。然后Java代码在这个集合中搜索test函数。
查询一个函数并不代表要执行该函数,类初始化比较耗时应该在使用的时候才去初始化。 而在 Froyo中,Dalvik虚拟机提前初始化 每个函数的返回值和参数。如果这些返回值和参数对象任何一个初始化失败了,就出现问题了。
前面我们在处理点击事件的时候,需要通过反射查询这个test函数,而Dalvik加载了TestActivity的所有函数。当然还包含onPrepareNavigateUpTaskStack这个4.1系统中引入的函数,而该函数的参数TaskStackBuilder 在2.2版本中并不存在。当Dalvik虚拟机初始化该函数的时候遇到了问题,从而导致查找处理点击事件的test函数失败。
这种问题导致开发者很难知道为何会出现这个 NoSuchMethodException, 明明在TestActivity中声明了这个参数为View的test函数。
所以在兼容低版本(小于2.3)设备的时候,开发者一定要注意对反射的使用。可以通过Lint来检测是否在代码中使用了低于android:minSdkVersion所指定的版本中的API。