从 android 9.0 开始,当代码调用某些系统api的时候,会因为api的一些判定灰名单级别,不同程度的对app做出提醒,最严重的是直接弹窗提醒,次之是会在logcat打印出调用内容。
具体api名单列表:https://developer.android.google.cn/about/versions/10/non-sdk-q
但有些情况下我们确实要使用这些api,下面是我总结了以下几种方案:
1. 反射禁止弹窗
优点:
- 能避免弹窗
缺点:
- 不能避免代码扫描,logcat打印
- 某些类方法用 getMethod 无法发现,无法获取到,因此也就无法反射
try {
Class aClass = Class.forName("android.content.pm.PackageParser$Package");
Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class);
declaredConstructor.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
try {
Class cls = Class.forName("android.app.ActivityThread");
Method declaredMethod = cls.getDeclaredMethod("currentActivityThread");
declaredMethod.setAccessible(true);
Object activityThread = declaredMethod.invoke(null);
Field mHiddenApiWarningShown = cls.getDeclaredField("mHiddenApiWarningShown");
mHiddenApiWarningShown.setAccessible(true);
mHiddenApiWarningShown.setBoolean(activityThread, true);
} catch (Exception e) {
e.printStackTrace();
}
2. 使用元反射(能避免弹窗,logcat打印,只用使用元反射的才能达到效果)
优点:
- 能避免弹窗
- 能避免代码扫描,logcat打印
- 某些用getMethod无法发现的方法,可以被发现了了,也可以反射了
缺点:
- 对于正常反射的代码,无效,会弹窗,打印logcat
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) {
}
反射时,原来的反射代码应这样写:
正常的反射
Method getStringMethod = A.class.getDeclaredConstructor("getString");
getStringMethod.invoke(new A());
元反射
Method getStringMethod = getDeclaredConstructor.invoke(A.class,"getString");
getStringMethod.invoke(new A());
3. 元反射基础上,本进程将所有灰黑api加入白名单(能避免弹窗,logcat打印,后续即使不使用元反射也能达到效果)
优点:
- 能避免弹窗
- 能避免代码扫描,logcat打印
- 某些用getMethod无法发现的方法,可以被发现了了,也可以反射了
- 对于正常反射的代码,仍然不会弹窗,打印logcat
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) {
}
}
最后推荐使用最后一种,成本最低效果最好。