前言
从 Android 9(API 级别 28)开始,Android 平台对应用能使用的非 SDK 接口实施了限制。只要应用引用非 SDK 接口或尝试使用反射或 JNI 来获取其句柄,这些限制就适用。
这些限制旨在帮助提升用户体验和开发者体验,为用户降低应用发生崩溃的风险,同时为开发者降低紧急发布的风险。
但!对开发者来说不一定是一件好事。
谷歌通过维护几个名单来进行对非SDK接口的限制
解决方案
既然谷歌通过维护这样几个名单来限制sdk的使用,那么我们只要通过修改名单就可以实现解除对应限制。
参考谷歌源代码中的测试项目
在Android 10谷歌的测试代码中
最终都是通过调用
VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"});
Set exemptions to "L" (matches all classes) if we are testing whitelisting.← 谷歌注释
Method forName, getDeclaredMethod, getMethod, getDeclaredField, getField, getDeclaredConstructor, getConstructor, newInstance;
private void allowHideMethod() {
try {
forName = Class.class.getDeclaredMethod("forName", String.class);
// invoke = Method.class.getMethod("invoke", Object.class, Object[].class);
// 反射获取方法
getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
getMethod = Class.class.getDeclaredMethod("getMethod", String.class, Class[].class);
// 反射获取变量
getDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class);
getField = Class.class.getDeclaredMethod("getField", String.class);
// 反射实例化代码
getDeclaredConstructor = Class.class.getDeclaredMethod("getDeclaredConstructor", Class[].class);
getConstructor = Class.class.getDeclaredMethod("getConstructor", Class[].class);
newInstance = Constructor.class.getDeclaredMethod("newInstance", Object[].class);
} catch (Throwable igone) {
}
if (Build.VERSION.SDK_INT > 27) {
/*
* 设置豁免所有hide api
* http://androidxref.com/9.0.0_r3/xref/art/test/674-hiddenapi/src-art/Main.java#100
* VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"});
*/
try {
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 sVmRuntime = getRuntime.invoke(null);
setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{new String[]{"L"}});
} catch (Throwable igone) {
igone.printStackTrace();
}
}
}
allowHideMethod();
try {
Class clz = mActivityCustomErrorBinding.etError.getClass();
while (clz != TextView.class) {
clz = clz.getSuperclass();
}
Field field_mEditor = (Field) getDeclaredField.invoke(clz, "mEditor");
field_mEditor.setAccessible(true);
Object mEditor = field_mEditor.get(mActivityCustomErrorBinding.etError);
Class clz_Editor = Class.forName("android.widget.Editor");
Method showError = (Method) getDeclaredMethod.invoke(clz_Editor, "showError", null);
Method hideError = (Method) getDeclaredMethod.invoke(clz_Editor, "hideError", null);
Log.e(TAG, mEditor.getClass().getName());
Field field = (Field) getDeclaredField.invoke(clz_Editor, "mErrorPopup");
field.setAccessible(true);
} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
在需要进行反射的地方前面调用disableHideApi()。使用完毕后调用 setHiddenApiExemptions.invoke(sVmRuntime,newObject[]{newString[0]});重置列表即可。
实际上! 通过反射获取的getDeclaredMethod
等反射方法进行反射工作,也可以直接绕过对非SDK接口的限制(上面例子其实不用调用disableHideApi()也可以通过)
例如Method setHiddenApiExemptions = (Method)getDeclaredMethod.invoke(vmRuntimeClass,"setHiddenApiExemptions",newClass[]{String[].class});
上面例子中的setHiddenApiExemptions
本来就是属于限制使用的非SDK接口。