zengjingfang / AndroidBox

Android开发知识、经验、资料等总结,作为个人的开发知识体系
Apache License 2.0
16 stars 3 forks source link

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

Open zengjingfang opened 6 years ago

zengjingfang commented 6 years ago

反射获取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 commented 6 years ago

看代码发现 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 commented 6 years ago

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

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

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

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

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

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

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

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

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

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

zengjingfang commented 6 years ago

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

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

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

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

zengjingfang commented 6 years ago

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

于是删除方法的注解

  @TargetApi(Build.VERSION_CODES.KITKAT)

果然,发现编译器警告ReflectiveOperationException

image

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

zengjingfang commented 6 years ago

查看了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 commented 6 years ago

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

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 commented 6 years ago

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

image

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

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

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

zengjingfang commented 6 years ago

查看这个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 commented 6 years ago

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

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

zengjingfang commented 6 years ago

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

 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 commented 6 years ago

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

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