Android P使用私有API弹出warning(转载)

转自:https://huangyu.github.io/archives/35fd3a81.html

最近手头的Mix2s接收到MIUI官方开发版的推送,可以升级为Android P,很开心,然后升级后,部分应用就打不开或者挂了,比如某奇艺(买了VIP看剧都看不了,强烈吐槽)。
于是顺手检查了一下公司的应用,发现都能正常启动和使用,但是最近在做SDK的接入Demo,一打开发现弹出了warning对话框

起初以为是targetAPI的问题,升级到28问题依旧,于是Google了一下,发现是从Android P开始,系统会限制非SDK的接口调用,也就是如果App通过反射使用系统隐藏的API,则会弹出提示。

具体的细节可参考:https://developer.android.com/about/versions/pie/restrictions-non-sdk-interfaces?hl=zh-cn

Android P中,将所有API分为以下几类:

  • 白名单:SDK的API,正常使用;
  • 浅灰名单(light greylist)会在logcat弹出提示:Accessing hidden field Landroid/os/Message;->flags:I (light greylist, JNI);
  • 深灰名单(dark greylist)则debug版本在会弹出dialog提示框,在release版本会有Toast提示,均提示为”Detected problems with API compatibility”。;
  • 黑名单(darklist)则会引发异常。

看了logcat日志,项目中许多地方使用到了系统私有API,直接全部修改工作量比较大,于是寻找解决方案。

最后发现原因是在SDK 28版本中Activity中的performStart有这个警告,只要在调用performStart前,ActivityThread的mHiddenApiWarningShown变量的值为true,产生这个警告的条件就无法满足。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
boolean isAppDebuggable = (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
// This property is set for all non-user builds except final release
boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1;
if (isAppDebuggable || isApiWarningEnabled) {
if (!mMainThread.mHiddenApiWarningShown && VMRuntime.getRuntime().hasUsedHiddenApi()) {
// Only show the warning once per process.
mMainThread.mHiddenApiWarningShown = true;
String appName = getApplicationInfo().loadLabel(getPackageManager())
.toString();
String warning = "Detected problems with API compatibility\n"
+ "(visit g.co/dev/appcompat for more info)";
if (isAppDebuggable) {
new AlertDialog.Builder(this)
.setTitle(appName)
.setMessage(warning)
.setPositiveButton(android.R.string.ok, null)
.setCancelable(false)
.show();
} else {
Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show();
}
}
}

具体是在Application OnCreate加入以下代码:(通过反射私有变量来解决私有API的warning,也是有趣)

1
2
3
4
5
6
7
8
9
10
11
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();
}

最终warning框不再弹出,但是logcat依旧,在Logcat中看到,修改mHiddenApiWarningShown本身就是dark greylist,估计后续该办法会给官方禁用。

最好的解决方式是根据logcat的warning提示,对使用darklist的地方必须进行修改,尽量避免使用dark greylist方法。

分享到