从 Android 9(API 级别 28)开始,Android 平台对应用能使用的非 SDK 接口实施了限制。只要应用引用非 SDK 接口或尝试使用反射或 JNI 来获取其句柄,这些限制就适用。
这些限制旨在帮助提升用户体验和开发者体验,为用户降低应用发生崩溃的风险,同时为开发者降低紧急发布的风险。
但!对开发者来说不一定是一件好事。
谷歌通过维护几个名单来进行对非SDK接口的限制
解决方案既然谷歌通过维护这样几个名单来限制sdk的使用,那么我们只要通过修改名单就可以实现解除对应限制。
参考谷歌源代码中的测试项目
在Android 10谷歌的测试代码中
https://android.googlesource.com/platform/art/+/refs/heads/android10-release/test/674-hiddenapi/src-art/Main.java#120
最终都是通过调用
VMRuntime.getRuntime().setHiddenApiExemptions(new String[]{"L"});Set exemptions to "L" (matches all classes) if we are testing whitelisting.← 谷歌注释
private void disableHideApi() {
try {
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);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
Class clazz_VMRuntime = (Class) forName.invoke(null, "dalvik.system.VMRuntime");
Method getRuntime = (Method) getDeclaredMethod.invoke(clazz_VMRuntime, "getRuntime", null);
Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(clazz_VMRuntime, "setHiddenApiExemptions", new Class[]{String[].class});
Object vmRuntime = getRuntime.invoke(null);
// Set exemptions to "L" (matches all classes) if we are testing whitelisting.
setHiddenApiExemptions.invoke(vmRuntime, new Object[]{new String[]{"L"}});
}
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
disableHideApi();
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接口。



