最近手机升级了Android 9,在写应用程序的时候进场会弹出一个弹框,如下在这里插入图片描述
吓得我一身冷汗,在对应的网站上看了下信息,原来是在android限制调用hide注解的api,注意这种现在并非原来的在sdk中简单去掉hide注解的api,而是在虚拟机层面做了限制。
本篇文章用于记录整个调查过程。
首先弹出警告弹窗的位置在Activity.java中的performRestart函数中,有如下片段
7238 // This property is set for all non-user builds except final release
7239 boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1;
7240
7241 if (isAppDebuggable || isApiWarningEnabled) {
7242 if (!mMainThread.mHiddenApiWarningShown && VMRuntime.getRuntime().hasUsedHiddenApi()) {
7243 // Only show the warning once per process.
7244 mMainThread.mHiddenApiWarningShown = true;
7245
7246 String appName = getApplicationInfo().loadLabel(getPackageManager())
7247 .toString();
7248 String warning = "Detected problems with API compatibility\n"
7249 + "(visit g.co/dev/appcompat for more info)";
7250 if (isAppDebuggable) {
7251 new AlertDialog.Builder(this)
7252 .setTitle(appName)
7253 .setMessage(warning)
7254 .setPositiveButton(android.R.string.ok, null)
7255 .setCancelable(false)
7256 .show();
7257 } else {
7258 Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show();
7259 }
7260 }
7261 }
经过上面的分析,我们就清楚了大致的流程
VMRuntime.getRuntime().hasUsedHiddenApi() 就是判断应用有没有调用过隐藏函数判断的依据。
VMRuntime是art虚拟机的运行时,对应art代码中的runtime.cc里面的Runtime类,我们知道java和c++之间调用要使用jni做粘合剂,对应的jni代码在art/runtime/native/dalvik_system_VMRuntime.cc中,函数为
static jboolean VMRuntime_hasUsedHiddenApi(JNIEnv*, jobject) {
return Runtime::Current()->HasPendingHiddenApiWarning() ? JNI_TRUE : JNI_FALSE;
}
art Runtime是单例的,我们分析下HasPendingHiddenApiWarning函数
bool HasPendingHiddenApiWarning() const {
return pending_hidden_api_warning_;
}
就是读取pending_hidden_api_warning_的变量值。
所以我们后面要关注的就是该值在哪里被设置
void SetPendingHiddenApiWarning(bool value) {
pending_hidden_api_warning_ = value;
}
有三个地方调用该函数,其中两个是设置值为false,说明用于清除该变量,我们不需要关心,那么只有一个位置,在art/runtime/hidden_api.cc 文件中,
我们需要关心的代码如下
template
209 Action GetMemberActionImpl(T* member,
210 HiddenApiAccessFlags::ApiList api_list,
211 Action action,
212 AccessMethod access_method)
这是一个模板函数,其中有两个实现
// Need to instantiate this.
template Action GetMemberActionImpl(ArtField* member,
HiddenApiAccessFlags::ApiList api_list,
Action action,
AccessMethod access_method);
template Action GetMemberActionImpl(ArtMethod* member,
HiddenApiAccessFlags::ApiList api_list,
Action action,
AccessMethod access_method);
在分析GetMemberActionImpl函数之前我们先来分析下哪里调用了它,翻遍所有代码我们发现在art/runtime/hidden_api.h文件中有调用
template
inline Action GetMemberAction(T* member,
Thread* self,
std::function fn_caller_is_trusted,
AccessMethod access_method)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(member != nullptr);
// Decode hidden API access flags.
// NB Multiple threads might try to access (and overwrite) these simultaneously,
// causing a race. We only do that if access has not been denied, so the race
// cannot change Java semantics. We should, however, decode the access flags
// once and use it throughout this function, otherwise we may get inconsistent
// results, e.g. print whitelist warnings (b/78327881).
HiddenApiAccessFlags::ApiList api_list = member->GetHiddenApiAccessFlags();
Action action = GetActionFromAccessFlags(member->GetHiddenApiAccessFlags());
if (action == kAllow) {
// Nothing to do.
return action;
}
// Member is hidden. Invoke `fn_caller_in_platform` and find the origin of the access.
// This can be *very* expensive. Save it for last.
if (fn_caller_is_trusted(self)) {
// Caller is trusted. Exit.
return kAllow;
}
// Member is hidden and caller is not in the platform.
return detail::GetMemberActionImpl(member, api_list, action, access_method);
}
函数里面确认了两个参数api_list 和action,api_list参数使用 member->GetHiddenApiAccessFlags() 函数获取,其实该类型是一个枚举类型,代码使用该函数或者变量的类型,其中包括如下几种
enum ApiList {
kWhitelist = 0, 白名单函数
kLightGreylist, 白灰名单
kDarkGreylist, 灰名单
kBlacklist, 黑名单
kNoList, 不在列表中
};
Action则代表遇到不同的名单列表执行的默认动作,也是一个枚举变量,使用
GetActionFromAccessFlags(member->GetHiddenApiAccessFlags()) 函数获取
enum Action {
kAllow, //通过
kAllowButWarn, //通过但是警告
kAllowButWarnAndToast, //通过警告弹出toast
kDeny //拒绝
};
inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) {
if (api_list == HiddenApiAccessFlags::kWhitelist) {
return kAllow; //白名单标志默认动作是通过
}
//下面要根据EnforcementPolicy 决定如何执行默认动作,我们先不关系它
EnforcementPolicy policy = Runtime::Current()->GetHiddenApiEnforcementPolicy();
if (policy == EnforcementPolicy::kNoChecks) {
// Exit early. Nothing to enforce.
return kAllow;
}
// if policy is "just warn", always warn. We returned above for whitelist APIs.
if (policy == EnforcementPolicy::kJustWarn) {
return kAllowButWarn;
}
DCHECK(policy >= EnforcementPolicy::kDarkGreyAndBlackList);
// The logic below relies on equality of values in the enums EnforcementPolicy and
// HiddenApiAccessFlags::ApiList, and their ordering. Assertions are in hidden_api.cc.
if (static_cast(policy) > static_cast(api_list)) {
return api_list == HiddenApiAccessFlags::kDarkGreylist
? kAllowButWarnAndToast
: kAllowButWarn;
} else {
return kDeny;
}
}
看过app_list和action的含义后我们回来分析GetMemberActionImpl函数
208 template
209 Action GetMemberActionImpl(T* member,
210 HiddenApiAccessFlags::ApiList api_list,
211 Action action,
212 AccessMethod access_method) {
213 DCHECK_NE(action, kAllow);
214
215 // Get the signature, we need it later.
216 MemberSignature member_signature(member);
217
218 Runtime* runtime = Runtime::Current();
219
220 // Check for an exemption first. Exempted APIs are treated as white list.
221 // We only do this if we're about to deny, or if the app is debuggable. This is because:
222 // - we only print a warning for light greylist violations for debuggable apps
223 // - for non-debuggable apps, there is no distinction between light grey & whitelisted APIs.
224 // - we want to avoid the overhead of checking for exemptions for light greylisted APIs whenever
225 // possible.
226 const bool shouldWarn = kLogAllAccesses || runtime->IsJavaDebuggable();
227 if (shouldWarn || action == kDeny) {
228 if (member_signature.IsExempted(runtime->GetHiddenApiExemptions())) {
//1 对于在豁免列表中的函数,直接放行
229 action = kAllow;
230 // Avoid re-examining the exemption list next time.
231 // Note this results in no warning for the member, which seems like what one would expect.
232 // Exemptions effectively adds new members to the whitelist.
233 MaybeWhitelistMember(runtime, member); //加入到白名单
234 return kAllow;
235 }
236
237 if (access_method != kNone) {
238 // Print a log message with information about this class member access.
239 // We do this if we're about to block access, or the app is debuggable.
240 member_signature.WarnAboutAccess(access_method, api_list); //2不能直接放行的打印log
241 }
242 }
243
244 if (kIsTargetBuild && !kIsTargetLinux) {
245 uint32_t eventLogSampleRate = runtime->GetHiddenApiEventLogSampleRate();
246 // Assert that RAND_MAX is big enough, to ensure sampling below works as expected.
247 static_assert(RAND_MAX >= 0xffff, "RAND_MAX too small");
248 if (eventLogSampleRate != 0 && //3 一些情况打印event log。还要控制速率
249 (static_cast(std::rand()) & 0xffff) < eventLogSampleRate) {
250 member_signature.LogAccessToEventLog(access_method, action);
251 }
252 }
253
254 if (action == kDeny) { // action是拒绝的直接返回
255 // Block access
256 return action;
257 }
258
259 // Allow access to this member but print a warning.
260 DCHECK(action == kAllowButWarn || action == kAllowButWarnAndToast);
261
262 if (access_method != kNone) { //打印警告
263 // Depending on a runtime flag, we might move the member into whitelist and
264 // skip the warning the next time the member is accessed.
265 MaybeWhitelistMember(runtime, member);
266
267 // If this action requires a UI warning, set the appropriate flag.
268 if (shouldWarn &&
269 (action == kAllowButWarnAndToast || runtime->ShouldAlwaysSetHiddenApiWarningFlag())) {
270 runtime->SetPendingHiddenApiWarning(true);
271 }
272 }
273
274 return action;
275 }
从上面的代码数可以看到,GetMemberActionImpl函数主要是用于对不同action进行log打印加白等工作。
由此可见,最终要的函数还是GetActionFromAccessFlags 函数。
到这里我们的问题大致就清楚了
整个框架是根据方法中的一个flags去获取对应的执行动作,其中还有两点一点,flags是从哪设置的,hidden_api_policy_又是怎么设置的。