从Android Pie access to certain hidden classes, methods and fields was restricted开始。在使用Pie之前,仅通过反射即可使用这些隐藏的非SDK组件非常容易。
但是,现在,尝试访问Activity#createDialog()
之类的组件时,面向API 28(Pie)或更高版本的应用将遇到ClassNotFoundException,NoSuchMethodError或NoSuchFieldException异常。对于大多数人来说,这很好,但是作为喜欢使用该API的人,这可能会使事情变得困难。
如何解决这些限制?
答案 0 :(得分:3)
实际上有几种方法可以做到这一点。
出于测试目的,Google构建了一种在给定Android设备上全局禁用隐藏API限制的方式。问题链接中标题为如何启用对非SDK接口的访问权限?”部分的内容如下:
您可以通过使用以下adb命令更改API实施策略,来启用对开发设备上非SDK接口的访问:
adb shell settings put global hidden_api_policy_pre_p_apps 1 adb shell settings put global hidden_api_policy_p_apps 1
要将API实施策略重置为默认设置,请使用以下命令:
adb shell settings delete global hidden_api_policy_pre_p_apps adb shell settings delete global hidden_api_policy_p_apps
这些命令不需要root设备。
您可以将API实施策略中的整数设置为以下值之一:
- 0:禁用所有非SDK接口检测。使用此设置将禁用所有非SDK接口使用的日志消息,并阻止您使用StrictMode API测试应用。不建议使用此设置。
- 1:启用对所有非SDK接口的访问,但会打印日志消息,并带有警告,告知任何非SDK接口使用情况。使用此设置还可以使您使用StrictMode API测试应用。
- 2:禁止使用属于黑名单或灰名单且受目标API级别限制的非SDK接口。
- 3:禁止使用属于黑名单的非SDK接口,但允许使用属于灰名单且受目标API级别限制的接口。
(在Q测试版中,现在似乎只有一把钥匙:hidden_api_policy
。)
(在我的测试中,更改此设置后,您的应用需要完全重新启动-已终止进程-才能生效。)
您甚至可以使用Settings.Global.putInt(ContentResolver, String, Int)
在应用程序内部进行更改。但是,它要求应用程序拥有WRITE_SECURE_SETTINGS
权限,该权限只会自动授予签名级或特权应用程序。可以通过亚行手动授予它。
安全设置方法非常适合测试或用于个人应用程序,但是如果您打算将应用程序分发到不受您控制的设备上,则试图指导最终用户如何使用ADB可能是一场噩梦,甚至如果他们已经知道该怎么办,那就不方便了。
幸运的是,实际上有一种方法可以使用本机代码中的一些巧妙技巧来禁用应用程序的API限制。
在您的JNI_OnLoad()
方法中,您可以执行以下操作:
static art::Runtime* runtime = nullptr;
extern "C" jint JNI_OnLoad(JavaVM *vm, void *reserved) {
...
runtime = reinterpret_cast<art::JavaVMExt*>(vm)->GetRuntime();
runtime->SetHiddenApiEnforcementPolicy(art::hiddenapi::EnforcementPolicy::kNoChecks);
...
}
这将为您禁用隐藏的API检查,而无需任何特殊权限。
还有一个库可供您使用:https://github.com/tiann/FreeReflection/
JNI并不适合所有人(包括我)。它还需要您为不同的体系结构使用单独的应用程序版本。幸运的是,还有一个纯Java解决方案。
Android的隐藏API限制仅适用于未由平台签名签名且未在/system/etc/sysconfig/
中手动列入白名单的第三方应用。这意味着该框架(显然)可以访问它想要的任何隐藏方法,而这正是该方法所利用的。
这里的解决方案是使用双重反射(或“元反射”,翻译源称其为)。这是一个示例,它检索隐藏的方法(在Kotlin中):
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String::class.java, arrayOf<Class<*>>()::class.java)
val someHiddenMethod = getDeclaredMethod.invoke(SomeClass::class.java, "someHiddenMethod", Param1::class.java, Param2::class.java)
val result = someHiddenMethod.invoke(someClassInstance, param1, param2)
现在,这本身可以说是一个足够好的解决方案,但是可以采取进一步的措施。类dalvik.system.VMRuntime
具有方法:setHiddenApiExemptions(vararg methods: String)
。只需将"L"
传递给此方法即可免除所有隐藏的API,我们可以通过两次反射来做到这一点。
val forName = Class::class.java.getDeclaredMethod("forName", String::class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String::class.java, arrayOf<Class<*>>()::class.java)
val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") as Class<*>
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) as Method
val vmRuntime = getRuntime.invoke(null)
setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))
例如,将该代码放入您的Application类的onCreate()
方法中,然后您就可以像平常一样使用隐藏的API。
有关此示例的完整Java示例,请查看JNI部分中链接的FreeReflection库,或遵循下面的源代码。
答案 1 :(得分:3)
限制如何起作用?
VM(可能在java_lang_Class.cc中)识别调用者以及是否允许他访问隐藏字段或隐藏方法的新方法是遍历Stacktrace,直到出现一帧不匹配的“白名单”跳帧”(现在我们称之为“跳过列表”)。意味着如果您只是执行“通过反射进行反射”, Method.invoke(...)会在“跳过列表”中,并且将被跳过。下一帧将是您的方法,VM将检查您是否是系统库。如果无法访问,将被拒绝。
还可以吗?
现在解决方法:
如果Stacktrace除了getMethod()/ getDeclaredMethod()以外不包含任何框架,会发生什么?
回答:由于没有要验证的内容,因此VM无法验证...
好,但是我们如何实现呢?
好吧,如果我们生成一个新线程,则除了调用Method.invoke(...)的帧外,堆栈跟踪都将为“空”。意味着我们将堆栈跟踪减少到属于我们代码的一帧。
这是游戏中原生元素的来源:
通常,VM通过三个接口检查API限制:
意味着JNI验证程序不会遍历Java堆栈,并且Reflection验证程序不在乎本机。
好,我们将它们结合起来。
我们通过调用std :: async(...)。get()方法创建一个“空”堆栈跟踪,并通过JNI调用Reflection API(是的,我们通过JNI调用java.lang.reflect.Class.get *)因为它不是受限制的方法,所以jni调用将成功。现在,Reflection验证程序遍历整个堆栈,将找不到任何Java Frame,因为此线程中没有Java Frame(Class.get *调用除外)。
希望我满足的要求不是那么详细,而是要准确无误。
另请参阅:
答案 2 :(得分:1)
我正在为@TheWanderer的惊人实现添加Java实现:
Method forName = Class.class.getDeclaredMethod("forName", String.class);
Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
Class vmRuntimeClass = (Class) forName.invoke(null, "dalvik.system.VMRuntime");
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[] {String[].class});
Object vmRuntime = getRuntime.invoke(null);
setHiddenApiExemptions.invoke(vmRuntime, new String[][]{new String[]{"L"}});
答案 3 :(得分:0)
仅以合并链接包并为我工作的方式简化TheWanderer的回答:
向您的build.gradle添加依赖项...
implementation 'me.weishu:free_reflection:2.2.0'
将attachBaseContext添加到您的MainActivity中(例如):
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Reflection.unseal(base);
}
或者,在科特林:
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
Reflection.unseal(base)
}
而且,如果您的导入不是自动添加的,
import me.weishu.reflection.Reflection
import android.content.Context
希望有帮助!