Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android系统版本兼容ReflectiveOperationException问题 #5

Open
zengjingfang opened this issue Jan 9, 2018 · 11 comments
Open

Android系统版本兼容ReflectiveOperationException问题 #5

zengjingfang opened this issue Jan 9, 2018 · 11 comments

Comments

@zengjingfang
Copy link
Owner

zengjingfang commented Jan 9, 2018

反射获取getTag接口报ReflectiveOperationException

private static String getWakeLockTag(PowerManager.WakeLock wakeLock) {
        String tag = "Default-WakeLock-Tag";
        if (wakeLock == null) {
            return tag;
        }
        try {
            Method getTagMethod = wakeLock.getClass().getDeclaredMethod("getTag");
            getTagMethod.setAccessible(true);
            tag = (String) getTagMethod.invoke(wakeLock, (Object[]) null);
        } catch (ClassNotFoundExceptione) {
            LogUtil.w(TAG,e);
        } 
        return tag;
    }
@zengjingfang
Copy link
Owner Author

zengjingfang commented Jan 9, 2018

看代码发现 ClassNotFoundExceptione 是 ReflectiveOperationException 的子类,于是修改如下

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private static String getWakeLockTag(PowerManager.WakeLock wakeLock) {
        String tag = "Default-WakeLock-Tag";
        if (wakeLock == null) {
            return tag;
        }
        try {
            Method getTagMethod = wakeLock.getClass().getDeclaredMethod("getTag");
            getTagMethod.setAccessible(true);
            tag = (String) getTagMethod.invoke(wakeLock, (Object[]) null);
        } catch (ReflectiveOperationException e) {
            LogUtil.w(TAG,e);
        } catch (Exception e) {
            LogUtil.w(TAG,e);
        }
        return tag;
    }

查看手机系统为Android4.2.2.r1源码:

4.2.2_r1 PowerManager

有wakeLock 这个内部类,但是没有getTag这个方法。

且来看看高版本的方法:

   /** @hide */
   public void setTag(String tag) {
         mTag = tag;
   }
   
   /** @hide */
   public String getTag() {
        return mTag;
   }

可以看出加了 @hide 对外不公开,但是系统内部可见的,所以这里要用反射。

@zengjingfang
Copy link
Owner Author

zengjingfang commented Jan 9, 2018

以为万事大吉,谁知道报了

java.lang.VerifyError: com/xxx/xxx/xxx/xxx/manager/WakeLockManager

getWakeLockTag 是上述类WakeLockManager中的一个方法

查资料说是JVM加载类的时候,“校验器”检查文件格式虽然正确,但是内部的存在不一致性和安全性的问题,所以抛出了该错误。

不同的虚拟编译器不一样,所以抛出的错误信息叶铿不一样。

异常名称 异常栈中的段落信息 可能原因

  • java.lang.VerifyError Call to wrong initialization method 可能是在调用构造函数即的时候传进了错误的owner

  • java.lang.VerifyError Incompatible object argument for function call 同样是方法调用的时候出现的错误。看时候有参数设置错误了

  • java.lang.VerifyError Stack size too large 设置的最大栈空间大小不够

  • java.lang.VerifyError Illegal local variable number 可能是设置的最大局部变量大小不够,也可能是访问的局部变量的index不对

  • java.lang.VerifyError Must call initializers using invokespecial 在你调用 方法的时候使用了非INVOKESPECIAL的其他操纵码了。

  • java.lang.VerifyError Expecting to find integer on stack 可能是在赋值的时候类型不匹配,典型的就是将int类型直接赋值到Integer这之类的。固然在写java代码的时候可以直接赋值,但是在字节码的时候先要调用Integer的valueOf方法创建一个Integer对象再赋值

  • java.lang.ClassFormatError Arguments can't fit into locals in class file 可能是设置的最大局部变量大小不够

Android 虚拟机注意
ART 模式下面,可能不会报告错误
但是在 Davlik 虚拟机下,会在运行时编译,检测器就会工作
导致在5.0及其以上的设备工作正常,但在操作系统5.0以下(部分4.4开启了ART不会出现)以下报告java.lang.VerifyError` 错误

导致这个错误的原因有2个

三方jar包本身有错误
反编译smali代码修改继承或者申请寄存器操作错误

作者:泛原罪
链接:https://www.jianshu.com/p/07873b237b86

@zengjingfang
Copy link
Owner Author

zengjingfang commented Jan 9, 2018

看完上述的文章,还是没搞明白为什么会有这个错误

接着看到另外一篇文章
Android 不想和你说话,抛了个 java.lang.VerifyError

文中说明出现这个异常的问题主要在类文件校验错误,出现这种情况的情况有:

  • 去extends 一个final类,去override一个final方法等
  • 使用的方法高版本的SDK有但是低版本没有

但是,这个地方既然已经用了反射的,为什么还会报这个错误?

@zengjingfang
Copy link
Owner Author

怀疑修改后还是存在版本兼容性问题

于是删除方法的注解

  @TargetApi(Build.VERSION_CODES.KITKAT)

果然,发现编译器警告ReflectiveOperationException

image

这么说,是这个ReflectiveOperationException在低版本中JVM加载时存在错误,理论上说的通。

@zengjingfang
Copy link
Owner Author

查看了Android的API文档

马上就发现,这个ReflectiveOperationException在19之后才加入,所以我们添加了注解:

@TargetApi(Build.VERSION_CODES.KITKAT)

那么,加了该注解,我们应该对低版本进行下兼容处理。

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private static String getWakeLockTag(PowerManager.WakeLock wakeLock,String wakeLockName) {
        String tag = "Default-WakeLock-Tag";
        if (wakeLock == null) {
            return tag;
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                Method getTagMethod = wakeLock.getClass().getDeclaredMethod("getTag");
                getTagMethod.setAccessible(true);
                tag = (String) getTagMethod.invoke(wakeLock, (Object[]) null);
            } catch (ReflectiveOperationException e) {
                LogUtil.w(TAG,e);
            } catch (Exception e) {
                LogUtil.w(TAG,e);
            }
        }else {
            LogUtil.d(TAG, "api < 19 ,so wakeLockName: " + wakeLockName);
            return wakeLockName;
        }

        return tag;
    }

@zengjingfang
Copy link
Owner Author

zengjingfang commented Jan 9, 2018

经过验证,还是报了如下相同异常

AndroidRuntime: FATAL EXCEPTION: Thread-21891
      java.lang.VerifyError: com/xxx/xxx/xxx/xxx/xxx/WakeLockManager
             at com.xxx.xxx.xxx.xxx.b.b.b(Unknown Source)
             at com.xxx.xxx.xxx.xxx.b.b.a(Unknown Source)
             at com.xxx.xxx.xxx.xxx.b.b$1.run(Unknown Source)

如果在5.0.0的机器上跑,则不存在该问题。

@zengjingfang
Copy link
Owner Author

zengjingfang commented Jan 9, 2018

如图,注释掉该部分代码就正常了

image

实际只需要将ReflectiveOperationException的变成Exception也不会有问题。

捕获的异常:java.lang.NoSuchMethodException: getTag []

基本说明了是运行时加载Class存在问题。

@zengjingfang
Copy link
Owner Author

查看这个class的文件,发现如下:

Constant pool:

   #63 = Class              #191          // java/lang/ReflectiveOperationException
  #117 = Utf8               Ljava/lang/ReflectiveOperationException;
  #119 = Class              #191          // java/lang/ReflectiveOperationException
  #191 = Utf8               java/lang/ReflectiveOperationException

@zengjingfang
Copy link
Owner Author

zengjingfang commented Jan 10, 2018

查JVM虚拟机类加载机制中验证步骤,发现:

  • 常量池的常量中是否有不被支持的常量类型(检查常量tag标志)

ReflectiveOperationException这个就是在低版本Android系统中不被支持的,所以在低版本Android系统中加载该WakeLockManager类时就报:java.lang.VerifyError。

@zengjingfang zengjingfang changed the title ReflectiveOperationException Android系统版本兼容ReflectiveOperationException问题 Jan 10, 2018
@zengjingfang zengjingfang moved this from In progress to Done in Android开发历险记 Jan 10, 2018
@zengjingfang
Copy link
Owner Author

上述的说法并不完全正确,因为发现如果我们修改代码如下:

 private static String getWakeLockTag(PowerManager.WakeLock wakeLock,String wakeLockName) {
        String tag = "Default-WakeLock-Tag";
        if (wakeLock == null) {
            return tag;
        }

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                Method getTagMethod = wakeLock.getClass().getDeclaredMethod("getTag");
                getTagMethod.setAccessible(true);
                new ReflectiveOperationException("test");// 直接New一个,而不是在catch的代码块里面
                tag = (String) getTagMethod.invoke(wakeLock, (Object[]) null);
            }catch (Exception e) {
                LogUtil.w(TAG,e);
            }
        }else {
            LogUtil.d(TAG, "api < 19 ,so wakeLockName: " + wakeLockName);
            return wakeLockName;
        }
        return tag;
    }

这样在运行时,并不会报错。

@zengjingfang
Copy link
Owner Author

zengjingfang commented Feb 27, 2018

那么catch里面和直接在代码里面直接去new有什么区别呢?

  • catch代码块在类加载时就会进行校验
  • 而try方法代码中需要运行时才会检测

所以,出现了上述的现象。在代码中 new ReflectiveOperationException("test");并没有真正的运行,但是在catch代码块中直接就爆出了 java.lang.VerifyError`。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

No branches or pull requests

1 participant