出这个问题有两个原因:
- 之前在公众号推文,很多同学认为android 9之后只要是反射都不被允许了,希望拨正这个观念;
- 虽然已经有很多好文提到android hidden api如何突破,但是大多是都是基于android 9来测试的,实际上随着targetSDKVersion的提升,很多方案都失效了,包括「反射的反射」这个方案,所以我觉得还是可以讨论下;
那么问题来了:
- hidden api是指不让使用反射吗?
- hidden api list在每个系统版本上,怎么知道哪些api被限制反射使用呢?
- hidden api 官方的限制思路是怎么样的?
- 目前市面上突破android hidden api的方案,能够支持targetSDKVersion 提升到android 10 , 11, 12吗?
更多问答 >>
-
每日一问 | 如何构造一个 hide interface 的实现类?
2022-02-08 23:51 -
每日一问 | 脱糖对于Android 打包期间插桩的有什么影响?
2022-03-07 21:26 -
每日一问 .class vs Class.forName() vs loadClass() 类加载傻傻分不清楚?
2022-02-11 14:22 -
每日一问 | 被声明为private final 的内部类,能生成一个子类对象吗?逆天篡改~
2022-04-15 21:13 -
每日一问 | 可以不借助 bindService,实现跨进程 binder 通信吗?
2022-04-27 23:43 -
每日一问 UndeclaredThrowableException 是什么异常?
2021-12-02 00:50 -
每日一问 | Gson中序列化对象的操作有低侵入的优化方案吗?
2021-12-02 00:50 -
每日一问 | 好奇ActivityThread中为什么会有一个 Application的集合?
2021-08-30 21:36 -
每日一问 | 关于 Activity 重建,值得探究的几个问题
2021-08-30 21:37
优秀啊!这是直接在java层通过unsafe去操作natvie内存了?照这么说,是不是可以写一个inline hook for Java了?另外补充一个,dalvik.system.VMDebug下还 ...查看更多
优秀啊!这是直接在java层通过unsafe去操作natvie内存了?照这么说,是不是可以写一个inline hook for Java了?另外补充一个,dalvik.system.VMDebug下还有个叫allowHiddenApiReflectionFrom的方法可以在vm.isJavaDebugable==true下使用,关闭单个类的访问权限检查
大佬
Unsafe 整出来的内存不能运行的,不过 Android 有 mmap 的 java binding 所以用 unsafe 整 native inline hook 也是完全是可以的(不过获取符号时 ...查看更多
Unsafe 整出来的内存不能运行的,不过 Android 有 mmap 的 java binding 所以用 unsafe 整 native inline hook 也是完全是可以的(不过获取符号时候就需要用 java 进行 ELF 解析比较痛苦)。甚至也可以做 java hook 的(替换 artmethod 的 entrypoint)。
有纯java实现的mmap?不过我看现在网上很多提到用mmap的,都是在native层实现的啊,那也还是避免不了使用jni,用了jni,那再用unsafe就没意义了。。不知道java.nio.File ...查看更多
有纯java实现的mmap?不过我看现在网上很多提到用mmap的,都是在native层实现的啊,那也还是避免不了使用jni,用了jni,那再用unsafe就没意义了。。不知道java.nio.FileChannel行不行?它有个map方法,由FileChannelImpl来实现(http://aospxref.com/android-12.0.0_r3/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java#984),看了下对应的底层代码,是用mmap来实现的(http://aospxref.com/android-12.0.0_r3/xref/libcore/ojluni/src/main/native/FileChannelImpl.c#FileChannelImpl_map0)
有:https://cs.android.com/android/platform/superproject/+/master:libcore/luni/src/main/java/android/s ...查看更多
有:https://cs.android.com/android/platform/superproject/+/master:libcore/luni/src/main/java/android/system/Os.java;drc=bacbadbc61a48e90cb5976040e7e0695bb75b69a;l=474 但是没有 mprotect 所以 hook 还是很麻烦
原来这里还有直接提供api啊,学到了👍,至于没有提供mprotect函数,那不做inline hook也可以啊,只做java hook。YAHFA的方案native代码比较少,我感觉可以试一下。不过 ...查看更多
原来这里还有直接提供api啊,学到了👍,至于没有提供mprotect函数,那不做inline hook也可以啊,只做java hook。YAHFA的方案native代码比较少,我感觉可以试一下。不过好像它的方案不支持像sandhook那样的动态hook,只能预先编译好代码,如果要在java层添加支持,那动态的jit compile也是个难题
做 art hook 这里有一个先例:https://weishu.me/2017/03/20/dive-into-art-hello-world/ ,简单改一下可以变成类似 yahfa 那种形式。没 ...查看更多
做 art hook 这里有一个先例:https://weishu.me/2017/03/20/dive-into-art-hello-world/ ,简单改一下可以变成类似 yahfa 那种形式。没懂你说的动态 hook 具体指什么,像 xposed 那样?
像 xposed 那种的话我自己有在维护一个:https://github.com/canyie/pine/ 。真要做的话,用纯 java 写一个 elf 解析器解析到 libc.so 里一些关键函数 ...查看更多
像 xposed 那种的话我自己有在维护一个:https://github.com/canyie/pine/ 。真要做的话,用纯 java 写一个 elf 解析器解析到 libc.so 里一些关键函数的地址,用汇编处理一下寄存器和跳转然后编译成机器码,Os.mmap 申请到 executable memory 往里填,然后找一个不用的方法改它的 entrypoint 然后调用这个方法可以实现调用 native function。不过这种活工作量太大稳定性上不去没人想做
感谢指路!👍我去学习一下。是的,就是动态生成类之后调用jit_compile_method这一步。你说的 "汇编处理一下寄存器和跳转然后编译成机器码" 是指在java层做动态编译吗?还能这样? ...查看更多
感谢指路!👍我去学习一下。是的,就是动态生成类之后调用jit_compile_method这一步。你说的 "汇编处理一下寄存器和跳转然后编译成机器码" 是指在java层做动态编译吗?还能这样?
不需要啊。。像 epic 那样写好汇编编译好然后写在 java 代码里就好
👍感谢解惑!
来了
补充一下:
@Reginer 同学所说的替换android.jar
的做法,效果只是能直接引用那些访问权限为public的hidden api,对于那些非public的api依然是无能为力。而且,这种方式还会有一个隐患:如果不小心直接引用到了那些【黑名单上】或者【maxTargetSdk
小于应用targetSdkVersion
】的api,又没有做相关异常处理的话,那么app就会死掉了。因为在替换android.jar
之后,SDK里所有的api都能直接引用,你很难直接分辨出某个方法或者属性是否在黑名单中,甚至有些根本没有@hide标记的,就比如android.app.job.JobInfo类中的getPriority
和getPriorityString
,这两个方法都没有@hide标记,也没有使用UnsupportedAppUsage注解,但它们在Android 11上却是黑名单api,当你使用替换后的android.jar
来进行直接访问时,就会报NoSuchMethodError了(是的,即使不使用反射也不行)。当然了,官方也有提供像veridex之类的工具,可以最大程度检测到项目引用了哪些被禁止使用的api。但如果我们遇到一些必须要访问黑名单上的api才能实现的需求,那就只能尝试突破这个限制了。在Android 11之前,可以直接通过反射调用【反射API】来访问那些hidden api,同时,dalvik.system.VMRuntime类里面有个
但是在Adnroid 11上,这个方法就失效了:当我们通过Class的setHiddenApiExemptions
方法,通过这个方法可以解除所有反射操作的限制,具体可以看下weishu大佬的这篇文章。getDeclaredMethod
、getDeclaredField
、getDeclaredConstructor
等方法获取相关类成员时,native层的java_lang_Class.cc会检查发起反射调用的那个类是否系统类(其实就是检查对应的classloader,系统类的classloader会是nullptr),如果是系统类就不做限制,否则进行常规的权限检查。来看看Android10和11的相关代码对比:isChangeEnabled
函数其实就是检查art/runtime/runtime.h里面的set(disabled_compat_changes_
)是否没有添加kPreventMetaReflectionBlacklistAccess
(防止通过元反射访问黑名单上的api)标识,如果没有这个标识的话,就不把这个java.lang.reflect包下的类当作是Caller Class,继续往上爬调用链,那么最终爬到的可能就是我们自己的代码了。所以在Android11上,【嵌套反射】跟【直接使用反射】,它这里获取到的Caller Class都是一样的。这时候可能你会想:刚刚说到的disabled_compat_changes_
,可以在java层设置吗?如果在java层有对应方法可以设置,那手动把这个标识添加进去不就行了?有是有,只不过是在VMRuntime这个类里面(setDisabledCompatChanges),是黑名单api,你根本访问不到。这时候可能你会想:既然在java层有这个方法,那应该会在sdk里面某个地方有调用的啊!那能不能通过调用【有调用到这个方法】的方法,来个曲线救国呢?这个方法只在RuntimeInit的applicationInit
方法里面有调用,但是不用想了,这个方法也是黑名单api!这时候可能你会想:好了,不说这个了,还是先来了解下它底层的检测机制吧:其实刚刚说的检查调用链的目的主要是获取Caller Class(发起发射调用的类)的所在域(Domain),这个域分三种,分别是:kCorePlatform(0)、kPlatform(1)、kApplication(2)(普通app(PathClassLoader)加载的类就是属于最后一个)。最终在判断要不要拒绝访问(ShouldDenyAccessToMember
函数)的时候会检查这个域,它的规则是这样的:【低等级】域成员可以任意访问【高等级】域成员,而【高等级】的域成员访问【低等级】域成员会做相应的限制。比如CorePlatform类可以随意访问Platform和Application,但是Application的类访问Platform或者CorePlatform API则会进行进一步的检查:首先是判断进程有没有关闭访问检查——这个标识保存在 /art/runtime/runtime.h中,但在进程启动时,是在java层传过去的:com.android.server.am.ProcessList第1940行,不过启动进程大多数是由ATMS控制,所以这一步普通应用在java层还是没有机会去改变的。如果没有关闭访问检查,接下来就会遍历 /art/runtime/runtime.h中的vector(
hidden_api_exemptions_
)查找是否包含目标成员签名前缀,如果包含的话,会提前return false,即不拒绝本次访问(这里的hidden_api_exemptions_
,就是题目所说的Android11之前可通过【元反射】调用VMRuntime.setHiddenApiExemptions
来设置的)。最后,如果
hidden_api_exemptions_
里面也没有目标成员的签名前缀,还会判断目标成员是否TestApi(即在源码里有@TestApi注解的那些成员),如果是TestApi并且没有禁止TestApi访问的话,那么也是允许本次访问的(允许访问有两个条件,一个是testApiPolicy == EnforcementPolicy::kDisabled
,这里的Policy跟前面的一样,也是在进程启动时在ProcessList.startProcessLocked
中初始化的;第二个条件可通过VMRuntime.setDisabledCompatChanges
来设置使之成立,但现在已是黑名单API,所以也没戏)。最后的最后,会查询目标成员在哪个API名单中,用一个uint32_t值来表示(其实源码这里弄得挺复杂的,下面简化了一下流程):
如果目标成员在kGreylistMaxP名单里,并且art/runtime/runtime.h里的
disabled_compat_changes_
没有添加HIDE_MAXTARGETSDK_P_HIDDEN_APIS标识(跟前面的PREVENT_META_REFLECTION_BLACKLIST_ACCESS一样),则return true(即拦截本次访问);如果目标成员在kGreylistMaxQ名单里,做法跟上面的一样,只是标识变成了HIDE_MAXTARGETSDK_Q_HIDDEN_APIS;
其他情况,直接判断当前targetSdkVersion是否大于目标成员设置的max-target-version
好了,检查机制到这里的结束了(以上内容基于最新的Android12源码进行分析),但好像也没有发现哪一部分是可以在java层直接去干涉的?相关的api都封死了。
在Github上有个叫FreeReflection的库,发现它在Android12上依然是有效的,看了下它的源码,是通过DexFile去动态加载一个外部dex文件(这个外部dex其实是base64编码后写死在某个Class里面),然后
不过DexFile这个类早已经标记deprecated,可能在下个版本就没有了(其实BaseDexClassLoader也有间接用到DexFile类的,不知道删掉这个类之后会用什么来代替),到时候要解除这个访问限制,估计都要在native中进行了吧!loadClass
时ClassLoader传null,这样一来,目标Class就被认为是系统类,所以这个类可以随意访问任何一个hidden api。把这个 “系统类” 加载出来之后,就能直接通过反射来调用VMRuntime的setHiddenApiExemptions
方法来解除所有api的限制了。终于等到了
惭愧😭本来还想找一下java层有没有其他可行的方法,结果找了好久都找不到,流下了没有技术的泪水😭
1.显然不是
2.做了这么多年安卓,是不是限制反射的api我一运行就知道了。。实际也可以从这里知道3.就是验证权限,看apk有没有对应权限4.发一下我一直在用的方案吧,让天下再没有hidden api,从这里下载android.jar,替换到原生sdk里,直接调用方法就可以了。我自己的app现在适配到Android12,目前也是一直在用这个方案,compileSdk多少就替换哪个android.jar后期增加:
看了一些小伙伴的回复,这里发现可能有必要做个详细的解释。
关于第二问,链接可以看一下,里面有个api名单列表,也就是那个hiddenapi-flags.csv
关于第四问,我一直在用的方案,编辑原生jar包,比如上面问为什么
VMRuntime.setHiddenApiExemptions
调用不了你完全可以用AS创建一个类叫
VMRuntime
,然后新建个方法叫setHiddenApiExemptions
,返回值,参数列表和原生相对应,方法体空实现,然后生成个class
文件,放到原生的android.jar
里,替换到原位置说到这里就应该了解了吧,调用不到,不是public这些都不是问题,唯一的问题是同类名同方法名及参数列表相同,返回值不同,这种情况这个方案没办法调用解决
相信你写出来的代码必然是经过了解和测试的,大量使用反射会让你适配新系统巨难,因为你根本看不出来哪里报错,但是你替换jar包基本都会发现不兼容的api,既然所有的api都可以调用,剩下是不是就是权限验证的问题,当然这是另外的领域了
4 不对,你会发现还是不能调用 VMRuntime.setHiddenApiExemptions
楼下 @回家的诱惑 已经回复得很清晰了。android.jar 本质就是使用 stub 而已,这种方法我自己也在用 https://github.com/LSPosed/LSPosed/tree/ma ...查看更多
楼下 @回家的诱惑 已经回复得很清晰了。android.jar 本质就是使用 stub 而已,这种方法我自己也在用 https://github.com/LSPosed/LSPosed/tree/master/hiddenapi-stubs。但是这种方法依然不能访问 hiddenapi-flags.csv 中标注 blacklist 的方法,也就是说你的方法依然不能调用 setHiddenApiExemptions。
在用的话到这里就可以了,没必要再往下突破,调用不了是你权限不够。android.jar 本质就是compileOnly
再往下就不是技术了,耍小聪明和官方思想对着干没必要,要用到哪个api直接提给官方就完了
。。你可以自己测试一下,用链接的方式调用和用反射调用有什么区别,链接能调用到的 api 反射也可以,反之反射不可以的 api 链接也不行。
你这不是知道么,能直接调用的为什么还用反射。使用反射调用系统api以后的新系统适配会很难,只在运行的时候报错
你在说什么?直接调用和反射调用是同一个效果那说明直接调用也会被 hidden api 策略限制,根本不是什么权限问题,题目要求的就是突破限制,你可以用 adb 关掉限制再试一下是不是同一个效果;另外如 ...查看更多
你在说什么?直接调用和反射调用是同一个效果那说明直接调用也会被 hidden api 策略限制,根本不是什么权限问题,题目要求的就是突破限制,你可以用 adb 关掉限制再试一下是不是同一个效果;另外如果你使用的 api 在某个版本被移除了,反射调用的话扔隐藏直接调用也会,而且直接调用扔的是 Error 更难处理,你 catch Exception 是抓不到的
我是有系统权限的,隐藏api随便调用。如果你使用的 api 在某个版本被移除了,提升sdk的时候使用这种方式在编译时就会发现,直接报错编译不过了,而使用反射调用报错会更加隐蔽。这个方案是我从6.0适配 ...查看更多
我是有系统权限的,隐藏api随便调用。如果你使用的 api 在某个版本被移除了,提升sdk的时候使用这种方式在编译时就会发现,直接报错编译不过了,而使用反射调用报错会更加隐蔽。这个方案是我从6.0适配到12总结出来最快的适配方式,只要保证第一次测试运行正常,之后的适配都是少量的。而我总结出来突破的办法就是有权限你就用,没权限你就别用,非要用只能去找谷歌反馈不要隐藏,因为系统在人家手里,思想需要保持一致。如果不从根本或者思想上去解决带来问题的原因,列举出来的解决办法不都是小聪明么,投机取巧,在这个系统上能用,更新就没法用了,这是很多骚操作应用适配超慢的原因。我们暂时还无法相互理解,但是在经历一段时间的适配之后,相信总会保持一致的
你在做系统开发,隐藏 api 限制当然是随便关,往后也就没有意义因为没有限制就已经离题了。至于链接编译,你只适配一个系统版本当然可以,但是如果新版本删了你要的 api 同时要求不升 sdk 不重新编译 ...查看更多
你在做系统开发,隐藏 api 限制当然是随便关,往后也就没有意义因为没有限制就已经离题了。至于链接编译,你只适配一个系统版本当然可以,但是如果新版本删了你要的 api 同时要求不升 sdk 不重新编译就能工作呢?更进一步,如果厂商乱改给你删了你怎么适配?这些才是做第三方 app 开发容易遇到的场景。
我是做系统【插件】的,指用户拿到手机 root 之后我们通过替换系统文件等方式把自己的代码注入进到一系列进程然后在这些别人的进程里面干活,和你那种不同之处就是没有别人系统的源码,更别提换一个 andr ...查看更多
我是做系统【插件】的,指用户拿到手机 root 之后我们通过替换系统文件等方式把自己的代码注入进到一系列进程然后在这些别人的进程里面干活,和你那种不同之处就是没有别人系统的源码,更别提换一个 android.jar 。工作要求是,同一份代码,完全不可控的环境(用户可能购买任何牌子的手机),直接兼容运行正常。我印象比较深刻的一个就是,我们只对高版本系统开启某个兼容措施,后面有人反馈有个低版本的系统也需要,后面追查发现这个系统确实是低版本的,但是偷偷把高版本才有的一个 commit 给 cherry pick 了过来,你说这种事情单纯做系统的能遇到吗
完全偏题。题目问的就是普通 app 就是想用目前有没有什么方案。而你又是说自己有系统权限又是劝人不要用的。你说得对,系统更新可能就没法用了,但是题目问的就是非要用的时候啊。并且 SDK,system- ...查看更多
完全偏题。题目问的就是普通 app 就是想用目前有没有什么方案。而你又是说自己有系统权限又是劝人不要用的。你说得对,系统更新可能就没法用了,但是题目问的就是非要用的时候啊。并且 SDK,system-API,test-API 都是很稳定的实现,只是普通 app 不能用而已,系统 app 还是可以用的并且有稳定性保证(但是这可不是你说的 apk 权限问题,appops 哪有这种权限的控制,这是 zygote fork 时候的一个参数决定的)。像`setHiddenApiExemptions` 是 system-api 这个级别,虽然是隐藏API,但已经不会改了。你都知道查 csv 了这点不会不知道吧? 而且拿个 framework.jar 来调用的方法早烂大街了,谷歌在 Android 9 开始就是为了封杀这种方法搞的 hidden api restriction。 至于什么理解不理解,我和 @canyie 都是玩魔法的,甚至想在 java 里面调用 Os.mmap 来运行汇编代码,所以对不起,永远不可能理解。
最后一起回复一下子吧,我适配mtk和高通两个平台,从6.0适配到12,。android.jar是基于aosp生成的,总是提到厂商乱改,厂商是可以乱改,改的都是方法内部和增加api,随便删api厂商也没 ...查看更多
最后一起回复一下子吧,我适配mtk和高通两个平台,从6.0适配到12,。android.jar是基于aosp生成的,总是提到厂商乱改,厂商是可以乱改,改的都是方法内部和增加api,随便删api厂商也没这魄力,有的就自己开发系统了,不搞什么**UI。framework,jar是很早的方案不错,也正是因为它具有的稳定性,所以一直延续至今,framework,jar的方式说白就是变成Dalvik调用,就是普通调用的方式,官方原有的限制该在还是在,普通app调用黑名单级别api直接崩溃,放到system/app下或者shareUid为system的可以调用。好,你非要用,那可以去提官方豁免api。玩魔法,其实不就是小聪明么?把操作想得那么高级没必要,完全没必要。搞技术还是要脚踏实地,今天这骚明天那个骚,骚一个方案换一个公司,骚一个方案换一个项目。维护一个项目久一点,之后看看能不能理解。非要用非要用,连个权限都没有,怎么就非得用
但是偷偷把高版本才有的一个 commit 给 cherry pick 了过来,这和替换android.jar的原理那更像了,后来又提了个问题,https://www.wanandroid.com/we ...查看更多
但是偷偷把高版本才有的一个 commit 给 cherry pick 了过来,这和替换android.jar的原理那更像了,后来又提了个问题,https://www.wanandroid.com/wenda/show/20867,使用隐藏接口的,把我回答的compileOnly形式依赖,改变成implementation功能实现了。
看题,不要讨论什么不应该。题目要求就是去绕过,你一直在说让谷歌开放,就像有题不答非要问为什么出这种题。至于没有权限,那是两回事,要用权限限制的都不在本进程,同一个进程随便调用(不然系统自己在应用进程跑 ...查看更多
看题,不要讨论什么不应该。题目要求就是去绕过,你一直在说让谷歌开放,就像有题不答非要问为什么出这种题。至于没有权限,那是两回事,要用权限限制的都不在本进程,同一个进程随便调用(不然系统自己在应用进程跑的代码怎么用),而且我的东西使用前提就是用户 root 手机,不 root 跑来用会被我拉黑。然后你说 implementation,那个 commit 是在 art 虚拟机里面的,难道我把 art 那一堆 c++ 还有汇编复制过来编译一份?
而且如果是 java api 的话,因为 classloader 会先向父 loader 请求加载类,实际访问到的还是系统那个,然后还是会触发 hidden api 限制机制。我们实际遇到的问题是,a ...查看更多
而且如果是 java api 的话,因为 classloader 会先向父 loader 请求加载类,实际访问到的还是系统那个,然后还是会触发 hidden api 限制机制。我们实际遇到的问题是,art 虚拟机里调用一个来自未初始化类的静态方法应该首先触发类初始化,而 art 虚拟机采取的方案是把所有未初始化类的静态方法的入口点设置为一个跳板,触发类初始化,然后把这些静态方法的入口点设置成真正的(这个过程叫做 fixup)。而高版本里 art 给类加了一个状态,导致已经初始化过的类仍然可能再次触发这个 fixup 过程导致静态方法入口被重设。这个问题是与高版本的兼容性问题,是因为高版本做了修改为了兼容它所以不得已而为之,不是我们自己需要,更谈不上什么 implementation。
root没有hidden api限制
注入进其他进程,其他进程的限制。
我甚至看不懂大佬们在说什么😅
神仙打架奥
我也没懂
对了,这里有个值得补充的点,jni 的
JNI_OnLoad
下的 JNIEnv 是不受 hiddenapi 限制的,可以直接用 jni 调用setHiddenApiExemptions
。这个在 Android 12 上也是没问题的。所以纯 java 方法可以用我的库(Unsafe),能用 native 就直接写个JNI_OnLoad
调用setHiddenApiExemptions
反对 @Reginer 的 3 和 4。
android 权限系统通过 uid 和 selinux context 来限制应用权限,如 访问网络 这类需要直接和 linux 内核进行交互的权限,就是通过向对应的 uid 增加 inet (3003) 这个 group 来实现放行;而像常用的 ActivityManager 等接口,则在对端进程内主动检查调用进程的 uid。这两者的权限校验部分都不在本进程进行,而 hidden api 限制的获取 member 引用这部分自始至终都在本进程执行,而且授予权限的最小单位是进程,如果是权限那么系统自己要跑在应用进程的代码也会被拒绝访问,所以不可能是权限系统限制。关于系统应用可以调用 hidden api 的原因是,AMS 检测到这个应用是系统应用且声明了相关属性,会在进程启动时给 zygote 发送过去,zygote 收到以后把整个 art 虚拟机设置为不执行限制,自然就可以用。
官方文档 非常明确说明使用 dalvik 指令直接引用 member 也会受到限制。可以查看 ClassLinker::FindResolvedMethod 等函数看看系统是如何限制 dalvik 指令访问的。
https://blog.canyie.top/2020/06/10/hiddenapi-restriction-policy-on-android-r/
hidden api主要是标识这些api不可靠(权限问题、内部代码的中间过度方法、不固定的api可能某个版本就没了),不想开发者调用,以免在不同版本中出现问题。
并不影响你通过反射使用。问题是你反射了之后,只是能调用到代码,并无法避免“不可靠”问题。