原文地址:
https://www.fatalerrors.org/a/detected-problems-with-api-compatibility.html
Detected problems with API compatibility(visit g.co/dev/appcompat for more info)
Recently, the mobile phone has upgraded Android 9. When entering the application, it will pop up a box
Detected problems with API compatibility(visit g.co/dev/appcompat for more info)
I was scared to sweat. I looked at the information on the corresponding website. It turned out that android restricted the use of the hide annotation api. Note that this is not the original hide annotation API in the sdk, but the virtual machine level.
This article is used to record the entire investigation process.
First, the location of the warning pop-up is located in the performRestart function of Activity.java.
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 " 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 + " " + warning, Toast.LENGTH_LONG).show(); 7259 } 7260 } 7261 }
- Line 7241 checks to see if the user has invoked the hidden API (@hide annotated api) if the application has debug mode on, or the ro.art.hiddenapi.warning property is 1.
- After the previous step, 7241 is checked, provided that the application starts without a warning to invoke the hidden api, and during this time the hidden API is invoked, then the warning pops up.
- There are two ways to warn. The first is that the tunable application uses a dialog pop-up warning, or else uses the toast pop-up warning.
After the above analysis, we know the general process.
VMRuntime.getRuntime().hasUsedHiddenApi() is the basis for judging whether an application has called a hidden function.
VMRuntime is the Runtime class in the runtime.cc of the art code when the art virtual machine is running. We know that the call between java and C ++ uses jni as the binder. The corresponding jni code is in art/runtime/native/dalvik_system_VMRuntime.cc, and the function is
static jboolean VMRuntime_hasUsedHiddenApi(JNIEnv*, jobject) { return Runtime::Current()->HasPendingHiddenApiWarning() ? JNI_TRUE : JNI_FALSE; }
art Runtime is a singleton. We analyze the HasPendingHiddenApiWarning function.
bool HasPendingHiddenApiWarning() const { return pending_hidden_api_warning_; }
That is to read the variable value of pending_hidden_api_warning_.
So what we need to focus on is where the value is set.
void SetPendingHiddenApiWarning(bool value) { pending_hidden_api_warning_ = value; }
There are three places to call the function, two of which are set to false to indicate that we don't need to care about clearing the variable, so there is only one place in the art/runtime/hidden_api.cc file.
The code we need to care about is as follows.
template<typename T> 209 Action GetMemberActionImpl(T* member, 210 HiddenApiAccessFlags::ApiList api_list, 211 Action action, 212 AccessMethod access_method)
This is a template function. There are two implementations.
// Need to instantiate this. template Action GetMemberActionImpl<ArtField>(ArtField* member, HiddenApiAccessFlags::ApiList api_list, Action action, AccessMethod access_method); template Action GetMemberActionImpl<ArtMethod>(ArtMethod* member, HiddenApiAccessFlags::ApiList api_list, Action action, AccessMethod access_method);
Before we analyze the GetMemberActionImpl function, let's figure out where to call it, and look through all the code we found in the art/runtime/hidden_api.h file
template<typename T> inline Action GetMemberAction(T* member, Thread* self, std::function<bool(Thread*)> 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); }
Two parameters api_list and action are validated in the function. The api_list parameter is obtained using the member->GetHiddenApiAccessFlags() function, which is actually an enumeration type, and the code uses the function or variable type, including the following
enum ApiList {
kWhitelist = 0, whitelist function
kLightGreylist, white grey list
kDarkGreylist, grey list
kBlacklist, blacklist
kNoList, not in the list.
};
Action represents the default action performed by different list listings, and is also an enumeration variable.
GetActionFromAccessFlags(member->GetHiddenApiAccessFlags())
enum Action { kAllow, //pass kAllowButWarn, //Pass but warn kAllowButWarnAndToast, //Pass but warn by toast kDeny //refuse }; inline Action GetActionFromAccessFlags(HiddenApiAccessFlags::ApiList api_list) { if (api_list == HiddenApiAccessFlags::kWhitelist) { return kAllow; //The default action of white list is through. } //Next, we need to decide how to execute the default action based on 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<int>(policy) > static_cast<int>(api_list)) { return api_list == HiddenApiAccessFlags::kDarkGreylist ? kAllowButWarnAndToast : kAllowButWarn; } else { return kDeny; } }
After reading the meaning of app_list and action, we return to analyze the GetMemberActionImpl function.
208 template<typename T> 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 for the function in the exempted list, pass directly. 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); //Add to white list 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 print log that cannot be passed directly. 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 some cases print event log. And control the speed. 249 (static_cast<uint32_t>(std::rand()) & 0xffff) < eventLogSampleRate) { 250 member_signature.LogAccessToEventLog(access_method, action); 251 } 252 } 253 254 if (action == kDeny) { // action is a direct return of refusal. 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) { //Print warning 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 }
As you can see from the code above, the GetMemberActionImpl function is mainly used to print log and add whitelist for different action.
Thus, the final function is GetActionFromAccessFlags function.
Our problem is generally clear here.
The whole framework is based on a flags in the method to get the corresponding execution action, and there are two more points, where flags are set, and how hidden_api_policy_is set.