Detected problems with API compatibility(visit g.co/dev/appcompat for more info)

2023-11-06

最近手机升级了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         }
  1. 7241行检查是否需要检查用户调用了隐藏api(@hide注解的api),条件是应用开启了调试模式,或者
    ro.art.hiddenapi.warning 属性为1
  2. 经过上面一个步骤检查还要经过7241的检查,条件就是该应用启动后还没有弹出过调用隐藏api的警告,并且这期间调用了隐藏api,那么就弹出警告。
  3. 警告有两种方式,第一种是可调式的应用使用一个dialog弹出警告,否则使用toast弹出警告

经过上面的分析,我们就清楚了大致的流程
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<typename T>
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>(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);

在分析GetMemberActionImpl函数之前我们先来分析下哪里调用了它,翻遍所有代码我们发现在art/runtime/hidden_api.h文件中有调用

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);
}

函数里面确认了两个参数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<int>(policy) > static_cast<int>(api_list)) {
    return api_list == HiddenApiAccessFlags::kDarkGreylist
        ? kAllowButWarnAndToast
        : kAllowButWarn;
  } else {
    return kDeny;
  }
}

看过app_list和action的含义后我们回来分析GetMemberActionImpl函数

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 对于在豁免列表中的函数,直接放行
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<uint32_t>(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_又是怎么设置的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Detected problems with API compatibility(visit g.co/dev/appcompat for more info) 的相关文章

随机推荐

  • 创建多线程的四种方式

    目录儿 一 创建线程的四种方式 1 继承Thread类 2 实现Runnable接口 3 实现Callable接口 4 线程池 禁止使用 Executors 构建线程池 构建线程池的正确方式 一 创建线程的四种方式 1 继承Thread类
  • 大三,改变生活的一年

    国庆假期在偶然看到了去年假期画的stm32开发板的PCB 当时接触还没有一个月 想到了大三这一年来的点点滴滴 突然就想写下点什么 对于过去的一年的总结 又提醒自己还是给小白 要继续加油 首先我先说一下自己的情况 我是一个很普通的本科生 真的
  • 《人类染色体与染色体病》学习笔记

    染色质与染色体 染色质为细丝状 当细胞分裂时 染色质复制反复盘绕高度压缩 凝集形成兴泰特定的条状染色体 以保证DNA能准确分配到两个子细胞中 染色质和染色体的化学组成 DNA和组蛋白占染色质化学总量98 以上 染色质的种类 染色质由于其折叠
  • 消息通知系统

    using UnityEngine using System Collections using System Collections Generic public delegate void NotificationDelegate No
  • 华为OD机试真题-单词接龙-2023年OD统一考试(B卷)

    题目描述 单词接龙的规则是 可用于接龙的单词首字母必须要前一个单词的尾字母相同 当存在多个首字母相同的单词时 取长度最长的单词 如果长度也相等 则取字典序最小的单词 已经参与接龙的单词不能重复使用 现给定一组全部由小写字母组成单词数组 并指
  • hive错误:Exception in thread "main" java.lang.RuntimeException: java.io.IOException: Permission denied

    用不同用户去登录hive 可能会报如下错误 Exception in thread main java lang RuntimeException java io IOException Permission denied at org a
  • 嵌入式开发中的通讯协议(UART、SPI、CAN、I2C)

    一 UART UART是一种通用串行数据总线 用于异步通信 该总线双向通信 可以实现全双工传输和接收 1 1接口 两根线 UART TX 发送 UART RX 接收 1 2如何传输 UART作为异步串口通信协议的一种 工作原理是将传输数据的
  • Spring事务传播问题 — PROPAGATION_REQUIRES_NEW

    一 描述Spring遇到嵌套事务时 当被嵌套的事务被定义为 PROPAGATION REQUIRES NEW 时 内层Service的方法被调用时 外层方法的事务被挂起 内层事务相对于外层事务是完全独立的 有独立的隔离性等等 二 实验但实验
  • 区块链中节点和区块的关系&区块链的基本概念

    可以用数学知识来理解 节点是点 区块是线 区块链是面 节点是区块链应用技术里处理信息的基本单位 很多的结点处理完信息后 会被打上时间戳 生成数据区块 把区块按时间先后顺序连接起来就区块链 区块链是一个分布式分类账本 每个区块都是账本中的一页
  • PCB该怎样布局布线,这个小小案例,让你快速了解设计思路!

    在电路设计过程中 应用工程师往往会忽视印刷电路板 PCB 的布局 通常遇到的问题是 电路的原理图是正确的 但并不起作用 或仅以低性能运行 在本文中 我将向您介绍如何正确地布设运算放大器的电路板以确保其功能 性能和稳健性 最近 我与一名实习生
  • 16进制转化为10进制总结

    十六进制转换有16进制每一位上可以是从小到大为0 1 2 3 4 5 6 7 8 9 A B C D E F16个大小不同的数 即逢16进1 其中用A B C D E F 字母不区分大小写 这六个字母来分别表示10 11 12 13 14
  • Android Https相关完全解析 当OkHttp遇到Https

    http blog csdn net lmj623565791 article details 48129405
  • 初始化MySQL时可能遇到的问题

    之前自己第一次初始化数据库时一切顺利 基本过程也已经记录在这里 然而今天换了个环境重新配置数据库的时候 出现了许许多多的问题 趁自己还记得 简单做一下记录 1 Install时出错 在输入指令 mysqld install 时出错 出错内容
  • 如何控制步进电机速度(即,如何计算脉冲频率):

    两相步进电机介绍 实际步进电机控制很简单 应用都是傻瓜了 厂家做好步进电机的驱动器 步进电机如何工作由驱动器来控制 我们不需要对步进电机做深入的了解 只要知道步进电机驱动器的应用方法即可 当然简单的步进电机工作特性 还是必须知道的 下面我会
  • python代码~玫瑰花小练习

    完整代码如下 RoseDraw py import turtle as t 定义一个曲线绘制函数 def DegreeCurve n r d 1 for i in range n t left d t circle r abs d 初始位置
  • C++中 struct tm 和 time_t 时间和日期的使用方法

    1 概念 在C C 中 对字符串的操作有很多值得注意的问题 同样 C C 对时间的操作也有许多值得大家注意的地方 下面主要介绍在C C 中时间和日期的使用方法 通过学习C C 库 你会发现有很多操作 使用时间的方法 但在这之前你需要了解一些
  • C语言指针学习

    开始好好学习C语言啦 指针是C语言比较难的地方 但是非常重要 所以单独在此记录一下 有执念的人最可怕 一定要好好学习哇 C语言指针学习 1 指针是什么 2 null指针 3 指针的运算 4 数组指针 一维数组指针 二维数组指针 5 指针数组
  • 2023年网络安全比赛--网络安全应急响应中职组(超详细)

    一 竞赛时间 180分钟 共计3小时 二 竞赛阶段 竞赛阶段 任务阶段 竞赛任务 竞赛时间 分值 1 找出被黑客修改的系统别名 并将倒数第二个别名作为Flag值提交 2 找出系统中被植入的后门用户删除掉 并将后门用户的账号作为Flag值提交
  • 对1bit的脉冲信号进行展宽,转为32bit位宽,并产生有效信号

    如题 Verilog实现 奉上拙见 对1bit的脉冲信号进行展宽 转为32bit位宽 并产生有效信号 module zhankuan input clk input rst n input pulse in output reg pulse
  • Detected problems with API compatibility(visit g.co/dev/appcompat for more info)

    最近手机升级了Android 9 在写应用程序的时候进场会弹出一个弹框 如下在这里插入图片描述 吓得我一身冷汗 在对应的网站上看了下信息 原来是在android限制调用hide注解的api 注意这种现在并非原来的在sdk中简单去掉hide注