從 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) {
}
}
最後推薦使用最後一種,成本最低效果最好。