Android 性能之刷新率设置和管理

目录

1. 刷新率和帧率

2. 多种刷新率

3. 基本原理

3.1 屏幕 & 显示控制器

3.2 Composer Service

4. Framework 策略

4.1基本架构

4.2 刷新率设置项的定义

4.2.1 最低刷新率

4.2.2 默认刷新率 & 默认的用户设置刷新率

4.2.2.1 设置入口

4.2.2.2 设置场景

4.2.2.3 DisplayDeviceConfig

4.2.3 用户设置的刷新率

4.2.4 应用刷新率 & 应用期望的 base mode 刷新率

4.3 刷新率更新 & 应用流程

4.3.1 入口

4.3.1.1 system settings "peak_refresh_rate"

4.3.1.2 device_config <"display_manager", "peak_refresh_rate_default">

4.3.1.3 setDefaultRefreshRate 方法设置 "默认刷新率"(Just for test)

4.3.1.4 窗口切换

4.3.1.5 小结

4.3.2 Vote & VotesStorage

4.3.2.1 Vote

4.3.2.2 VotesStorage

4.3.3 DisplayModeDirector.updateRefreshRateSettingLocked

4.3.4 DisplayModeDirector .notifyDesiredDisplayModeSpecsChangedLocked

4.3.5 DisplayModeDirector.getDesiredDisplayModeSpecs

4.3.6 nativeSetDesiredDisplayModeSpecs

4.3.7 mode 与 VoteSummary 匹配规则

5. 用户设置界面

6. 默认配置


1. 刷新率和帧率

帧率是指应用 1 秒绘制了多少帧。帧率一般是实时变化的,即每秒都可能不一样,上一秒 58 下一秒 60 很平常。

刷新率一般是指屏幕刷新率,即屏幕 1 秒内刷新多少次。屏幕刷新率一般是固定的,不会上一秒 58 下一秒 60;现在通常的刷新率有 60/90/120。

对于 Android 平台来说,屏幕的刷新率会影响应用绘制的调度周期。一般情况下平台调度绘制的周期与屏幕刷新的周期同步,这是通过 vsync 机制来实现的。

考虑以下两个场景:

  • 游戏场景

对于游戏这类持续绘制的应用场景,我们期望帧率能够稳定在一个比较高(接近刷新率)的水平。

但是如果平台性能较差,或者游戏的算力开销较高,游戏帧率可能无法稳定在接近刷新率的水平,就会出现抖动率高的问题,用户体验会非常差。

此时如果能降低刷新率,反倒是可以使帧率更加稳定,得到更好的用户体验。

  • 低功耗模式

如果我们在某些场景下期望获得更低的功耗,而这些场景对帧率并不敏感(即帧率高低不影响用户体验),降低刷新率将是不错的选择。

2. 多种刷新率

https://source.android.com/docs/core/graphics/multiple-refresh-rate?hl=zh-cn

目前,Android 11 增加了对具有多种刷新率的设备的支持。此功能包含三个主要组成部分:

  • android.hardware.graphics.composer@2.4 中引入的新 HAL API。

  • 平台代码,用于解析不同刷新率的设备配置并设置所需的刷新率

  • 新增的 SDK 和 NDK API,使应用可以设置所需的帧速率

这里的 "多种刷新率" 支持,包含了两层含义。

一是指屏幕的多种显示模式具有不同刷新率,而平台能够支持设置不同的显示模式;

二是指在屏幕刷新率固定的情况下,平台能够使用不同的频率来调度应用绘制。

本文为了方便区分这两种情况,定义

  • 屏幕刷新率(refresh rate):屏幕刷新率是屏幕的物理属性

  • 应用刷新率(render frame rate):应用绘制的调度频率,是平台软件属性

  • 刷新率:包含屏幕刷新率和应用刷新率

需要注意的是,设置应用刷新率时,必须保证屏幕刷新率能被应用刷新率整除。

这是因为屏幕刷新率不能被应用刷新率整除时,帧的间隔将不均匀;而眼睛和大脑期望看到的是平滑、连续的运动,当帧显示时间不均匀时,会产生跳动感,即抖动。


 

一般,如果屏幕刷新率能被应用刷新率整除,我们就称应用刷新率与屏幕刷新率同步(而非完全相同)。

3. 基本原理

刷新率设置流程,涉及

  • 硬件,即屏幕和显示控制器

  • Hal Service,即 composer servcie

  • SurfaceFlinger

  • Framework,即系统服务

  • 应用

数据流向从下至上。

3.1 屏幕 & 显示控制器

显示控制器(Display Controller)是一个连接屏幕的硬件单元,负责管理和驱动屏幕显示图像的各个方面。它通常包括以下功能:

  1. 帧缓冲管理:显示控制器从帧缓冲区读取图像数据,这些数据是由图形处理单元(GPU)或中央处理单元(CPU)生成并存储的。

  2. 信号生成:显示控制器生成合适的信号来驱动屏幕。这些信号包括水平和垂直同步信号、时钟信号以及数据传输信号。

  3. 分辨率和刷新率:控制器管理屏幕的分辨率和刷新率,确保图像以正确的分辨率显示,并以适当的刷新率更新屏幕。

  4. 颜色空间转换:显示控制器可能需要将图像数据从一种颜色空间转换到另一种,以适应不同的显示要求。

  5. 图层合成:在一些高级显示控制器中,可以支持多图层合成。这意味着它可以将来自不同来源的多个图像合成到一个最终显示的图像上。这对于窗口系统、叠加窗口以及复杂的用户界面来说非常重要。

显示控制器的工作流程

  1. 接收数据:显示控制器从系统内存或专用的图形内存中读取图像数据。

  2. 处理数据:如果需要,显示控制器会对数据进行处理,例如颜色空间转换、缩放、旋转等。

  3. 输出信号:将处理过的数据转换为显示屏能够理解的信号格式,并通过显示接口(如HDMI、LVDS、eDP等)发送到显示屏。

也就是说,屏幕的不同刷新率是由显示控制器控制的。

显示控制器将屏幕刷新率、分辨率(height、width)等屏幕属性封装到显示模式(display mode)中。

一个显示模式就是一组屏幕属性的集合,不同显示模式包含的屏幕属性集合不会完全相同。

3.2 Composer Service

即实现 android.hardware.graphics.composer@2.4(hal 接口)的 hal 服务进程。

本地 Android 设备的 composer service 进程是 vendor.qti.hardware.display.composer-service。

yudi:/system_ext/etc/rpms # lshal|grep graphic
DM,FC Y android.hardware.graphics.allocator@4.0::IAllocator/default                                            0/4        1382
DM,FC Y android.hardware.graphics.composer@2.1::IComposer/default                                              0/3        1387
DM,FC Y android.hardware.graphics.composer@2.2::IComposer/default                                              0/3        1387
DM,FC Y android.hardware.graphics.composer@2.3::IComposer/default                                              0/3        1387
DM,FC Y android.hardware.graphics.composer@2.4::IComposer/default                                              0/3        1387
X     ? android.hardware.graphics.mapper@4.0::I*/* (/vendor/lib/hw/) (-qti-display)   N/A        N/A    1245 3994
X     ? android.hardware.graphics.mapper@4.0::I*/* (/vendor/lib64/hw/) (-qti-display) N/A        N/A    1244 1373 1387 1599 2208 3865 3959 4067 4085 4103 4112 4123 4128 4166 4374 4399 4407 4518 4629 4861 4877 4905 4923 4948 4979 4995 5203 5245 5268 5312 5327 5385 5441 5546 5650 5763 5790 5813 5883 5927 6002 6048 6131 6139 6268 6296 6335 6385 6407 6430 6631 6836 6983 7024 7062 7105 7181 7334yudi:/system_ext/etc/rpms # ps -A|grep 1387
system        1387     1   11534632   8952 binder_wait_for_work 0 S vendor.qti.hardware.display.composer-service

composer service 是 SurfaceFlinger 和显示控制器之间的中间层,用于提供标准化的接口,使 SurfaceFlinger 不必直接与特定的显示控制器硬件通信。它作为一个服务,通过 HIDL 接口,使得图形合成与显示控制器的具体实现分离开来。

surfaceflinger、composer service 和显示控制器的具体关系为

  • SurfaceFlinger:是 Android 系统中的显示服务器,负责

    • 合成来自不同应用程序的图形内容,生成最终的显示帧

    • 接收来自 Framework 的显示设置,选择显示模式,选择应用刷新率

  • composer service:提供了与显示控制器交互的标准接口,包括

    • 发送帧

    • 设置显示模式

  • 显示控制器:硬件单元,负责接收图像数据,并将其显示在屏幕上。

显示的工作流程

  1. SurfaceFlinger 合成帧:SurfaceFlinger 将来自不同应用的图形内容合成为最终的显示帧。

  2. 通过 composer service 发送帧:SurfaceFlinger 通过 composer service 接口将合成的帧发送到显示控制器。

  3. 显示控制器处理帧:显示控制器接收帧数据,并生成必要的信号将其显示在屏幕上。

设置的工作流程

  1. SurfaceFlinger 接收来自 Framework 的显示设置,包括默认的 display mode(base mode)、屏幕刷新率范围、应用刷新率范围、分辨率等,根据这些显示设置选择显示模式

  2. 通过 composer service 设置显示模式

  3. 显示控制器应用显示模式,比如修改屏幕分辨率、刷新率等

这里顺便说明下 composer service 和硬件合成器(hardware composer 或 hwcomposer)的关系。

在 Android 系统中,SurfaceFlinger 有硬件合成和软件合成的区别。我们需要澄清 hwcomposer 和 composer service 的角色,以及硬件合成和软件合成的概念。

SurfaceFlinger 和合成方式:

  1. 软件合成:

    1. 在软件合成模式下,SurfaceFlinger 使用 GPU 或 CPU 来合成各个图层。

    2. 这种方式虽然灵活,但可能会消耗更多的 CPU/GPU 资源。

  2. 硬件合成:

    1. 在硬件合成模式下,SurfaceFlinger 利用专门的显示控制器硬件(Hardware Composer)来合成图层。

    2. 这种方式更高效,因为专用硬件可以更快地处理图层合成,并减少对 CPU/GPU 的依赖,从而提高性能和省电。

合成的工作流程:

  1. SurfaceFlinger 负责管理图层,并决定是使用硬件合成还是软件合成。

  2. 硬件合成:

    1. 如果选择硬件合成,SurfaceFlinger 通过 composer service 接口(如 android.hardware.graphics.composer@2.4)与硬件合成器进行通信,利用硬件加速功能进行图层合成,并生成最终的显示帧。

  3. 软件合成:

    1. 如果选择软件合成,SurfaceFlinger 使用 GPU 或 CPU 进行图层合成,然后将合成后的图像帧发送到显示控制器。

Hardware Composer (hwcomposer)

  • Hardware Composer (hwcomposer) 即硬件合成器,硬件合成器可以理解为是显示控制器的一个功能模块。在许多现代显示系统中,它们通常被集成在同一个芯片或模块中,但也可以是分开的组件。无论哪种情况,它们的工作流程是紧密协作的,硬件合成器负责图层合成,显示控制器负责最终的图像输出

  • hwcomposer 也可以是指硬件合成器的硬件抽象层组件,专门负责与显示硬件进行交互。它能够直接利用显示硬件的合成功能,将多个图层高效地合成为一个最终的显示帧。随着 Android 版本的更新,hwcomposer 也经历了多个版本,从早期的 C 接口到后来的 HIDL 接口(如 android.hardware.graphics.composer@2.4)

composer service

  • composer service 是一个实现了 HIDL 接口(如 android.hardware.graphics.composer@2.4)的服务,作为 SurfaceFlinger 与 hwcomposer 之间的中间层。

本文主要目的是介绍 Framework 的刷新率设置策略,不关注显示控制器的硬件实现和 composer service 以及 SurfaceFlinger 的实现细节。

(SurfaceFlinger 内部的实现细节以后有时间再专门介绍)

不过要理解 Framework 的刷新率设置策略,有必要先了解 SurfaceFlinger 设置 display mode 和应用刷新率的基本逻辑。

有 3 个参数会影响 SurfaceFlinger 设置 display mode 和应用刷新率

  1. base mode(default mode):表示默认的 display mode,即如果没有合适的 display mode,就会选择 base mode

  2. primary range:表示屏幕刷新率范围,即目标 display mode 的屏幕刷新率应该在这个范围内

  3. app range:表示应用刷新率范围,即目标 display mode 的屏幕刷新率的某个除数应该在这个范围内;将这个除数作为应用刷新率

SurfaceFlinger 定义了一个 display properties 结构,包含这 3 个参数,并且提供了设置 display properties 的接口。

当 Framework 调用接口设置 display porperties 时,SurfaceFlinger 就会根据这几个参数,选择合适的 display mode 和应用刷新率。

Framework 有一套更加复杂的刷新率管理策略,但最终会将这些策略转换成一个 display properties 结构设置到 SurfaceFlinger。

4. Framework 策略

4.1基本架构

Framework 刷新率策略的核心代码包括

  1. frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java

  2. frameworks/base/services/core/java/com/android/server/display/mode/Vote.java

  3. frameworks/base/services/core/java/com/android/server/display/mode/VotesStorage.java

Vote(投票、选举)对象表示刷新率的策略。

Vote 包含 1.分辨率(长、宽)2.刷新率范围(包括 "屏幕刷新率范围" 和 "应用刷新率范围") 3.是否禁止动态刷新率 4. base mode 刷新率这几个参数。

VotesStorage 包含一个 Votes map。map 的 key 表示 Vote 的优先级。

一共有 15 个优先级 0~14,值越大优先级越高。

DisplayModeDirector 负责更新 VotesStorage,计算目标 display properties 结构并设置到 SurfaceFlinger。

当设置 "默认刷新率"、"应用刷新率" 时,DisplayModeDirector 更新 VotesStorage,并根据 VotesStorage 中的所有策略计算出目标 base mode、屏幕刷新率范围、应用刷新率范围, 封装到 display properties 结构并设置到 SurfaceFlinger。

下面会在分析刷新率设置流程时,具体介绍 Vote、VotesStorage、DisplayModeDirector 的作用。

4.2 刷新率设置项的定义

在分析刷新率设置流程之前,为了避免混淆,需要先解释各种 xxx 刷新率设置项的定义。

  • 最低刷新率:由 system settings "min_refresh_rate" 设置,缺省为 0

  • 默认刷新率:优先由 /product/etc/displayconfig/ 或 /vendor/etc/displayconfig/ 下的配置文件设置,其次由 xml 中的 R.integer.config_defaultRefreshRate 设置,缺省为 60

  • 默认的用户设置刷新率:优先由 device_config <"display_manager", "peak_refresh_rate_default"> 设置,其次由 /product/etc/displayconfig/ 或 /vendor/etc/displayconfig/ 下的配置文件设置,最次由 xml 中的 R.integer.config_defaultPeakRefreshRate 设置,缺省为 0

  • 用户设置的刷新率:由 system settings "peak_refresh_rate" 设置,缺省为 "默认的用户设置刷新率"

  • 应用刷新率:由应用窗口属性设置。

  • 应用期望的 base mode 刷新率:由应用窗口属性设置。

以上这些设置项实际上都是对 "应用刷新率"(而非 "屏幕刷新率")的限制。

这些 xxx 刷新率设置项会影响不同的 Votes 策略。

  0 --- 默认策略,限制应用刷新率在 [0,默认刷新率] 之间变化

  3 --- 用户(user)设置的最低策略,限制应用刷新率的最低值,即应用刷新率在 [最低刷新率,正无限大] 之间变化

  4 --- 应用(app)设置的策略,限制应用刷新率范围在 [应用刷新率min应用刷新率max] 之间变化

  7 --- 用户(user)设置的策略,限制应用刷新率在 [0, max(用户设置的刷新率, 最低刷新率)] 之间变化

4.2.1 最低刷新率

由 system settings "min_refresh_rate" 设置,缺省为 0。

略。

4.2.2 默认刷新率 & 默认的用户设置刷新率

4.2.2.1 设置入口

默认刷新率,对应变量 DisplayModeDirector$SettingsObserver.mDefaultRefreshRate

默认的用户设置刷新率,对应变量 DisplayModeDirector$SettingsObserver.mDefaultPeakRefreshRate。

设置默认刷新率(mDefaultRefreshRate 变量)和默认的用户设置刷新率(mDefaultPeakRefreshRate 变量)的方法都是 SettingsObserver.setRefreshRates。

SettingsObserver.setRefreshRates 方法设置默认刷新率,以及默认的用户设置刷新率。

  • 入参 DisplayDeviceConfig displayDeviceConfig 表示 /vendor/etc/displayconfig/ 以及 /product/etc/displayconfig/ 的显示配置;

  • 入参 boolean attemptLoadingFromDeviceConfig 表示是否使用 device_config 设置 "默认的用户设置刷新率"

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java1067      @VisibleForTesting
1068      final class SettingsObserver extends ContentObserver {
...
1092  
1093          /**
1094           * This is used to update the refresh rate configs from the DeviceConfig, which
1095           * if missing from DisplayDeviceConfig, and finally fallback to config.xml.
1096           */
1097          public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig,
1098                  boolean attemptLoadingFromDeviceConfig) {// 设置 "默认的用户设置刷新率"
1099              setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig);// 设置 "默认刷新率"
1100              mDefaultRefreshRate =
1101                      (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
1102                          R.integer.config_defaultRefreshRate)
1103                          : (float) displayDeviceConfig.getDefaultRefreshRate();
1104          }
1105  ...
  • 设置 "默认刷新率"

入参 DisplayDeviceConfig displayDeviceConfig 不为 null 时,使用 displayDeviceConfig 的配置来设置 "默认刷新率";

否则,还是使用 R.integer.config_defaultRefreshRate 来设置 "默认刷新率"。

  • 设置 "默认的用户设置刷新率"

setDefaultPeakRefreshRate 方法,设置 "默认的用户设置刷新率"。

入参 boolean attemptLoadingFromDeviceConfig 为 true 时,将 "默认的用户设置刷新率" 设置为 device_config <"display_manager", "peak_refresh_rate_default"> 的值;

否则,如果入参 DisplayDeviceConfig displayDeviceConfig 不为 null,则使用 displayDeviceConfig 的配置来设置 "默认的用户设置刷新率";

否则,还是使用 R.integer.config_defaultPeakRefreshRate 来设置 "默认的用户设置刷新率"。

或 DisplayDeviceConfig 更新 mDefaultPeakRefreshRate。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java1173          private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig,
1174                  boolean attemptLoadingFromDeviceConfig) {
1175              Float defaultPeakRefreshRate = null;
1176  // 使用 device_config <"display_manager",  "peak_refresh_rate_default"> 的值设置 defaultPeakRefreshRate
1177              if (attemptLoadingFromDeviceConfig) {
1178                  try {
1179                      defaultPeakRefreshRate =
1180                          mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
1181                  } catch (Exception exception) {
1182                      // Do nothing
1183                  }
1184              }// 优先使用 displayconfig 配置文件的值设置 defaultPeakRefreshRate,// 其次使用 config.xml 的配置设置 defaultPeakRefreshRate
1185              if (defaultPeakRefreshRate == null) {
1186                  defaultPeakRefreshRate =
1187                          (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger(
1188                                  R.integer.config_defaultPeakRefreshRate)
1189                                  : (float) displayDeviceConfig.getDefaultPeakRefreshRate();
1190              }
1191              mDefaultPeakRefreshRate = defaultPeakRefreshRate;
1192          }
4.2.2.2 设置场景

调用 SettingsObserver.setRefreshRates 的场景有两处。

  • 其一是设备启动时,构造 SettingsObserver 对象。

此时,调用 SettingsObserver.setRefreshRates 方法, 由于入参 DisplayDeviceConfig displayDeviceConfig 传入的是 null,入参 boolean attemptLoadingFromDeviceConfig 传入的是 false,因此将

设置 mDefaultRefreshRate 的值为 R.integer.config_defaultRefreshRate;

设置 mDefaultPeakRefreshRate 的值为 R.integer.config_defaultPeakRefreshRate。

从注释来看,之所以在 SettingsObserver 对象构造时不用 DeviceConfig 设置 mDefaultRefreshRate 的值,是出于性能考虑。

即读 DeviceConfig 会产生密集的 IO 操作,导致 DMS 启动变慢。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java1067      @VisibleForTesting
1068      final class SettingsObserver extends ContentObserver {
1069          private final Uri mPeakRefreshRateSetting =
1070                  Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
1071          private final Uri mMinRefreshRateSetting =
1072                  Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE);
1073          private final Uri mLowPowerModeSetting =
1074                  Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
1075          private final Uri mMatchContentFrameRateSetting =
1076                  Settings.Secure.getUriFor(Settings.Secure.MATCH_CONTENT_FRAME_RATE);
1077  
1078          private final Context mContext;
1079          private float mDefaultPeakRefreshRate;
1080          private float mDefaultRefreshRate;
1081  
1082          SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
1083              super(handler);
1084              mContext = context;
1085              // We don't want to load from the DeviceConfig while constructing since this leads to
1086              // a spike in the latency of DisplayManagerService startup. This happens because
1087              // reading from the DeviceConfig is an intensive IO operation and having it in the
1088              // startup phase where we thrive to keep the latency very low has significant impact.
1089              setRefreshRates(/* displayDeviceConfig= */ null,
1090                  /* attemptLoadingFromDeviceConfig= */ false);
1091          }
...
  • 其二是主显示设备(主屏幕)准备好(添加到 DMS)时。此时再次调用 SettingsObserver.setRefreshRates 方法,通过传入的 DisplayDeviceConfig 更新 mDefaultRefreshRate 和 mDefaultPeakRefreshRate 的值,入参 boolean attemptLoadingFromDeviceConfig 传入 true。

    • Display.DEFAULT_DISPLAY(0)表示主显示设备

    • DisplayModeDirector.defaultDisplayDeviceUpdated 调用 SettingsObserver.setRefreshRates

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java3265      private final class LogicalDisplayListener implements LogicalDisplayMapper.Listener {
3266          @Override
3267          public void onLogicalDisplayEventLocked(LogicalDisplay display, int event) {
3268              switch (event) {
3269                  case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED:
3270                      handleLogicalDisplayAddedLocked(display);
3271                      break;
... ...1783      private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
1784          final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
1785          final int displayId = display.getDisplayIdLocked();
1786          final boolean isDefault = displayId == Display.DEFAULT_DISPLAY;
1787          configureColorModeLocked(display, device);
1788          if (!mAreUserDisabledHdrTypesAllowed) {
1789              display.setUserDisabledHdrTypes(mUserDisabledHdrTypes);
1790          }// 注意:只有主显示设备(主屏幕)添加时才会更新 mDefaultRefreshRate 和 mDefaultPeakRefreshRate。// (1)
1791          if (isDefault) {
1792              notifyDefaultDisplayDeviceUpdated(display);
1793              recordStableDisplayStatsIfNeededLocked(display);
1794              recordTopInsetLocked(display);
1795          }
... ...1946      private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {// (2)
1947          mDisplayModeDirector.defaultDisplayDeviceUpdated(display.getPrimaryDisplayDeviceLocked()
1948                  .mDisplayDeviceConfig);
1949      }
// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java671      /**
672       * Called when the underlying display device of the default display is changed.
673       * Some data in this class relates to the physical display of the device, and so we need to
674       * reload the configurations based on this.
675       * E.g. the brightness sensors and refresh rate capabilities depend on the physical display
676       * device that is being used, so will be reloaded.
677       *
678       * @param displayDeviceConfig configurations relating to the underlying display device.
679       */
680      public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
681          synchronized (mLock) {
682              mDefaultDisplayDeviceConfig = displayDeviceConfig;// (3)
683              mSettingsObserver.setRefreshRates(displayDeviceConfig,
684                  /* attemptLoadingFromDeviceConfig= */ true);
685              mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
686                  /* attemptLoadingFromDeviceConfig= */ true);
687              mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
688              mHbmObserver.setupHdrRefreshRates(displayDeviceConfig);
689          }
690      }

4.2.2.3 DisplayDeviceConfig

首先要注意,DisplayDeviceConfig 不是 DeviceConfig,跟 DeviceConfig 完全没有关系。

DisplayDeviceConfig 即 DisplayDevice 的 Config!

DisplayDeviceConfig 由 DisplayDevice 创建。

调用 DisplayDevice.getDisplayDeviceConfig 方法时,如果 DisplayDeviceConfig 对象(DisplayDeviceConfig.mDisplayDeviceConfig)未创建,则会创建 DisplayDeviceConfig 实例。

DisplayDevice 创建 DisplayDeviceConfig 实例调用的是 DisplayDeviceConfig.create(mContext, false) 方法。

// frameworks/base/services/core/java/com/android/server/display/DisplayDevice.java86      /*
87       * Gets the DisplayDeviceConfig for this DisplayDevice.
88       *
89       * @return The DisplayDeviceConfig; {@code null} if not overridden.
90       */
91      public DisplayDeviceConfig getDisplayDeviceConfig() {
92          if (mDisplayDeviceConfig == null) {
93              mDisplayDeviceConfig = loadDisplayDeviceConfig();
94          }
95          return mDisplayDeviceConfig;
96      }
97  
...
391      private DisplayDeviceConfig loadDisplayDeviceConfig() {
392          return DisplayDeviceConfig.create(mContext, false);
393      }

但是 DisplayDevice 是抽象类,其实现类 LocalDisplayDevice 重写了 getDisplayDeviceConfig 方法。

LocalDisplayDevice 创建 DisplayDeviceConfig 实例调用的是 DisplayDeviceConfig.create(context, mPhysicalDisplayId, mIsFirstDisplay) 方法。

// frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java191      private final class LocalDisplayDevice extends DisplayDevice {
...
473          @Override
474          public DisplayDeviceConfig getDisplayDeviceConfig() {
475              if (mDisplayDeviceConfig == null) {
476                  loadDisplayDeviceConfig();
477              }
478              return mDisplayDeviceConfig;
479          }
...
496          private void loadDisplayDeviceConfig() {
497              // Load display device config
498              final Context context = getOverlayContext();
499              mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId,
500                      mIsFirstDisplay);
501  
502              // Load brightness HWC quirk
503              mBacklightAdapter.setForceSurfaceControl(mDisplayDeviceConfig.hasQuirk(
504                      DisplayDeviceConfig.QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC));
505          }

DisplayDeviceConfig 有 3 种构造方式。

其一是调用 DisplayDeviceConfig.create(Context context, boolean useConfigXml),入参 useConfigXml 传 true,

其二还是调用 DisplayDeviceConfig.create(Context context, boolean useConfigXml),但入参 useConfigXml 传 false。

  • 入参 useConfigXml 为 true 时,调用 getConfigFromGlobalXml 方法。

  • 入参 useConfigXml 为 false 时,调用 getConfigFromPmValues 方法。

// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java725      /**
726       * Creates an instance using global values since no display device config xml exists. Uses
727       * values from config or PowerManager.
728       *
729       * @param context      The context from which the DisplayDeviceConfig is to be constructed.
730       * @param useConfigXml A flag indicating if values are to be loaded from the configuration file,
731       *                     or the default values.
732       * @return A configuration instance.
733       */
734      public static DisplayDeviceConfig create(Context context, boolean useConfigXml) {
735          final DisplayDeviceConfig config;
736          if (useConfigXml) {
737              config = getConfigFromGlobalXml(context);
738          } else {
739              config = getConfigFromPmValues(context);
740          }
741          return config;
742      }

useConfigXml 为 true 时。

getConfigFromGlobalXml 方法用 config.xml 中的配置来初始化 DisplayDeviceConfig 属性,包括 "默认刷新率" 和 "默认的用户设置刷新率"。

此时,

默认刷新率(即 mDefaultRefreshRate)由 R.integer.config_defaultRefreshRate 设置;

默认的用户设置刷新率(即 mDefaultPeakRefreshRate)由 R.integer.config_defaultPeakRefreshRate 设置。

// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java1645      private static DisplayDeviceConfig getConfigFromGlobalXml(Context context) {
1646          DisplayDeviceConfig config = new DisplayDeviceConfig(context);
1647          config.initFromGlobalXml(); // (1)
1648          return config;
1649      }
// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java
1701      private void initFromGlobalXml() {
1702          // If no ddc exists, use config.xml
1703          loadBrightnessDefaultFromConfigXml();
1704          loadBrightnessConstraintsFromConfigXml();
1705          loadBrightnessMapFromConfigXml();
1706          loadBrightnessRampsFromConfigXml();
1707          loadAmbientLightSensorFromConfigXml();
1708          loadBrightnessChangeThresholdsFromXml();
1709          setProxSensorUnspecified();
1710          loadAutoBrightnessConfigsFromConfigXml();
1711          loadAutoBrightnessAvailableFromConfigXml();
1712          loadRefreshRateSetting(null); // (2)
1713          mLoadedFrom = "<config.xml>";
1714      }
// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java1990      private void loadRefreshRateSetting(DisplayConfiguration config) {
1991          final RefreshRateConfigs refreshRateConfigs =
1992                  (config == null) ? null : config.getRefreshRate();
1993          BlockingZoneConfig lowerBlockingZoneConfig =
1994                  (refreshRateConfigs == null) ? null
1995                          : refreshRateConfigs.getLowerBlockingZoneConfigs();
1996          BlockingZoneConfig higherBlockingZoneConfig =
1997                  (refreshRateConfigs == null) ? null
1998                          : refreshRateConfigs.getHigherBlockingZoneConfigs();
1999          loadPeakDefaultRefreshRate(refreshRateConfigs); // (3)
2000          loadDefaultRefreshRate(refreshRateConfigs); // (4)
2001          loadDefaultRefreshRateInHbm(refreshRateConfigs);
2002          loadLowerRefreshRateBlockingZones(lowerBlockingZoneConfig);
2003          loadHigherRefreshRateBlockingZones(higherBlockingZoneConfig);
2004          loadRefreshRateZoneProfiles(refreshRateConfigs);
2005      }
2006  
2007      private void loadPeakDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
2008          if (refreshRateConfigs == null || refreshRateConfigs.getDefaultPeakRefreshRate() == null) {
2009              mDefaultPeakRefreshRate = mContext.getResources().getInteger(
2010                  R.integer.config_defaultPeakRefreshRate);
2011          } else {
2012              mDefaultPeakRefreshRate =
2013                  refreshRateConfigs.getDefaultPeakRefreshRate().intValue();
2014          }
2015      }
2016  
2017      private void loadDefaultRefreshRate(RefreshRateConfigs refreshRateConfigs) {
2018          if (refreshRateConfigs == null || refreshRateConfigs.getDefaultRefreshRate() == null) {
2019              mDefaultRefreshRate = mContext.getResources().getInteger(
2020                  R.integer.config_defaultRefreshRate);
2021          } else {
2022              mDefaultRefreshRate =
2023                  refreshRateConfigs.getDefaultRefreshRate().intValue();
2024          }
2025      }
...

useConfigXml 为 false 时。

getConfigFromPmValues 方法用 PowerManager 中写死的常量值来初始化 DisplayDeviceConfig 属性,主要初始化了一些背光相关的属性,没有设置默认刷新率(即 mDefaultRefreshRate)和默认的用户设置刷新率(即 mDefaultPeakRefreshRate)。

// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java1651      private static DisplayDeviceConfig getConfigFromPmValues(Context context) {
1652          DisplayDeviceConfig config = new DisplayDeviceConfig(context);
1653          config.initFromDefaultValues(); // (1)
1654          return config;
1655      }
// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java1716      private void initFromDefaultValues() {
1717          // Set all to basic values
1718          mLoadedFrom = "Static values";
1719          mBacklightMinimum = PowerManager.BRIGHTNESS_MIN;
1720          mBacklightMaximum = PowerManager.BRIGHTNESS_MAX;
1721          mBrightnessDefault = BRIGHTNESS_DEFAULT;
1722          mBrightnessRampFastDecrease = PowerManager.BRIGHTNESS_MAX;
1723          mBrightnessRampFastIncrease = PowerManager.BRIGHTNESS_MAX;
1724          mBrightnessRampSlowDecrease = PowerManager.BRIGHTNESS_MAX;
1725          mBrightnessRampSlowIncrease = PowerManager.BRIGHTNESS_MAX;
1726          mBrightnessRampDecreaseMaxMillis = 0;
1727          mBrightnessRampIncreaseMaxMillis = 0;
1728          setSimpleMappingStrategyValues();
1729          loadAmbientLightSensorFromConfigXml();
1730          setProxSensorUnspecified();
1731          loadAutoBrightnessAvailableFromConfigXml();
1732      }

其三是调用 DisplayDeviceConfig.create(Context context, long physicalDisplayId, boolean isFirstDisplay) 方法(*)。

系统中的 DisplayDeviceConfig 实例默认是通过这种方式构造的(参见上文中 LocalDisplayDevice 的 getDisplayDeviceConfig 方法),因此我们需要重点关心。

// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java704      /**
705       * Creates an instance for the specified display. Tries to find a file with identifier in the
706       * following priority order:
707       * <ol>
708       *     <li>physicalDisplayId</li>
709       *     <li>physicalDisplayId without a stable flag (old system)</li>
710       *     <li>portId</li>
711       * </ol>
712       *
713       * @param physicalDisplayId The display ID for which to load the configuration.
714       * @return A configuration instance for the specified display.
715       */
716      public static DisplayDeviceConfig create(Context context, long physicalDisplayId,
717              boolean isFirstDisplay) {// (1)
718          final DisplayDeviceConfig config = createWithoutDefaultValues(context, physicalDisplayId,
719                  isFirstDisplay);
720  
721          config.copyUninitializedValuesFromSecondaryConfig(loadDefaultConfigurationXml(context));
722          return config;
723      }

createWithoutDefaultValues 方法创建 DisplayDeviceConfig 实例,解析相关的配置文件,初始化 DisplayDeviceConfig 的属性。

(copyUninitializedValuesFromSecondaryConfig 方法与刷新率没什么关系,这里可以忽略。)

  • 配置文件所在目录的优先级

优先加载 product 目录下的配置文件(/product/etc/displayconfig);

product 目录下没有配置文件时,再加载 vendor 目录下的配置文件(/vendor/etc/displayconfig)。

  • 配置文件的优先级

优先加载目标屏幕 id 对应的配置,即 display_id_%d.xml 文件,%d 表示 id 值;

其次加载目标屏幕 id 对应的配置,即 display_%d.xml 文件,%d 表示不带 stable flag 的 id 值;

最次加载目标屏幕端口号(port)对应的配置,即 display_port_%d.xml 文件,%d 表示端口号。

// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java744      private static DisplayDeviceConfig createWithoutDefaultValues(Context context,
745              long physicalDisplayId, boolean isFirstDisplay) {
746          DisplayDeviceConfig config;
747  // 加载 product 目录下的配置
748          config = loadConfigFromDirectory(context, Environment.getProductDirectory(),
749                  physicalDisplayId);
750          if (config != null) {
751              return config;
752          }
753  // 加载 vendor 目录下的配置
754          config = loadConfigFromDirectory(context, Environment.getVendorDirectory(),
755                  physicalDisplayId);
756          if (config != null) {
757              return config;
758          }
759  
760          // If no config can be loaded from any ddc xml at all,
761          // prepare a whole config using the global config.xml.
762          // Guaranteed not null
763          return create(context, isFirstDisplay);
764      }
444      private static final String ETC_DIR = "etc";
445      private static final String DISPLAY_CONFIG_DIR = "displayconfig";
446      private static final String CONFIG_FILE_FORMAT = "display_%s.xml";
447      private static final String DEFAULT_CONFIG_FILE = "default.xml";
448      private static final String DEFAULT_CONFIG_FILE_WITH_UIMODE_FORMAT = "default_%s.xml";
449      private static final String PORT_SUFFIX_FORMAT = "port_%d";
450      private static final String STABLE_ID_SUFFIX_FORMAT = "id_%d";
451      private static final String NO_SUFFIX_FORMAT = "%d";
452      private static final long STABLE_FLAG = 1L << 62;
...
815      private static DisplayDeviceConfig loadConfigFromDirectory(Context context,
816              File baseDirectory, long physicalDisplayId) {
817          DisplayDeviceConfig config;// 加载目标屏幕 id 对应的配置,即 display_id_%d.xml 文件,%d 表示 id 值
818          // Create config using filename from physical ID (including "stable" bit).
819          config = getConfigFromSuffix(context, baseDirectory, STABLE_ID_SUFFIX_FORMAT,
820                  physicalDisplayId);
821          if (config != null) {
822              return config;
823          }
824  // 加载目标屏幕 id 对应的配置,即 display_%d.xml 文件,%d 表示不带 stable flag 的 id 值。// Android 用 id 的高 63 位(从 0 开始第 62 位)表示屏幕 id 是否固定,即 stable flag。
825          // Create config using filename from physical ID (excluding "stable" bit).
826          final long withoutStableFlag = physicalDisplayId & ~STABLE_FLAG;
827          config = getConfigFromSuffix(context, baseDirectory, NO_SUFFIX_FORMAT, withoutStableFlag);
828          if (config != null) {
829              return config;
830          }
831  // 加载目标屏幕端口号(port)对应的配置,即 display_port_%d.xml 文件,%d 表示端口号
832          // Create config using filename from port ID.
833          final DisplayAddress.Physical physicalAddress =
834                  DisplayAddress.fromPhysicalDisplayId(physicalDisplayId);
835          int port = physicalAddress.getPort();
836          config = getConfigFromSuffix(context, baseDirectory, PORT_SUFFIX_FORMAT, port);
837          return config;
838      }
1631      private static DisplayDeviceConfig getConfigFromSuffix(Context context, File baseDirectory,
1632              String suffixFormat, long idNumber) {
1633  
1634          final String suffix = String.format(Locale.ROOT, suffixFormat, idNumber);
1635          final String filename = String.format(Locale.ROOT, CONFIG_FILE_FORMAT, suffix);
1636          final File filePath = Environment.buildPath(
1637                  baseDirectory, ETC_DIR, DISPLAY_CONFIG_DIR, filename);
1638          final DisplayDeviceConfig config = new DisplayDeviceConfig(context);// 读取配置文件,初始化 DisplayDeviceConfig 属性
1639          if (config.initFromFile(filePath)) {
1640              return config;
1641          }
1642          return null;
1643      }
1657      @VisibleForTesting
1658      boolean initFromFile(File configFile) {
1659          if (!configFile.exists()) {
1660              // Display configuration files aren't required to exist.
1661              return false;
1662          }
1663  
1664          if (!configFile.isFile()) {
1665              Slog.e(TAG, "Display configuration is not a file: " + configFile + ", skipping");
1666              return false;
1667          }
1668  
1669          try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) {
1670              final DisplayConfiguration config = XmlParser.read(in);
1671              if (config != null) {
1672                  loadName(config);
1673                  loadDensityMapping(config);
1674                  loadBrightnessDefaultFromDdcXml(config);
1675                  loadBrightnessConstraintsFromConfigXml();
1676                  loadBrightnessMap(config);
1677                  loadThermalThrottlingConfig(config);
1678                  loadHighBrightnessModeData(config);
1679                  loadQuirks(config);
1680                  loadBrightnessRamps(config);
1681                  loadAmbientLightSensorFromDdc(config);
1682                  loadScreenOffBrightnessSensorFromDdc(config);
1683                  loadProxSensorFromDdc(config);
1684                  loadAmbientHorizonFromDdc(config);
1685                  loadBrightnessChangeThresholds(config);
1686                  loadAutoBrightnessConfigValues(config);// 初始化刷新率相关的配置
1687                  loadRefreshRateSetting(config);
1688                  loadScreenOffBrightnessSensorValueToLuxFromDdc(config);
1689                  loadUsiVersion(config);
1690              } else {
1691                  Slog.w(TAG, "DisplayDeviceConfig file is null");
1692              }
1693          } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) {
1694              Slog.e(TAG, "Encountered an error while reading/parsing display config file: "
1695                      + configFile, e);
1696          }
1697          mLoadedFrom = configFile.toString();
1698          return true;
1699      }

这里又回到了 loadRefreshRateSetting 方法。

如果配置文件中包含了默认刷新率、默认的用户设置刷新率配置,则将默认刷新率(即 mDefaultRefreshRate)、默认的用户设置刷新率(即 mDefaultPeakRefreshRate)设置为配置文件中的值。

否则,使用 config.xml 中的配置,即 mDefaultRefreshRate 由 R.integer.config_defaultRefreshRate 设置, mDefaultPeakRefreshRate 由 R.integer.config_defaultPeakRefreshRate 设置。

如果配置文件和 config.xml(R.integer.config_defaultRefreshRate、R.integer.config_defaultPeakRefreshRate)都没有配置默认刷新率、默认的用户设置刷新率,则使用缺省值。

// frameworks/base/services/core/java/com/android/server/display/DisplayDeviceConfig.java453      private static final int DEFAULT_PEAK_REFRESH_RATE = 0;
454      private static final int DEFAULT_REFRESH_RATE = 60;
...
622      /**
623       * The default peak refresh rate for a given device. This value prevents the framework from
624       * using higher refresh rates, even if display modes with higher refresh rates are available
625       * from hardware composer. Only has an effect if the value is non-zero.
626       */
627      private int mDefaultPeakRefreshRate = DEFAULT_PEAK_REFRESH_RATE;
628  
629      /**
630       * The default refresh rate for a given device. This value sets the higher default
631       * refresh rate. If the hardware composer on the device supports display modes with
632       * a higher refresh rate than the default value specified here, the framework may use those
633       * higher refresh rate modes if an app chooses one by setting preferredDisplayModeId or calling
634       * setFrameRate(). We have historically allowed fallback to mDefaultPeakRefreshRate if
635       * mDefaultRefreshRate is set to 0, but this is not supported anymore.
636       */
637      private int mDefaultRefreshRate = DEFAULT_REFRESH_RATE;

本地 Android 设备没有 /<product | vendor>/etc/displayconfig。

yudi:/product # find -name "displayconfig"yudi:/vendor # find -name "displayconfig"

"默认的用户设置刷新率" 可以通过 device_config <"display_manager", "peak_refresh_rate_default"> 设置,因此也可以通过手动设置 device_config <"display_manager", "peak_refresh_rate_default"> 来调用 "刷新率更新和应用流程"。

4.2.3 用户设置的刷新率

由 system settings "peak_refresh_rate" 设置。

即可以通过 adb shell settings put system peak_refresh_rate <value> 设置刷新率。

"用户设置的刷新率" 在 Framework 中没有对应的全局变量,设置 "用户设置的刷新率" 会调用 "刷新率更新和应用流程"。

4.2.4 应用刷新率 & 应用期望的 base mode 刷新率

应用刷新率 & 应用期望的 base mode 刷新率都是通过应用的窗口属性来设置的。

4.3 刷新率更新 & 应用流程

4.3.1 入口

刷新率更新 & 应用的主要入口包括

  1. 当 "默认的用户设置刷新率" 或 "用户设置的刷新率" 变化时~

    1. "默认的用户设置刷新率" 可以通过 device_config <"display_manager", "peak_refresh_rate_default"> 修改

    2. "用户设置的刷新率" 可以通过 system settings "peak_refresh_rate" 修改

  2. 窗口切换,且上层的窗口设置了 "期望的 base mode id"、"期望的 base mode 刷新率" 或 "应用刷新率范围" 属性时~

4.3.1.1 system settings "peak_refresh_rate"

DisplayModeDirector 初始化时,开始监听 system settings "peak_refresh_rate" 变化。

监听到 system settings "peak_refresh_rate" 变化时,调用 updateRefreshRateSettingLocked 方法更新(应用)刷新率。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java152      public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
153              @NonNull Injector injector) {
...// (1)
162          mSettingsObserver = new SettingsObserver(context, handler);
...
174      }176      /**
177       * Tells the DisplayModeDirector to update allowed votes and begin observing relevant system
178       * state.
179       *
180       * This has to be deferred because the object may be constructed before the rest of the system
181       * is ready.
182       */
183      public void start(SensorManager sensorManager) {// (2)
184          mSettingsObserver.observe();
185          mDisplayObserver.observe();
186          mBrightnessObserver.observe(sensorManager);
187          mSensorObserver.observe();
188          mHbmObserver.observe();
189          mSkinThermalStatusObserver.observe();
190          synchronized (mLock) {
191              // We may have a listener already registered before the call to start, so go ahead and
192              // notify them to pick up our newly initialized state.
193              notifyDesiredDisplayModeSpecsChangedLocked();
194          }
195      }1067      @VisibleForTesting
1068      final class SettingsObserver extends ContentObserver {
1069          private final Uri mPeakRefreshRateSetting =
1070                  Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
...
1106          public void observe() {
1107              final ContentResolver cr = mContext.getContentResolver();// (3)
1108              mInjector.registerPeakRefreshRateObserver(cr, this);
1109              cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
1110                      UserHandle.USER_SYSTEM);
1111              cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
1112                      UserHandle.USER_SYSTEM);
1113              cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/,
1114                      this);
1115  
1116              Float deviceConfigDefaultPeakRefresh =
1117                      mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
1118              if (deviceConfigDefaultPeakRefresh != null) {
1119                  mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh;
1120              }
1121  
1122              synchronized (mLock) {
1123                  updateRefreshRateSettingLocked();
1124                  updateLowPowerModeSettingLocked();
1125                  updateModeSwitchingTypeSettingLocked();
1126              }
1127          }
...
1207          private void updateRefreshRateSettingLocked() {
1208              final ContentResolver cr = mContext.getContentResolver();
1209              float minRefreshRate = Settings.System.getFloatForUser(cr,
1210                      Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());
1211              float peakRefreshRate = Settings.System.getFloatForUser(cr,
1212                      Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId());// (6)
1213              updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
1214          }
...
1149          @Override
1150          public void onChange(boolean selfChange, Uri uri, int userId) {
1151              synchronized (mLock) {// (5)
1152                  if (mPeakRefreshRateSetting.equals(uri)
1153                          || mMinRefreshRateSetting.equals(uri)) {
1154                      updateRefreshRateSettingLocked();
1155                  } else if (mLowPowerModeSetting.equals(uri)) {
1156                      updateLowPowerModeSettingLocked();
1157                  } else if (mMatchContentFrameRateSetting.equals(uri)) {
1158                      updateModeSwitchingTypeSettingLocked();
1159                  }
1160              }
1161          }
...
2876      @VisibleForTesting
2877      static class RealInjector implements Injector {
2847          Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
...
2890  
2891          @Override
2892          public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
2893                  @NonNull ContentObserver observer) {// (4)
2894              cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
2895                      observer, UserHandle.USER_SYSTEM);
2896          }
...

4.3.1.2 device_config <"display_manager", "peak_refresh_rate_default">

DisplayModeDirector 初始化时,创建 DeviceConfigDisplaySettings 对象开始监听 device_config <"display_manager", "peak_refresh_rate_default"> 变化。

监听到 device_config <"display_manager", "peak_refresh_rate_default"> 变化时,

通过 msg 通知 SettingsObserver 默认刷新率变化,调用 updateRefreshRateSettingLocked 方法更新(应用)刷新率。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java// DeviceConfigDisplaySettings:device_config 监听器
2676      private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
2677          public void startListening() {
...
2762          /*
2763           * Return null if no such property
2764           */
2765          public Float getDefaultPeakRefreshRate() {// (2)
2766              float defaultPeakRefreshRate = mDeviceConfig.getFloat(
2767                      DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
2768                      DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
2769  
2770              if (defaultPeakRefreshRate == -1) {
2771                  return null;
2772              }
2773              return defaultPeakRefreshRate;
2774          }
2775
2776          @Override
2777          public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {// (1)
2778              Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();// (3)
2779              mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
2780                      defaultPeakRefreshRate).sendToTarget();
...// DisplayModeDirectorHandler:DisplayModeDirector 的 Handler
886      private final class DisplayModeDirectorHandler extends Handler {
887          DisplayModeDirectorHandler(Looper looper) {
888              super(looper, null, true /*async*/);
889          }
...// (4)
924                  case MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED:
925                      Float defaultPeakRefreshRate = (Float) msg.obj;
926                      mSettingsObserver.onDeviceConfigDefaultPeakRefreshRateChanged(
927                              defaultPeakRefreshRate);
928                      break;
...// SettingsObserver:settings 监听器
1067      @VisibleForTesting
1068      final class SettingsObserver extends ContentObserver {
...
1136          public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) {
1137              synchronized (mLock) {// (5)
1138                  if (defaultPeakRefreshRate == null) {
1139                      setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig,
1140                              /* attemptLoadingFromDeviceConfig= */ false);
1141                      updateRefreshRateSettingLocked();
1142                  } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
1143                      mDefaultPeakRefreshRate = defaultPeakRefreshRate;
1144                      updateRefreshRateSettingLocked();
1145                  }
1146              }
1147          }

4.3.1.3 setDefaultRefreshRate 方法设置 "默认刷新率"(Just for test)

此方法只在 DisplayModeDirector 的单元测试代码中使用。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java1067      @VisibleForTesting
1068      final class SettingsObserver extends ContentObserver {
...
1129          public void setDefaultRefreshRate(float refreshRate) {
1130              synchronized (mLock) {
1131                  mDefaultRefreshRate = refreshRate;
1132                  updateRefreshRateSettingLocked();
1133              }
1134          }
...

4.3.1.4 窗口切换

窗口切换时,系统调用 DisplayContent.applySurfaceChangesTransaction 方法。

在 DisplayContent.applySurfaceChangesTransaction 方法中

  1. 遍历所有 windows,更新 mTmpApplySurfaceChangesTransactionState。mTmpApplySurfaceChangesTransactionState 保存目标 base mode id、目标 base mode 的刷新率、"应用刷新率 [min, max]" 信息。

  2. 调用 DMS.LocalService.setDisplayProperties 方法将 base mode 和 "应用刷新率 [min, max]" 更新到 VotesStorage,VotesStorage 更新时通过 VotesStorage.mListener 回调 DisplayModeDirector.notifyDesiredDisplayModeSpecsChangedLocked 方法将 base mode 和 "应用刷新率 [min, max]" 设置到 SurfaceFlinger。

// frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java4934      // TODO: Super unexpected long method that should be broken down...
4935      void applySurfaceChangesTransaction() {
4936          final WindowSurfacePlacer surfacePlacer = mWmService.mWindowPlacerLocked;
4937  
4938          beginHoldScreenUpdate();
4939  
4940          mTmpUpdateAllDrawn.clear();
4941  
4942          if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats("On entry to LockedInner",
4943                  pendingLayoutChanges);
4944  
4945          if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
4946              mWallpaperController.adjustWallpaperWindows();
4947          }
4948  
4949          if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {
4950              if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
4951              if (updateOrientation()) {
4952                  setLayoutNeeded();
4953                  sendNewConfiguration();
4954              }
4955          }
4956  
4957          if ((pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
4958              setLayoutNeeded();
4959          }
4960  
4961          // Perform a layout, if needed.
4962          performLayout(true /* initial */, false /* updateInputWindows */);
4963          pendingLayoutChanges = 0;
4964  
4965          Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyPostLayoutPolicy");
4966          try {
4967              mDisplayPolicy.beginPostLayoutPolicyLw();
4968              forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);
4969              mDisplayPolicy.finishPostLayoutPolicyLw();
4970          } finally {
4971              Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
4972          }
4973          mInsetsStateController.onPostLayout();
4974  
4975          mTmpApplySurfaceChangesTransactionState.reset();
4976  
4977          Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
4978          try {// 遍历所有 windows,对每个 window 调用 mApplySurfaceChangesTransaction 方法,// 更新 mTmpApplySurfaceChangesTransactionState
4979              forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
4980          } finally {
4981              Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
4982          }
4983          prepareSurfaces();
4984  
4985          // This should be called after the insets have been dispatched to clients and we have
4986          // committed finish drawing windows.
4987          mInsetsStateController.getImeSourceProvider().checkShowImePostLayout();
4988  
4989          mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent;
4990          if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) {// 设置 display properties
4991              mWmService.mDisplayManagerInternal.setDisplayProperties(mDisplayId,
4992                      mLastHasContent,
4993                      mTmpApplySurfaceChangesTransactionState.preferredRefreshRate,
4994                      mTmpApplySurfaceChangesTransactionState.preferredModeId,
4995                      mTmpApplySurfaceChangesTransactionState.preferredMinRefreshRate,
4996                      mTmpApplySurfaceChangesTransactionState.preferredMaxRefreshRate,
4997                      mTmpApplySurfaceChangesTransactionState.preferMinimalPostProcessing,
4998                      mTmpApplySurfaceChangesTransactionState.disableHdrConversion,
4999                      true /* inTraversal, must call performTraversalInTrans... below */);
5000          }
5001          // If the display now has content, or no longer has content, update recording.
5002          updateRecording();
5003  
5004          final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
5005          if (wallpaperVisible != mLastWallpaperVisible) {
5006              mLastWallpaperVisible = wallpaperVisible;
5007              mWmService.mWallpaperVisibilityListeners.notifyWallpaperVisibilityChanged(this);
5008          }
5009  
5010          while (!mTmpUpdateAllDrawn.isEmpty()) {
5011              final ActivityRecord activity = mTmpUpdateAllDrawn.removeLast();
5012              // See if any windows have been drawn, so they (and others associated with them)
5013              // can now be shown.
5014              activity.updateAllDrawn();
5015          }
5016  
5017          finishHoldScreenUpdate();
5018      }

DMS.LocalService.setDisplayProperties 方法。(VotesStorage 以及 VotesStorage.mListener 回调会在更后面分析)

-> DMS.setDisplayPropertiesInternal

-> DisplayModeDirector$AppRequestObserver.setAppRequest

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java4255      @VisibleForTesting
4256      final class LocalService extends DisplayManagerInternal {
4257  
...
4409          @Override
4410          public void setDisplayProperties(int displayId, boolean hasContent,
4411                  float requestedRefreshRate, int requestedMode, float requestedMinRefreshRate,
4412                  float requestedMaxRefreshRate, boolean requestedMinimalPostProcessing,
4413                  boolean disableHdrConversion, boolean inTraversal) {// (1)
4414              setDisplayPropertiesInternal(displayId, hasContent, requestedRefreshRate,
4415                      requestedMode, requestedMinRefreshRate, requestedMaxRefreshRate,
4416                      requestedMinimalPostProcessing, disableHdrConversion, inTraversal);
4417          }
...
2431      void setDisplayPropertiesInternal(int displayId, boolean hasContent,
2432              float requestedRefreshRate, int requestedModeId, float requestedMinRefreshRate,
2433              float requestedMaxRefreshRate, boolean preferMinimalPostProcessing,
2434              boolean disableHdrConversion, boolean inTraversal) {
2435          synchronized (mSyncRoot) {
2436              final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
2437              if (display == null) {
2438                  return;
2439              }
2440  
2441              boolean shouldScheduleTraversal = false;
2442  
2443              if (display.hasContentLocked() != hasContent) {
2444                  if (DEBUG) {
2445                      Slog.d(TAG, "Display " + displayId + " hasContent flag changed: "
2446                              + "hasContent=" + hasContent + ", inTraversal=" + inTraversal);
2447                  }
2448  
2449                  display.setHasContentLocked(hasContent);
2450                  shouldScheduleTraversal = true;
2451              }// (2)// 入参 requestedModeId 表示应用期望的 base mode id,// 入参 requestedRefreshRate 表示应用期望的 base mode 的刷新率,// 如果入参 requestedModeId 有效(即不为 0),则将 requestedModeId 设为 base mode,// 如果入参 requestedModeId 无效(即为 0),且入参 requestedRefreshRate 有效,// 则找到与 requestedRefreshRate 匹配的 display mode,并设为 base mode。
2452              if (requestedModeId == 0 && requestedRefreshRate != 0) {
2453                  // Scan supported modes returned by display.getInfo() to find a mode with the same
2454                  // size as the default display mode but with the specified refresh rate instead.
2455                  Display.Mode mode = display.getDisplayInfoLocked().findDefaultModeByRefreshRate(
2456                          requestedRefreshRate);
2457                  if (mode != null) {
2458                      requestedModeId = mode.getModeId();
2459                  } else {
2460                      Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
2461                              + requestedRefreshRate + " on Display: " + displayId);
2462                  }
2463              }// (3)
2464              mDisplayModeDirector.getAppRequestObserver().setAppRequest(
2465                      displayId, requestedModeId, requestedMinRefreshRate, requestedMaxRefreshRate);
...

DisplayModeDirector$AppRequestObserver.setAppRequest 方法。

  1. 调用 setAppRequestedModeLocked 方法更新 "应用期望的 base mode 刷新率"

  2. 调用 setAppPreferredRefreshRateRangeLocked 方法更新 "应用刷新率" 范围

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java1272      /**
1273       *  Responsible for keeping track of app requested refresh rates per display
1274       */
1275      public final class AppRequestObserver {
...
1284          /**
1285           * Sets refresh rates from app request
1286           */// 设置应用期望的 base mode 刷新率 & 应用刷屏率
1287          public void setAppRequest(int displayId, int modeId, float requestedMinRefreshRateRange,
1288                  float requestedMaxRefreshRateRange) {
1289              synchronized (mLock) {// 设置应用期望的 base mode(刷新率)
1290                  setAppRequestedModeLocked(displayId, modeId);// 设置应用刷新率
1291                  setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
1292                          requestedMaxRefreshRateRange);
1293              }
1294          }
1295// 设置应用期望的 base mode 刷新率
1296          private void setAppRequestedModeLocked(int displayId, int modeId) {// 目标 base mode 和当前的 base mode 相同时,直接返回
1297              final Display.Mode requestedMode = findModeByIdLocked(displayId, modeId);
1298              if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
1299                  return;
1300              }
1301  
1302              final Vote baseModeRefreshRateVote;
1303              final Vote sizeVote;// 目标 base mode 有效,则将该 mode 的刷新率作为应用期望的 base mode 刷新率
1304              if (requestedMode != null) {
1305                  mAppRequestedModeByDisplay.put(displayId, requestedMode);
1306                  baseModeRefreshRateVote =
1307                          Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate());
1308                  sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
1309                          requestedMode.getPhysicalHeight());// 目标 base mode 无效(没有该 mode)时,清除应用期望的 base mode 刷新率设置
1310              } else {
1311                  mAppRequestedModeByDisplay.remove(displayId);
1312                  baseModeRefreshRateVote = null;
1313                  sizeVote = null;
1314              }
1315  // 更新 VotesStorage
1316              mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
1317                      baseModeRefreshRateVote);
1318              mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
1319          }
1320// 设置应用设置的范围
1321          private void setAppPreferredRefreshRateRangeLocked(int displayId,
1322                  float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
1323              final Vote vote;
1324  // 检查目标范围是否无效。// 目标 min 和 max 至少有一个应该 > 0.// 目标 max <=0 时,目标范围为 [min, 无限大];// 目标 max > 0 时,目标范围为 [min, max]。
1325              RefreshRateRange refreshRateRange = null;
1326              if (requestedMinRefreshRateRange > 0 || requestedMaxRefreshRateRange > 0) {
1327                  float min = requestedMinRefreshRateRange;
1328                  float max = requestedMaxRefreshRateRange > 0
1329                          ? requestedMaxRefreshRateRange : Float.POSITIVE_INFINITY;
1330                  refreshRateRange = new RefreshRateRange(min, max);
1331                  if (refreshRateRange.min == 0 && refreshRateRange.max == 0) {
1332                      // requestedMinRefreshRateRange/requestedMaxRefreshRateRange were invalid
1333                      refreshRateRange = null;
1334                  }
1335              }
1336  // 目标范围和当前的 "应用刷新率" 范围相同,直接返回
1337              if (Objects.equals(refreshRateRange,
1338                      mAppPreferredRefreshRateRangeByDisplay.get(displayId))) {
1339                  return;
1340              }
1341  
1342              if (refreshRateRange != null) {
1343                  mAppPreferredRefreshRateRangeByDisplay.put(displayId, refreshRateRange);
1344                  vote = Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max);// 目标范围无效时,清除"应用刷新率" 范围设置
1345              } else {
1346                  mAppPreferredRefreshRateRangeByDisplay.remove(displayId);
1347                  vote = null;
1348              }// 更新 VotesStorage
1349              mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
1350                      vote);
1351          }

遍历窗口,获取窗口 "期望的 base mode id"、"期望的 base mode 刷新率" 或 "应用刷新率范围" 属性的方法是 mApplySurfaceChangesTransaction 。

对应的窗口属性分别是:

WindowManager$LayoutParams.preferredRefreshRate

WindowManager$LayoutParams.preferredDisplayModeId

WindowManager$LayoutParams.preferredMinDisplayRefreshRate

WindowManager$LayoutParams.preferredMaxDisplayRefreshRate

4.3.1.5 小结
  • 设置 system settings "peak_refresh_rate" 或 device_config <"display_manager", "peak_refresh_rate_default"> 时("用户设置的刷新率"、"默认的用户设置刷新率" 变化),调用 DisplayModeDirector.updateRefreshRateSettingLocked 更新刷新率;

  • 窗口切换,并且可见窗口包含以下属性时

    • 调用 mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE, vote) 更新目标屏幕 id 的 ""刷新率策略。

          vote 是由这两个窗口属性值计算得到的 "应用(app)设置的策略"

      • WindowManager$LayoutParams.preferredMinDisplayRefreshRate

      • WindowManager$LayoutParams.preferredMaxDisplayRefreshRate

    • 调用 mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, baseModeRefreshRateVote) 更新目标屏幕 id 的刷新率策略。

          baseModeRefreshRateVote 是由这两个窗口属性值计算得到的 "应用(app)设置的 base mode 刷新率策略"

      • WindowManager$LayoutParams.preferredRefreshRate

      • WindowManager$LayoutParams.preferredDisplayModeId

实际上,DisplayModeDirector.updateRefreshRateSettingLocked 方法其实也是计算得到 "策略对象" 并更新到 VotesStorage。

VotesStorage 更新时再回调用 VotesStorage.mListener 应用更新。

只不过,DisplayModeDirector.updateRefreshRateSettingLocked 是根据 "最低刷新率"、“默认刷新率"、"默认的用户设置刷新率", "用户设置的刷新率" 这些设置计算得到 "用户(user)设置的最低策略"、"默认策略"、"用户(user)设置的策略" 的策略对象,并调用 VotesStorage.updateGlobalVote 更新全局刷新率策略。

4.3.2 Vote & VotesStorage

4.3.2.1 Vote

Vote(投票、选举)对象表示刷新率的策略。

一共有 15 种策略,每种策略有一个优先级,从 0~14,值越大策略的优先级越高。

0 --- 默认策略,限制应用刷新率在 [0,默认刷新率] 之间变化

3 --- 用户(user)设置的最低策略,限制应用刷新率的最低值,即应用刷新率在 [最低刷新率,正无限大] 之间变化

4 --- 应用(app)设置的策略,限制应用刷新率范围在 [应用刷新率min应用刷新率max] 之间变化

5 --- 应用(app)设置的 base mode 刷新率策略,应用期望的 base mode 刷新率的值

7 --- 用户(user)设置的策略,限制应用刷新率在 [0, max(用户设置的刷新率, 最低刷新率)] 之间变化

本文只介绍这几个策略,这几个策略中除 5 以外都只跟 "应用刷新率" 相关。"屏幕刷新率" 相关的策略主要跟背光、HBM(屏幕亮度增强模式),UDFPS(屏下指纹)相关。

这些策略优先级的定义如下,它们是常量。

// frameworks/base/services/core/java/com/android/server/display/mode/Vote.java21  final class Vote {
22      // DEFAULT_RENDER_FRAME_RATE votes for render frame rate [0, DEFAULT]. As the lowest
23      // priority vote, it's overridden by all other considerations. It acts to set a default
24      // frame rate for a device.// 默认策略
25      static final int PRIORITY_DEFAULT_RENDER_FRAME_RATE = 0;
26  
27      // PRIORITY_FLICKER_REFRESH_RATE votes for a single refresh rate like [60,60], [90,90] or
28      // null. It is used to set a preferred refresh rate value in case the higher priority votes
29      // result is a range.
30      static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
31  
32      // High-brightness-mode may need a specific range of refresh-rates to function properly.
33      static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
34  
35      // SETTING_MIN_RENDER_FRAME_RATE is used to propose a lower bound of the render frame rate.
36      // It votes [minRefreshRate, Float.POSITIVE_INFINITY]// 用户(user)设置的最低策略
37      static final int PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE = 3;
38  
39      // APP_REQUEST_RENDER_FRAME_RATE_RANGE is used to for internal apps to limit the render
40      // frame rate in certain cases, mostly to preserve power.
41      // @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
42      // @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
43      // It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].//  应用(app)设置的策略
44      static final int PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4;
45  
46      // We split the app request into different priorities in case we can satisfy one desire
47      // without the other.
48  
49      // Application can specify preferred refresh rate with below attrs.
50      // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
51      // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
52      //
53      // When the app specifies a LayoutParams#preferredDisplayModeId, in addition to the
54      // refresh rate, it also chooses a preferred size (resolution) as part of the selected
55      // mode id. The app preference is then translated to APP_REQUEST_BASE_MODE_REFRESH_RATE and
56      // optionally to APP_REQUEST_SIZE as well, if a mode id was selected.
57      // The system also forces some apps like denylisted app to run at a lower refresh rate.
58      // @see android.R.array#config_highRefreshRateBlacklist
59      //
60      // When summarizing the votes and filtering the allowed display modes, these votes determine
61      // which mode id should be the base mode id to be sent to SurfaceFlinger:
62      // - APP_REQUEST_BASE_MODE_REFRESH_RATE is used to validate the vote summary. If a summary
63      //   includes a base mode refresh rate, but it is not in the refresh rate range, then the
64      //   summary is considered invalid so we could drop a lower priority vote and try again.
65      // - APP_REQUEST_SIZE is used to filter out display modes of a different size.
66      //
67      // The preferred refresh rate is set on the main surface of the app outside of
68      // DisplayModeDirector.// 应用(app)设置的 base mode 刷新率策略
69      // @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
70      static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
71      static final int PRIORITY_APP_REQUEST_SIZE = 6;
72  
73      // SETTING_PEAK_RENDER_FRAME_RATE has a high priority and will restrict the bounds of the
74      // rest of low priority voters. It votes [0, max(PEAK, MIN)]// 用户(user)设置的策略
75      static final int PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE = 7;
76  
77      // To avoid delay in switching between 60HZ -> 90HZ when activating LHBM, set refresh
78      // rate to max value (same as for PRIORITY_UDFPS) on lock screen
79      static final int PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE = 8;
80  
81      // For concurrent displays we want to limit refresh rate on all displays
82      static final int PRIORITY_LAYOUT_LIMITED_FRAME_RATE = 9;
83  
84      // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if
85      // Settings.Global.LOW_POWER_MODE is on.
86      static final int PRIORITY_LOW_POWER_MODE = 10;
87  
88      // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
89      // higher priority voters' result is a range, it will fix the rate to a single choice.
90      // It's used to avoid refresh rate switches in certain conditions which may result in the
91      // user seeing the display flickering when the switches occur.
92      static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 11;
93  
94      // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
95      static final int PRIORITY_SKIN_TEMPERATURE = 12;
96  
97      // The proximity sensor needs the refresh rate to be locked in order to function, so this is
98      // set to a high priority.
99      static final int PRIORITY_PROXIMITY = 13;
100  
101      // The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
102      // to function, so this needs to be the highest priority of all votes.
103      static final int PRIORITY_UDFPS = 14;
104  
105      // Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
106      // APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
107  
108      static final int MIN_PRIORITY = PRIORITY_DEFAULT_RENDER_FRAME_RATE;
109      static final int MAX_PRIORITY = PRIORITY_UDFPS;
110  
111      // The cutoff for the app request refresh rate range. Votes with priorities lower than this
112      // value will not be considered when constructing the app request refresh rate range.
113      static final int APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF =
114              PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
...

Vote 对象包含以下属性。

显示 width(pixels);

显示 height(pixels);

刷新率范围(包含应用刷新率范围,屏幕刷新率范围);

是否禁止屏幕切换刷新率(屏幕刷新率范围是一个孤立的值);

应用设置的期望 base mode 刷新率。

// frameworks/base/services/core/java/com/android/server/display/mode/Vote.java121      /**
122       * The requested width of the display in pixels, or INVALID_SIZE;
123       */
124      public final int width;
125      /**
126       * The requested height of the display in pixels, or INVALID_SIZE;
127       */
128      public final int height;
129      /**
130       * Information about the refresh rate frame rate ranges DM would like to set the display to.
131       */
132      public final SurfaceControl.RefreshRateRanges refreshRateRanges;
133  
134      /**
135       * Whether refresh rate switching should be disabled (i.e. the refresh rate range is
136       * a single value).
137       */
138      public final boolean disableRefreshRateSwitching;
139  
140      /**
141       * The preferred refresh rate selected by the app. It is used to validate that the summary
142       * refresh rate ranges include this value, and are not restricted by a lower priority vote.
143       */
144      public final float appRequestBaseModeRefreshRate;

Vote 对象有 5 种构造方式,这 5 种方式构造的 Vote 对象功能不同。

  • Vote.forPhysicalRefreshRates 方法

构造的 Vote 不限制分辨率、应用刷新率范围(0~正无穷)、base mode 的刷新率;

限制屏幕刷新率范围;如果范围的 min == max,则禁止刷新率切换。

  • Vote.forRenderFrameRates 方法

构造的 Vote 不限制分辨率、屏幕刷新率范围(0~正无穷)、base mode 的刷新率;

限制应用刷新率范围,不禁止刷新率切换。

  • Vote.forSize 方法

仅限制分辨率(即 width、height 值)

  • Vote.forDisableRefreshRateSwitching 方法

仅禁止刷新率切换

  • Vote.forBaseModeRefreshRate 方法

仅设置应用设置的期望 base mode 刷新率

146      static Vote forPhysicalRefreshRates(float minRefreshRate, float maxRefreshRate) {
147          return new Vote(INVALID_SIZE, INVALID_SIZE, minRefreshRate, maxRefreshRate, 0,
148                  Float.POSITIVE_INFINITY,
149                  minRefreshRate == maxRefreshRate, 0f);
150      }
151  
152      static Vote forRenderFrameRates(float minFrameRate, float maxFrameRate) {
153          return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, minFrameRate,
154                  maxFrameRate,
155                  false, 0f);
156      }
157  
158      static Vote forSize(int width, int height) {
159          return new Vote(width, height, 0, Float.POSITIVE_INFINITY, 0, Float.POSITIVE_INFINITY,
160                  false,
161                  0f);
162      }
163  
164      static Vote forDisableRefreshRateSwitching() {
165          return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
166                  Float.POSITIVE_INFINITY, true,
167                  0f);
168      }
169  
170      static Vote forBaseModeRefreshRate(float baseModeRefreshRate) {
171          return new Vote(INVALID_SIZE, INVALID_SIZE, 0, Float.POSITIVE_INFINITY, 0,
172                  Float.POSITIVE_INFINITY, false,
173                  baseModeRefreshRate);
174      }
175  
176      private Vote(int width, int height,
177              float minPhysicalRefreshRate,
178              float maxPhysicalRefreshRate,
179              float minRenderFrameRate,
180              float maxRenderFrameRate,
181              boolean disableRefreshRateSwitching,
182              float baseModeRefreshRate) {
183          this.width = width;
184          this.height = height;
185          this.refreshRateRanges = new SurfaceControl.RefreshRateRanges(
186                  new SurfaceControl.RefreshRateRange(minPhysicalRefreshRate, maxPhysicalRefreshRate),
187                  new SurfaceControl.RefreshRateRange(minRenderFrameRate, maxRenderFrameRate));
188          this.disableRefreshRateSwitching = disableRefreshRateSwitching;
189          this.appRequestBaseModeRefreshRate = baseModeRefreshRate;
190      }

4.3.2.2 VotesStorage

VotesStorage 的核心是成员变量 mVotesByDisplay。

每个 display(id)可以包含多个 Votes,每个 Vote 对应一个策略(优先级),将这些 Votes 存为 map,即每个 display(id)对应一个 Votes map,其 entry 为 <priority,Vote>;

然后再将所有 display(id)的 Votes map 存为一个更大的 map,即 mVotesByDisplay,其 entry 为 <display id,map<prioriy, Vote>>。

// frameworks/base/services/core/java/com/android/server/display/mode/VotesStorage.java29  class VotesStorage {
30      private static final String TAG = "VotesStorage";
...
40      // A map from the display ID to the collection of votes and their priority. The latter takes
41      // the form of another map from the priority to the vote itself so that each priority is
42      // guaranteed to have exactly one vote, which is also easily and efficiently replaceable.
43      @GuardedBy("mStorageLock")
44      private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();
...

GLOBAL_ID(-1)表示全局策略,当全局策略中包含某个 priority,而目标 display id 不包含该 priority,则会使用全局的 priority 策略。也就是说目标 display id 的策略优先级更高。

更新 Votes map。

  1. updateGlobalVote 方法更新 GLOBAL_ID 即全局的 Votes map。

  2. updateVote 方法更新目标 display id 的 Votes map。

// frameworks/base/services/core/java/com/android/server/display/mode/VotesStorage.java75      /** updates vote storage for all displays */
76      void updateGlobalVote(int priority, @Nullable Vote vote) {
77          updateVote(GLOBAL_ID, priority, vote);
78      }
79  
80      /** updates vote storage */
81      void updateVote(int displayId, int priority, @Nullable Vote vote) {
82          if (mLoggingEnabled) {
83              Slog.i(TAG, "updateVoteLocked(displayId=" + displayId
84                      + ", priority=" + Vote.priorityToString(priority)
85                      + ", vote=" + vote + ")");
86          }
87          if (priority < Vote.MIN_PRIORITY || priority > Vote.MAX_PRIORITY) {
88              Slog.w(TAG, "Received a vote with an invalid priority, ignoring:"
89                      + " priority=" + Vote.priorityToString(priority)
90                      + ", vote=" + vote);
91              return;
92          }
93          SparseArray<Vote> votes;
94          synchronized (mStorageLock) {
95              if (mVotesByDisplay.contains(displayId)) {
96                  votes = mVotesByDisplay.get(displayId);
97              } else {
98                  votes = new SparseArray<>();
99                  mVotesByDisplay.put(displayId, votes);
100              }
101              if (vote != null) {
102                  votes.put(priority, vote);
103              } else {
104                  votes.remove(priority);
105              }
106          }
107          if (mLoggingEnabled) {
108              Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
109          }// 回调
110          mListener.onChanged();
111      }
112  

VotesStorage 更新策略后,会回调监听器的 onChanged 方法。

我们先看下 VotesStorage 监听器是怎么注册的,以及监听器的 onChanged 方法做了什么。

DisplayModeDirector 初始化时构造 VotesStorage 对象,同时注册监听器,监听器的 onChanged 方法调用 DisplayModeDirector .notifyDesiredDisplayModeSpecsChangedLocked。

也就是说,VotesStorage 更新后,会回调 DisplayModeDirector .notifyDesiredDisplayModeSpecsChangedLocked 方法。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java152      public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler,
153              @NonNull Injector injector) {
154          mContext = context;
155          mHandler = new DisplayModeDirectorHandler(handler.getLooper());
156          mInjector = injector;
157          mSupportedModesByDisplay = new SparseArray<>();
158          mDefaultModeByDisplay = new SparseArray<>();
159          mAppRequestObserver = new AppRequestObserver();
160          mDeviceConfig = injector.getDeviceConfig();
161          mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
162          mSettingsObserver = new SettingsObserver(context, handler);
163          mBrightnessObserver = new BrightnessObserver(context, handler, injector);
164          mDefaultDisplayDeviceConfig = null;
165          mUdfpsObserver = new UdfpsObserver();// 构造 VotesStorage,设置回调
166          mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked);
167          mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
168          mSensorObserver = new SensorObserver(context, mVotesStorage, injector);
169          mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
170          mHbmObserver = new HbmObserver(injector, mVotesStorage, BackgroundThread.getHandler(),
171                  mDeviceConfigDisplaySettings);
172          mAlwaysRespectAppRequest = false;
173          mSupportsFrameRateOverride = injector.supportsFrameRateOverride();
174      }
175  
// frameworks/base/services/core/java/com/android/server/display/mode/VotesStorage.java29  class VotesStorage {
37      private final Listener mListener;
45  
46      VotesStorage(@NonNull Listener listener) {
47          mListener = listener;
48      }
...
149      interface Listener {
150          void onChanged();
151      }
152  }

注意,这里涉及到 Java 的方法引用和函数式编程特性。VotesStorage 的构造函数参数传递的是一个方法引用 this::notifyDesiredDisplayModeSpecsChangedLocked。

方法引用 this::notifyDesiredDisplayModeSpecsChangedLocked 是 Java 8 引入的一种语法,用于简化 Lambda 表达式。在这种情况下,它引用了 DisplayModeDirector 类中的 notifyDesiredDisplayModeSpecsChangedLocked 方法,并将其传递给 VotesStorage 构造函数。

这相当于创建了一个 Listener 接口的匿名实现类对象,该对象的 onChanged() 方法实现调用的是 notifyDesiredDisplayModeSpecsChangedLocked 方法。

相当于:

mVotesStorage = new VotesStorage(new Listener() {@Overridepublic void onChanged() {notifyDesiredDisplayModeSpecsChangedLocked();}
});

返回目标 display id 的 Votes map。

// frameworks/base/services/core/java/com/android/server/display/mode/VotesStorage.java53      /**
54       * gets all votes for specific display, note that global display votes are also added to result
55       */
56      @NonNull
57      SparseArray<Vote> getVotes(int displayId) {
58          SparseArray<Vote> votesLocal;
59          SparseArray<Vote> globalVotesLocal;
60          synchronized (mStorageLock) {// 复制目标 display id 的 Votes map,即 temp Votes map 1
61              SparseArray<Vote> displayVotes = mVotesByDisplay.get(displayId);
62              votesLocal = displayVotes != null ? displayVotes.clone() : new SparseArray<>();// 复制 GLOBAL_ID 的 Votes map,即 temp Votes map 2
63              SparseArray<Vote> globalVotes = mVotesByDisplay.get(GLOBAL_ID);
64              globalVotesLocal = globalVotes != null ? globalVotes.clone() : new SparseArray<>();
65          }// 合并 temp Votes map 1 和 temp Votes map 2.// 即将 GLOBAL_ID 包含而目标 display id 不包含的 priority Vote 合并到返回的 Votes map
66          for (int i = 0; i < globalVotesLocal.size(); i++) {
67              int priority = globalVotesLocal.keyAt(i);
68              if (!votesLocal.contains(priority)) {
69                  votesLocal.put(priority, globalVotesLocal.valueAt(i));
70              }
71          }
72          return votesLocal;
73      }

4.3.3 DisplayModeDirector.updateRefreshRateSettingLocked

DisplayModeDirector.updateRefreshRateSettingLocked 方法根据 "最低刷新率"、“默认刷新率"、"默认的用户设置刷新率", "用户设置的刷新率" 这些设置计算得到 "用户(user)设置的最低策略"、"默认策略"、"用户(user)设置的策略" 的策略对象(Votes),并调用 VotesStorage.updateGlobalVote 更新全局刷新率策略。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java1207          private void updateRefreshRateSettingLocked() {
1208              final ContentResolver cr = mContext.getContentResolver();// 最低刷新率
1209              float minRefreshRate = Settings.System.getFloatForUser(cr,
1210                      Settings.System.MIN_REFRESH_RATE, 0f, cr.getUserId());// 用户设置的刷新率
1211              float peakRefreshRate = Settings.System.getFloatForUser(cr,
1212                      Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate, cr.getUserId());// 更新 global 策略
1213              updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
1214          }
1215
1216          private void updateRefreshRateSettingLocked(
1217                  float minRefreshRate, float peakRefreshRate, float defaultRefreshRate) {
1218              // TODO(b/156304339): The logic in here, aside from updating the refresh rate votes, is
1219              // used to predict if we're going to be doing frequent refresh rate switching, and if
1220              // so, enable the brightness observer. The logic here is more complicated and fragile
1221              // than necessary, and we should improve it. See b/156304339 for more info.
1222              Vote peakVote = peakRefreshRate == 0f
1223                      ? null
1224                      : Vote.forRenderFrameRates(0f, Math.max(minRefreshRate, peakRefreshRate));// 更新 global "用户(user)设置的策略"
1225              mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE,
1226                      peakVote);// 更新 global "用户(user)设置的最低策略"
1227              mVotesStorage.updateGlobalVote(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE,
1228                      Vote.forRenderFrameRates(minRefreshRate, Float.POSITIVE_INFINITY));
1229              Vote defaultVote =
1230                      defaultRefreshRate == 0f
1231                              ? null : Vote.forRenderFrameRates(0f, defaultRefreshRate);// 更新 global "默认策略"
1232              mVotesStorage.updateGlobalVote(Vote.PRIORITY_DEFAULT_RENDER_FRAME_RATE, defaultVote);
1233  
1234              float maxRefreshRate;
1235              if (peakRefreshRate == 0f && defaultRefreshRate == 0f) {
1236                  // We require that at least one of the peak or default refresh rate values are
1237                  // set. The brightness observer requires that we're able to predict whether or not
1238                  // we're going to do frequent refresh rate switching, and with the way the code is
1239                  // currently written, we need either a default or peak refresh rate value for that.
1240                  Slog.e(TAG, "Default and peak refresh rates are both 0. One of them should be set"
1241                          + " to a valid value.");
1242                  maxRefreshRate = minRefreshRate;
1243              } else if (peakRefreshRate == 0f) {
1244                  maxRefreshRate = defaultRefreshRate;
1245              } else if (defaultRefreshRate == 0f) {
1246                  maxRefreshRate = peakRefreshRate;
1247              } else {
1248                  maxRefreshRate = Math.min(defaultRefreshRate, peakRefreshRate);
1249              }
1250  
1251              mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, maxRefreshRate);
1252          }

4.3.4 DisplayModeDirector .notifyDesiredDisplayModeSpecsChangedLocked

上文提到过,VotesStorage 更新策略(不管是 VotesStorage.updateGlobalVote 还是 VotesStorage.updateVote)后会回调 DisplayModeDirector .notifyDesiredDisplayModeSpecsChangedLocked 方法。

notifyDesiredDisplayModeSpecsChangedLocked 方法发送 msg MSG_REFRESH_RATE_RANGE_CHANGED 和 obj mDesiredDisplayModeSpecsListener 到 handler。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java798      private void notifyDesiredDisplayModeSpecsChangedLocked() {
799          if (mDesiredDisplayModeSpecsListener != null
800                  && !mHandler.hasMessages(MSG_REFRESH_RATE_RANGE_CHANGED)) {
801              // We need to post this to a handler to avoid calling out while holding the lock
802              // since we know there are things that both listen for changes as well as provide
803              // information. If we did call out while holding the lock, then there's no
804              // guaranteed lock order and we run the real of risk deadlock.
805              Message msg = mHandler.obtainMessage(
806                      MSG_REFRESH_RATE_RANGE_CHANGED, mDesiredDisplayModeSpecsListener);
807              msg.sendToTarget();
808          }
809      }

handler 收到 msg MSG_REFRESH_RATE_RANGE_CHANGED 时调用 mDesiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged 方法。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java876      /**
877       * Listens for changes refresh rate coordination.
878       */
879      public interface DesiredDisplayModeSpecsListener {
880          /**
881           * Called when the refresh rate range may have changed.
882           */
883          void onDesiredDisplayModeSpecsChanged();
884      }
885  
886      private final class DisplayModeDirectorHandler extends Handler {
887          DisplayModeDirectorHandler(Looper looper) {
888              super(looper, null, true /*async*/);
889          }
890  
891          @Override
892          public void handleMessage(Message msg) {
893              switch (msg.what) {
...
930                  case MSG_REFRESH_RATE_RANGE_CHANGED:
931                      DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener =
932                              (DesiredDisplayModeSpecsListener) msg.obj;
933                      desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged();
934                      break;
...
948              }
949          }
950      }

mDesiredDisplayModeSpecsListener 是一个显示参数监听器,

由 DisplayModeDirector.setDesiredDisplayModeSpecsListener 方法设置。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java661      /**
662       * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges.
663       */
664      public void setDesiredDisplayModeSpecsListener(
665              @Nullable DesiredDisplayModeSpecsListener desiredDisplayModeSpecsListener) {
666          synchronized (mLock) {
667              mDesiredDisplayModeSpecsListener = desiredDisplayModeSpecsListener;
668          }
669      }

DMS 启动时,创建一个目标显示参数的监听器,注册到 DisplayModeDirector。

当目标显示参数变化时,将显示参数设置到 LocalDisplayDevice。

// frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java684      /**
685       * Called when the system is ready to go.
686       */
687      public void systemReady(boolean safeMode) {
688          synchronized (mSyncRoot) {
707  
...// 设置监听器
708          mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
709                  new DesiredDisplayModeSpecsObserver());
...4641      class DesiredDisplayModeSpecsObserver
4642              implements DisplayModeDirector.DesiredDisplayModeSpecsListener {
4643  
4644          private final Consumer<LogicalDisplay> mSpecsChangedConsumer = display -> {
4645              int displayId = display.getDisplayIdLocked();// 从 DisplayModeDirector 获取显示参数(DisplayModeDirector$DesiredDisplayModeSpecs 结构)
4646              DisplayModeDirector.DesiredDisplayModeSpecs desiredDisplayModeSpecs =
4647                      mDisplayModeDirector.getDesiredDisplayModeSpecs(displayId);
4648              DisplayModeDirector.DesiredDisplayModeSpecs existingDesiredDisplayModeSpecs =
4649                      display.getDesiredDisplayModeSpecsLocked();
4650              if (DEBUG) {
4651                  Slog.i(TAG,
4652                          "Comparing display specs: " + desiredDisplayModeSpecs
4653                                  + ", existing: " + existingDesiredDisplayModeSpecs);
4654              }
4655              if (!desiredDisplayModeSpecs.equals(existingDesiredDisplayModeSpecs)) {// 将显示参数设置到 LocalDisplayDevice 
4656                  display.setDesiredDisplayModeSpecsLocked(desiredDisplayModeSpecs);
4657                  mChanged = true;
4658              }
4659          };
4660  
4661          @GuardedBy("mSyncRoot")
4662          private boolean mChanged = false;
4663  // 显示参数变化,监听器回调
4664          public void onDesiredDisplayModeSpecsChanged() {
4665              synchronized (mSyncRoot) {
4666                  mChanged = false;
4667                  mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer,
4668                          /* includeDisabled= */ false);
4669                  if (mChanged) {
4670                      scheduleTraversalLocked(false);
4671                      mChanged = false;
4672                  }
4673              }
4674          }
4675      }

将显示参数设置到 LocalDisplayDevice

  1. 由 DisplayModeDirector$DesiredDisplayModeSpecs.baseModeId 得到目标 base mode id

  2. 将目标 base mode id 连同 DisplayModeDirector$DesiredDisplayModeSpecs.primary、DisplayModeDirector$DesiredDisplayModeSpecs.appRequest 一起封装到 SurfaceControl$DesiredDisplayModeSpecs 结构,并设置到 SurfaceFlinger。mSurfaceControlProxy.setDesiredDisplayModeSpecs 调用 native 方法 nativeSetDesiredDisplayModeSpecs。

// frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java191      private final class LocalDisplayDevice extends DisplayDevice {
... ...
989          @Override
990          public void setDesiredDisplayModeSpecsLocked(
991                  DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
992              if (displayModeSpecs.baseModeId == 0) {
993                  // Bail if the caller is requesting a null mode. We'll get called again shortly with
994                  // a valid mode.
995                  return;
996              }
997  
998              // Find the mode Id based on the desired mode specs. In case there is more than one
999              // mode matching the mode spec, prefer the one that is in the default mode group.
1000              // For now the default config mode is taken from the active mode when we got the
1001              // hotplug event for the display. In the future we might want to change the default
1002              // mode based on vendor requirements.
1003              // Note: We prefer the default mode group over the current one as this is the mode
1004              // group the vendor prefers.
1005              int baseSfModeId = findSfDisplayModeIdLocked(displayModeSpecs.baseModeId,
1006                      mDefaultModeGroup);
1007              if (baseSfModeId < 0) {
1008                  // When a display is hotplugged, it's possible for a mode to be removed that was
1009                  // previously valid. Because of the way display changes are propagated through the
1010                  // framework, and the caching of the display mode specs in LogicalDisplay, it's
1011                  // possible we'll get called with a stale mode id that no longer represents a valid
1012                  // mode. This should only happen in extremely rare cases. A followup call will
1013                  // contain a valid mode id.
1014                  Slog.w(TAG,
1015                          "Ignoring request for invalid base mode id " + displayModeSpecs.baseModeId);
1016                  updateDeviceInfoLocked();
1017                  return;
1018              }
1019              if (mDisplayModeSpecsInvalid || !displayModeSpecs.equals(mDisplayModeSpecs)) {
1020                  mDisplayModeSpecsInvalid = false;
1021                  mDisplayModeSpecs.copyFrom(displayModeSpecs);
1022                  getHandler().sendMessage(PooledLambda.obtainMessage(
1023                          LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this,
1024                          getDisplayTokenLocked(),
1025                          new SurfaceControl.DesiredDisplayModeSpecs(baseSfModeId,
1026                                  mDisplayModeSpecs.allowGroupSwitching,
1027                                  mDisplayModeSpecs.primary,
1028                                  mDisplayModeSpecs.appRequest)));
1029              }
1030          }
1031  
1032          private void setDesiredDisplayModeSpecsAsync(IBinder displayToken,
1033                  SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
1034              // Do not lock when calling these SurfaceControl methods because they are sync
1035              // operations that may block for a while when setting display power mode.
1036              mSurfaceControlProxy.setDesiredDisplayModeSpecs(displayToken, modeSpecs);
1037          }

这里的关键是,调用 DisplayModeDirector.getDesiredDisplayModeSpecs 方法来获取目标显示参数,即得到 DisplayModeDirector$DesiredDisplayModeSpecs 结构。

而 DisplayModeDirector.getDesiredDisplayModeSpecs 方法实际上又是从 VotesStorage 获取目标显示参数的。

下面一个章节就介绍 VotesStorage 中的策略如何影响 DisplayModeDirector.getDesiredDisplayModeSpecs 的返回值。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java365      /**
366       * Calculates the refresh rate ranges and display modes that the system is allowed to freely
367       * switch between based on global and display-specific constraints.
368       *
369       * @param displayId The display to query for.
370       * @return The ID of the default mode the system should use, and the refresh rate range the
371       * system is allowed to switch between.
372       */
373      @NonNull
374      public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) {
375          synchronized (mLock) {
376              SparseArray<Vote> votes = mVotesStorage.getVotes(displayId);
377              Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
378              Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
... ...

4.3.5 DisplayModeDirector.getDesiredDisplayModeSpecs

DisplayModeDirector.getDesiredDisplayModeSpecs 方法返回一个 DisplayModeDirector$DesiredDisplayModeSpecs 对象。

DisplayModeDirector.getDesiredDisplayModeSpecs 方法的核心是 summarizeVotes。

summarizeVotes 方法返回一个 VoteSummary 对象。

VoteSummary 表示对一组 Votes 策略的整合,整合结果包含目标: "屏幕刷新率" 范围、"应用刷新率"范围、以及 base mode 的刷新率。

DesiredDisplayModeSpecs 对象是对目标 base mode、 "屏幕刷新率" 范围、"应用刷新率" 范围的封装。

DisplayModeDirector.getDesiredDisplayModeSpecs 方法,

先调用 summarizeVotes 方法将 priority 0~15 的所有 Votes 策略整合为 primarySummary(如果支持的 display modes 中没有任何一个 mode 可以满足所有 Votes 策略的要求,则整合 priority 1~15,依此类推),primarySummary 中如果包含 "base mode 的刷新率",则找一个符合要求的 mode 作为 base mode。

然后再次调用 summarizeVotes 方法将 priority 4~15 的 Votes 策略整合为 appRequestSummary。

先分析 summarizeVotes 方法的作用。

  1. 入参 SparseArray<Vote> votes 是目标 Votes map

  2. 入参 int lowestConsideredPriority 和 int highestConsideredPriority 表示目标 priority 范围

  3. 入参 VoteSummary summary 是 out 输出

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java258      // VoteSummary is returned as an output param to cut down a bit on the number of temporary
259      // objects.
260      private void summarizeVotes(
261              SparseArray<Vote> votes,
262              int lowestConsideredPriority,
263              int highestConsideredPriority,
264              /*out*/ VoteSummary summary) {
265          summary.reset();
266          for (int priority = highestConsideredPriority;
267                  priority >= lowestConsideredPriority;
268                  priority--) {
269              Vote vote = votes.get(priority);
270              if (vote == null) {
271                  continue;
272              }
273  
274  
275              // For physical refresh rates, just use the tightest bounds of all the votes.
276              // The refresh rate cannot be lower than the minimal render frame rate.
277              final float minPhysicalRefreshRate = Math.max(vote.refreshRateRanges.physical.min,
278                      vote.refreshRateRanges.render.min);
279              summary.minPhysicalRefreshRate = Math.max(summary.minPhysicalRefreshRate,
280                      minPhysicalRefreshRate);
281              summary.maxPhysicalRefreshRate = Math.min(summary.maxPhysicalRefreshRate,
282                      vote.refreshRateRanges.physical.max);
283  
284              // Same goes to render frame rate, but frame rate cannot exceed the max physical
285              // refresh rate
286              final float maxRenderFrameRate = Math.min(vote.refreshRateRanges.render.max,
287                      vote.refreshRateRanges.physical.max);
288              summary.minRenderFrameRate = Math.max(summary.minRenderFrameRate,
289                      vote.refreshRateRanges.render.min);
290              summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, maxRenderFrameRate);
291  
292              // For display size, disable refresh rate switching and base mode refresh rate use only
293              // the first vote we come across (i.e. the highest priority vote that includes the
294              // attribute).
295              if (summary.height == Vote.INVALID_SIZE && summary.width == Vote.INVALID_SIZE
296                      && vote.height > 0 && vote.width > 0) {
297                  summary.width = vote.width;
298                  summary.height = vote.height;
299              }
300              if (!summary.disableRefreshRateSwitching && vote.disableRefreshRateSwitching) {
301                  summary.disableRefreshRateSwitching = true;
302              }
303              if (summary.appRequestBaseModeRefreshRate == 0f
304                      && vote.appRequestBaseModeRefreshRate > 0f) {
305                  summary.appRequestBaseModeRefreshRate = vote.appRequestBaseModeRefreshRate;
306              }
307  
308              if (mLoggingEnabled) {
309                  Slog.w(TAG, "Vote summary for priority " + Vote.priorityToString(priority)
310                          + ": " + summary);
311              }
312          }
313      }

summarizeVotes 方法

  1. 对 Votes 的 "屏幕刷新率" 范围取交集,成为目标 "屏幕刷新率"

  2. 对 Votes 的 "应用刷新率" 范围取交集,成为目标 "应用刷新率"

  3. 如果 Votes 包含具有 "应用期望的 base mode 刷新率" 的 Vote,将其中 priority 最高的设置为目标 "base mode 刷新率"

注意,如果目标 Votes map 中没有满足目标 priority 范围的 Vote,返回的 VoteSummary 对象的刷新率范围就是 [0,无限大]

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java220      private static final class VoteSummary {
221          public float minPhysicalRefreshRate;
222          public float maxPhysicalRefreshRate;
223          public float minRenderFrameRate;
224          public float maxRenderFrameRate;
225          public int width;
226          public int height;
227          public boolean disableRefreshRateSwitching;
228          public float appRequestBaseModeRefreshRate;
229  
230          VoteSummary() {
231              reset();
232          }
233  
234          public void reset() {
235              minPhysicalRefreshRate = 0f;
236              maxPhysicalRefreshRate = Float.POSITIVE_INFINITY;
237              minRenderFrameRate = 0f;
238              maxRenderFrameRate = Float.POSITIVE_INFINITY;
239              width = Vote.INVALID_SIZE;
240              height = Vote.INVALID_SIZE;
241              disableRefreshRateSwitching = false;
242              appRequestBaseModeRefreshRate = 0f;
243          }
...

DesiredDisplayModeSpecs getDesiredDisplayModeSpecs 方法代码比较长,下面分成 7 段分析。

第一段。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java365      /**
366       * Calculates the refresh rate ranges and display modes that the system is allowed to freely
367       * switch between based on global and display-specific constraints.
368       *
369       * @param displayId The display to query for.
370       * @return The ID of the default mode the system should use, and the refresh rate range the
371       * system is allowed to switch between.
372       */
373      @NonNull
374      public DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(int displayId) {
375          synchronized (mLock) {// 获取目标 display id 的 Votes map
376              SparseArray<Vote> votes = mVotesStorage.getVotes(displayId);// 目标 display id 支持的模式(modes)列表
377              Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);// 目标 display id 默认的模式(mode)
378              Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
379              if (modes == null || defaultMode == null) {
380                  Slog.e(TAG,
381                          "Asked about unknown display, returning empty display mode specs!"
382                                  + "(id=" + displayId + ")");
383                  return new DesiredDisplayModeSpecs();
384              }
385  
386              ArrayList<Display.Mode> availableModes = new ArrayList<>();
387              availableModes.add(defaultMode);
388              VoteSummary primarySummary = new VoteSummary();
389              int lowestConsideredPriority = Vote.MIN_PRIORITY;
390              int highestConsideredPriority = Vote.MAX_PRIORITY;
391  
392              if (mAlwaysRespectAppRequest) {
393                  lowestConsideredPriority = Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE;
394                  highestConsideredPriority = Vote.PRIORITY_APP_REQUEST_SIZE;
395              }
396  

第二段。

从 [Vote.MIN_PRIORITY,Vote.MAX_PRIORITY] 至 [Vote.MAX_PRIORITY,Vote.MAX_PRIORITY] 逐步缩小 priority 范围,调用 summarizeVotes 方法,直到 summarizeVotes 返回的 VoteSummary 能与目标 display id 支持的模式(modes)匹配:

  • 显示设备支持多种模式,每种模式有固定的刷新率。

  • 目标是寻找一个与我们的 Votes map 尽可能匹配的显示模式(至少要匹配最高优先级 priority 的 Vote)。

  • 最优的情况当然是这个显示模式满足所有 priority 的 Votes 期望,所以一开始匹配的是 [Vote.MIN_PRIORITY,Vote.MAX_PRIORITY] 范围内的 Votes 交集。

  • 最差的情况是只要匹配最高优先级(Vote.MAX_PRIORITY)的 Vote 就行。

  • 最最差的情况是匹配不到,即 availableModes 为空。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java397              // We try to find a range of priorities which define a non-empty set of allowed display
398              // modes. Each time we fail we increase the lowest priority.
399              while (lowestConsideredPriority <= highestConsideredPriority) {// (1)
400                  summarizeVotes(
401                          votes, lowestConsideredPriority, highestConsideredPriority, primarySummary);
402  
403                  // If we don't have anything specifying the width / height of the display, just use
404                  // the default width and height. We don't want these switching out from underneath
405                  // us since it's a pretty disruptive behavior.
406                  if (primarySummary.height == Vote.INVALID_SIZE
407                          || primarySummary.width == Vote.INVALID_SIZE) {
408                      primarySummary.width = defaultMode.getPhysicalWidth();
409                      primarySummary.height = defaultMode.getPhysicalHeight();
410                  }
411  // (2)
412                  availableModes = filterModes(modes, primarySummary);// (3)
413                  if (!availableModes.isEmpty()) {
414                      if (mLoggingEnabled) {
415                          Slog.w(TAG, "Found available modes=" + availableModes
416                                  + " with lowest priority considered "
417                                  + Vote.priorityToString(lowestConsideredPriority)
418                                  + " and constraints: "
419                                  + "width=" + primarySummary.width
420                                  + ", height=" + primarySummary.height
421                                  + ", minPhysicalRefreshRate="
422                                  + primarySummary.minPhysicalRefreshRate
423                                  + ", maxPhysicalRefreshRate="
424                                  + primarySummary.maxPhysicalRefreshRate
425                                  + ", minRenderFrameRate=" + primarySummary.minRenderFrameRate
426                                  + ", maxRenderFrameRate=" + primarySummary.maxRenderFrameRate
427                                  + ", disableRefreshRateSwitching="
428                                  + primarySummary.disableRefreshRateSwitching
429                                  + ", appRequestBaseModeRefreshRate="
430                                  + primarySummary.appRequestBaseModeRefreshRate);
431                      }
432                      break;
433                  }
434  
435                  if (mLoggingEnabled) {
436                      Slog.w(TAG, "Couldn't find available modes with lowest priority set to "
437                              + Vote.priorityToString(lowestConsideredPriority)
438                              + " and with the following constraints: "
439                              + "width=" + primarySummary.width
440                              + ", height=" + primarySummary.height
441                              + ", minPhysicalRefreshRate=" + primarySummary.minPhysicalRefreshRate
442                              + ", maxPhysicalRefreshRate=" + primarySummary.maxPhysicalRefreshRate
443                              + ", minRenderFrameRate=" + primarySummary.minRenderFrameRate
444                              + ", maxRenderFrameRate=" + primarySummary.maxRenderFrameRate
445                              + ", disableRefreshRateSwitching="
446                              + primarySummary.disableRefreshRateSwitching
447                              + ", appRequestBaseModeRefreshRate="
448                              + primarySummary.appRequestBaseModeRefreshRate);
449                  }
450  
451                  // If we haven't found anything with the current set of votes, drop the
452                  // current lowest priority vote.// (4)
453                  lowestConsideredPriority++;
454              }

第三段。

调用 summarizeVotes 方法,返回表示 [Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF,Vote.MAX_PRIORITY] priority 范围内 Votes 策略交集的 VoteSummary,即 appRequestSummary。

APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF 的值是 4,优先级高于 "用户(user)设置的最低策略(3)",低于 "应用(app)设置的 base mode 刷新率策略(5)"和 "用户(user)设置的策略(7)"。

APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF = PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE = 4。

注意到,summarizeVotes 返回的 appRequestSummary 可能是 primarySummary 的子集,也可能是 primarySummary 的父集。

而在 summarizeVotes 返回后,会对 appRequestSummary 做一个调整,保证 appRequestSummary 是 primarySummary 的父集。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java455  
456              if (mLoggingEnabled) {
457                  Slog.i(TAG,
458                          "Primary physical range: ["
459                                  + primarySummary.minPhysicalRefreshRate
460                                  + " "
461                                  + primarySummary.maxPhysicalRefreshRate
462                                  + "] render frame rate range: ["
463                                  + primarySummary.minRenderFrameRate
464                                  + " "
465                                  + primarySummary.maxRenderFrameRate
466                                  + "]");
467              }
468  // (1)
469              VoteSummary appRequestSummary = new VoteSummary();
470              summarizeVotes(
471                      votes,
472                      Vote.APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF,
473                      Vote.MAX_PRIORITY,
474                      appRequestSummary);// (2) 调整 appRequestSummary 的目标范围,保证 appRequestSummary 是 primarySummary 的父集
475              appRequestSummary.minPhysicalRefreshRate =
476                      Math.min(appRequestSummary.minPhysicalRefreshRate,
477                              primarySummary.minPhysicalRefreshRate);
478              appRequestSummary.maxPhysicalRefreshRate =
479                      Math.max(appRequestSummary.maxPhysicalRefreshRate,
480                              primarySummary.maxPhysicalRefreshRate);
481              appRequestSummary.minRenderFrameRate =
482                      Math.min(appRequestSummary.minRenderFrameRate,
483                              primarySummary.minRenderFrameRate);
484              appRequestSummary.maxRenderFrameRate =
485                      Math.max(appRequestSummary.maxRenderFrameRate,
486                              primarySummary.maxRenderFrameRate);
487              if (mLoggingEnabled) {
488                  Slog.i(TAG,
489                          "App request range: ["
490                                  + appRequestSummary.minPhysicalRefreshRate
491                                  + " "
492                                  + appRequestSummary.maxPhysicalRefreshRate
493                                  + "] Frame rate range: ["
494                                  + appRequestSummary.minRenderFrameRate
495                                  + " "
496                                  + appRequestSummary.maxRenderFrameRate
497                                  + "]");
498              }
499  

第四段。

调用 selectBaseMode 方法,从 availableModes 中选择一个 base mode。

availableModes 即通过第二段代码过滤出的匹配 Votes 策略(primarySummary)的 modes。

selectBaseMode 方法在 availableModes 中,

  1. 选择一个刷新率与应用设置的期望 base mode 刷新率(primarySummary.appRequestBaseModeRefreshRate)相同的 mode 作为 base mode;

  2. 如果应用没有设置期望 base mode 刷新率,则选择一个与 defaultMode 拥有相同刷新率的 mode 作为 base mode

  3. 如果 availableModes 列表中没有满足条件的 mode,则选择列表中首个 mode 作为 base mode

  4. 如果 availableModes 为空,则返回 null

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java// (1)
500              Display.Mode baseMode = selectBaseMode(primarySummary, availableModes, defaultMode);
501              if (baseMode == null) {
502                  Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling"
503                          + " back to the default mode. Display = " + displayId + ", votes = " + votes
504                          + ", supported modes = " + Arrays.toString(modes));
505  
506                  float fps = defaultMode.getRefreshRate();
507                  final RefreshRateRange range = new RefreshRateRange(fps, fps);
508                  final RefreshRateRanges ranges = new RefreshRateRanges(range, range);
509                  return new DesiredDisplayModeSpecs(defaultMode.getModeId(),
510                          /*allowGroupSwitching */ false,
511                          ranges, ranges);
512              }
513  

selectBaseMode 方法代码如下:

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java315      private boolean equalsWithinFloatTolerance(float a, float b) {
316          return a >= b - FLOAT_TOLERANCE && a <= b + FLOAT_TOLERANCE;
317      }
318  
319      private Display.Mode selectBaseMode(VoteSummary summary,
320              ArrayList<Display.Mode> availableModes, Display.Mode defaultMode) {
321          // The base mode should be as close as possible to the app requested mode. Since all the
322          // available modes already have the same size, we just need to look for a matching refresh
323          // rate. If the summary doesn't include an app requested refresh rate, we'll use the default
324          // mode refresh rate. This is important because SurfaceFlinger can do only seamless switches
325          // by default. Some devices (e.g. TV) don't support seamless switching so the mode we select
326          // here won't be changed.
327          float preferredRefreshRate =
328                  summary.appRequestBaseModeRefreshRate > 0
329                          ? summary.appRequestBaseModeRefreshRate : defaultMode.getRefreshRate();
330          for (Display.Mode availableMode : availableModes) {
331              if (equalsWithinFloatTolerance(preferredRefreshRate, availableMode.getRefreshRate())) {
332                  return availableMode;
333              }
334          }
335  
336          // If we couldn't find a mode id based on the refresh rate, it means that the available
337          // modes were filtered by the app requested size, which is different that the default mode
338          // size, and the requested app refresh rate was dropped from the summary due to a higher
339          // priority vote. Since we don't have any other hint about the refresh rate,
340          // we just pick the first.
341          return !availableModes.isEmpty() ? availableModes.get(0) : null;
342      }

第五段。

处理 "禁止 mode switch"。

"禁止 mode switch" 有三种:

  1. mModeSwitchingType 设置为 DisplayManager.SWITCHING_TYPE_NONE(最强

    这种情况下,会调较 primarySummary 和 appRequestSummary,使 primarySummary 和 appRequestSummary 固定 "屏幕刷新率" 和 "应用刷新率"。即,屏幕刷新率和平台的渲染帧率不会自动变化
  2. mModeSwitchingType 设置为 DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY(次强

    这种情况下,会调较 primarySummary 和 appRequestSummary,使 primarySummary 固定 "屏幕刷新率" 和 "应用刷新率",使 appRequestSummary 固定 "屏幕刷新率"。即,屏幕刷新率不会自动变化,但平台的渲染帧率可能会自动变化
  3. primarySummary.disableRefreshRateSwitching 为 true(如果 Votes map 中有一个用 forBaseModeRefreshRate 方法构造的 Vote,这些 Votes 的 summary 的 disableRefreshRateSwitching 就会是 true)

    这种情况下,会调较 primarySummary,使该 primarySummary 固定 "屏幕刷新率"。即屏幕刷新率和平台的渲染帧率都可能会自动变化
514              boolean modeSwitchingDisabled =
515                      mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE
516                              || mModeSwitchingType
517                                  == DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
518  
519              if (modeSwitchingDisabled || primarySummary.disableRefreshRateSwitching) {
520                  float fps = baseMode.getRefreshRate();
521                  disableModeSwitching(primarySummary, fps);
522                  if (modeSwitchingDisabled) {
523                      disableModeSwitching(appRequestSummary, fps);
524                      disableRenderRateSwitching(primarySummary, fps);
525  
526                      if (mModeSwitchingType == DisplayManager.SWITCHING_TYPE_NONE) {
527                          disableRenderRateSwitching(appRequestSummary, fps);
528                      }
529                  }
530              }
531 

disableModeSwitching 方法的作用是将目标 VoteSummary 调较为禁止 mode switch

关键是将目标 VoteSummary 的屏幕刷新率固定,从而不需要切换显示 mode。

其做法是,

  1. 将 VoteSummary 的屏幕刷新率固定为目标 fps;

  2. 如果 VoteSummary 的最大渲染帧率高于目标 fps,则将最大渲染帧率设置为目标 fps。

例如,

  假设 VoteSummary 原本的屏幕刷新率范围是 [90,120],渲染帧率要求范围是 [50, 90],base mode 的刷新率是 90

  • 屏幕刷新率范围会调整为 [90, 90]

  • 渲染帧率范围不调整,仍为 [50, 90]

  假如 VoteSummary 原本的屏幕刷新率范围是 [60,90],渲染帧率要求范围是 [50, 90],base mode 的刷新率是 60

  • 屏幕刷新率范围会调整为 [60, 60]

  • 渲染帧率范围调整为 [50,60]

disableRenderRateSwitching 方法的作用是将目标 VoteSummary 调较为禁止渲染帧率 switch

其做法是

  1. 首先修改 VoteSummary 的渲染帧率范围最大值,修改成与最小值一样;

  2. 检查目标 fps(base mode 的刷新率)是否与 VoteSummary 的渲染帧率要求匹配,简单说就是目标 fps 能够被渲染帧率整除。如果不能,则将 VoteSummary 的渲染帧率范围最大值、最小值都修改为 目标 fps。

例如

  假设 VoteSummary 渲染帧率要求范围是 [50, 90],base mode 的刷新率是 90

  • 将渲染帧率范围调整为 [90, 90]

  假设 VoteSummary 渲染帧率要求范围是 [50, 90],base mode 的刷新率是 60

  • 先将渲染帧率范围调整为 [90, 90]

  • 后将渲染帧率范围调整为 [60, 60]

注:这里的渲染帧率即应用刷新率。

344      private void disableModeSwitching(VoteSummary summary, float fps) {
345          summary.minPhysicalRefreshRate = summary.maxPhysicalRefreshRate = fps;
346          summary.maxRenderFrameRate = Math.min(summary.maxRenderFrameRate, fps);
347  
348          if (mLoggingEnabled) {
349              Slog.i(TAG, "Disabled mode switching on summary: " + summary);
350          }
351      }
352  
353      private void disableRenderRateSwitching(VoteSummary summary, float fps) {
354          summary.minRenderFrameRate = summary.maxRenderFrameRate;
355  
356          if (!isRenderRateAchievable(fps, summary)) {
357              summary.minRenderFrameRate = summary.maxRenderFrameRate = fps;
358          }
359  
360          if (mLoggingEnabled) {
361              Slog.i(TAG, "Disabled render rate switching on summary: " + summary);
362          }
363      }

第六段。

处理 "允许 group switch"。

532              boolean allowGroupSwitching =
533                      mModeSwitchingType == DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;

第七段。

返回 DesiredDisplayModeSpecs 对象。

534  
535              return new DesiredDisplayModeSpecs(baseMode.getModeId(),
536                      allowGroupSwitching,
537                      new RefreshRateRanges(
538                              new RefreshRateRange(
539                                      primarySummary.minPhysicalRefreshRate,
540                                      primarySummary.maxPhysicalRefreshRate),
541                              new RefreshRateRange(
542                                  primarySummary.minRenderFrameRate,
543                                  primarySummary.maxRenderFrameRate)),
544                      new RefreshRateRanges(
545                              new RefreshRateRange(
546                                      appRequestSummary.minPhysicalRefreshRate,
547                                      appRequestSummary.maxPhysicalRefreshRate),
548                              new RefreshRateRange(
549                                      appRequestSummary.minRenderFrameRate,
550                                      appRequestSummary.maxRenderFrameRate)));
551          }
552      }

4.3.6 nativeSetDesiredDisplayModeSpecs

将 Java 的 DesiredDisplayModeSpecs 对象转成 C++ 的 DisplayModeSpecs 对象。

调用 C++ 方法 SurfaceComposerClient::setDesiredDisplayModeSpecs 将 DisplayModeSpecs 应用到 SurfaceFlinger。

// frameworks/base/core/jni/android_view_SurfaceControl.cpp1298  static jboolean nativeSetDesiredDisplayModeSpecs(JNIEnv* env, jclass clazz, jobject tokenObj,
1299                                                   jobject DesiredDisplayModeSpecs) {
1300      sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
1301      if (token == nullptr) return JNI_FALSE;
1302  
1303      const auto makeRanges = [env](jobject obj) {
1304          const auto makeRange = [env](jobject obj) {
1305              gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange range;
1306              range.min = env->GetFloatField(obj, gRefreshRateRangeClassInfo.min);
1307              range.max = env->GetFloatField(obj, gRefreshRateRangeClassInfo.max);
1308              return range;
1309          };
1310  
1311          gui::DisplayModeSpecs::RefreshRateRanges ranges;
1312          ranges.physical = makeRange(env->GetObjectField(obj, gRefreshRateRangesClassInfo.physical));
1313          ranges.render = makeRange(env->GetObjectField(obj, gRefreshRateRangesClassInfo.render));
1314          return ranges;
1315      };
1316  // (1)
1317      gui::DisplayModeSpecs specs;
1318      specs.defaultMode = env->GetIntField(DesiredDisplayModeSpecs,
1319                                           gDesiredDisplayModeSpecsClassInfo.defaultMode);
1320      specs.allowGroupSwitching =
1321              env->GetBooleanField(DesiredDisplayModeSpecs,
1322                                   gDesiredDisplayModeSpecsClassInfo.allowGroupSwitching);
1323  // (2)
1324      specs.primaryRanges =
1325              makeRanges(env->GetObjectField(DesiredDisplayModeSpecs,
1326                                             gDesiredDisplayModeSpecsClassInfo.primaryRanges));// (3)
1327      specs.appRequestRanges =
1328              makeRanges(env->GetObjectField(DesiredDisplayModeSpecs,
1329                                             gDesiredDisplayModeSpecsClassInfo.appRequestRanges));
1330  // (4)
1331      size_t result = SurfaceComposerClient::setDesiredDisplayModeSpecs(token, specs);
1332      return result == NO_ERROR ? JNI_TRUE : JNI_FALSE;
1333  }

可以看到,SurfaceFlinger 接收的 DisplayModeSpecs 设置实际上只包含几个信息:

  1. 默认的显示模式(即 framework 逻辑中的 base mode)

  2. Primary Range(主屏幕的刷新率范围,即 framework 逻辑中的 primarySummary 指定的 "屏幕刷新率" 范围和 "应用刷新率" 范围)

  3. APP Request Range(应用请求的刷新率范围,即 framework 逻辑中的 appRequestRanges 指定的 "屏幕刷新率" 范围和 "应用刷新率" 范围)

4.3.7 mode 与 VoteSummary 匹配规则

即 filterModes 方法。

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java570      private ArrayList<Display.Mode> filterModes(Display.Mode[] supportedModes,
571              VoteSummary summary) {// 目标 VoteSummary 无效。// 即 VoteSummary 的目标刷新率范围的最小值比最大值还要大,这不是一个合理的范围。// 允许有 0.01(FLOAT_TOLERANCE)的误差
572          if (summary.minRenderFrameRate > summary.maxRenderFrameRate + FLOAT_TOLERANCE) {
573              if (mLoggingEnabled) {
574                  Slog.w(TAG, "Vote summary resulted in empty set (invalid frame rate range)"
575                          + ": minRenderFrameRate=" + summary.minRenderFrameRate
576                          + ", maxRenderFrameRate=" + summary.maxRenderFrameRate);
577              }
578              return new ArrayList<>();
579          }
580  
581          ArrayList<Display.Mode> availableModes = new ArrayList<>();// 如果应用设置了期望的 base mode 刷新率,则先将 missingBaseModeRefreshRate 变量置为 true
582          boolean missingBaseModeRefreshRate = summary.appRequestBaseModeRefreshRate > 0f;// 遍历显示设备支持的所有 modes
583          for (Display.Mode mode : supportedModes) {// 如果该 mode 的分辨率与目标 VoteSummary 的要求不符,则忽略该 mode
584              if (mode.getPhysicalWidth() != summary.width
585                      || mode.getPhysicalHeight() != summary.height) {
586                  if (mLoggingEnabled) {
587                      Slog.w(TAG, "Discarding mode " + mode.getModeId() + ", wrong size"
588                              + ": desiredWidth=" + summary.width
589                              + ": desiredHeight=" + summary.height
590                              + ": actualWidth=" + mode.getPhysicalWidth()
591                              + ": actualHeight=" + mode.getPhysicalHeight());
592                  }
593                  continue;
594              }// 如果该 mode 的刷新率不在目标 VoteSummary 的屏幕刷新率范围内,则忽略该 mode// 允许 0.01 的误差
595              final float physicalRefreshRate = mode.getRefreshRate();
596              // Some refresh rates are calculated based on frame timings, so they aren't *exactly*
597              // equal to expected refresh rate. Given that, we apply a bit of tolerance to this
598              // comparison.
599              if (physicalRefreshRate < (summary.minPhysicalRefreshRate - FLOAT_TOLERANCE)
600                      || physicalRefreshRate > (summary.maxPhysicalRefreshRate + FLOAT_TOLERANCE)) {
601                  if (mLoggingEnabled) {
602                      Slog.w(TAG, "Discarding mode " + mode.getModeId()
603                              + ", outside refresh rate bounds"
604                              + ": minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate
605                              + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate
606                              + ", modeRefreshRate=" + physicalRefreshRate);
607                  }
608                  continue;
609              }
610  
611              // The physical refresh rate must be in the render frame rate range, unless
612              // frame rate override is supported.// 如果不支持 mSupportsFrameRateOverride 功能,// 那么该 mode 的刷新率还应该在目标 VoteSummary 的渲染帧率范围内,否则忽略该 mode// 允许 0.01 的误差
613              if (!mSupportsFrameRateOverride) {
614                  if (physicalRefreshRate < (summary.minRenderFrameRate - FLOAT_TOLERANCE)
615                          || physicalRefreshRate > (summary.maxRenderFrameRate + FLOAT_TOLERANCE)) {
616                      if (mLoggingEnabled) {
617                          Slog.w(TAG, "Discarding mode " + mode.getModeId()
618                                  + ", outside render rate bounds"
619                                  + ": minPhysicalRefreshRate=" + summary.minPhysicalRefreshRate
620                                  + ", maxPhysicalRefreshRate=" + summary.maxPhysicalRefreshRate
621                                  + ", modeRefreshRate=" + physicalRefreshRate);
622                      }
623                      continue;
624                  }
625              }
626  // 检查该 mode 的刷新率是否能够满足目标 VoteSummary 的渲染帧率要求,如果不满足则忽略该 mode
627              if (!isRenderRateAchievable(physicalRefreshRate, summary)) {
628                  if (mLoggingEnabled) {
629                      Slog.w(TAG, "Discarding mode " + mode.getModeId()
630                              + ", outside frame rate bounds"
631                              + ": minRenderFrameRate=" + summary.minRenderFrameRate
632                              + ", maxRenderFrameRate=" + summary.maxRenderFrameRate
633                              + ", modePhysicalRefreshRate=" + physicalRefreshRate);
634                  }
635                  continue;
636              }
637  
638              availableModes.add(mode);// 如果该 mode 的刷新率跟应用设置的期望 base mode 刷新率相同(误差在 0.01 内),// 则将 missingBaseModeRefreshRate 设置为 false。// missingBaseModeRefreshRate 为 false 的意思是:// -- 要么没有应用设置期望 base mode 的刷新率// -- 要么我们找到的符合 "目标屏幕刷新率" 和 "目标渲染帧率" 要求的 modes 中,至少有一个 mode 的刷新率是跟应用设置的期望 base mode 刷新率相等的
639              if (equalsWithinFloatTolerance(mode.getRefreshRate(),
640                      summary.appRequestBaseModeRefreshRate)) {
641                  missingBaseModeRefreshRate = false;
642              }
643          }// 如果符合 "目标屏幕刷新率" 和 "目标渲染帧率" 要求的 modes 的刷新率都跟应用设置的期望 base mode 刷新率不同,// 则直接返回一个空列表
644          if (missingBaseModeRefreshRate) {
645              return new ArrayList<>();
646          }
647  // 返回符合 "目标屏幕刷新率" 和 "目标渲染帧率" 要求的 modes 列表
648          return availableModes;
649      }
650  

检查目标 mode 的刷新率是否能够满足目标 VoteSummary 的渲染帧率要求:

Math.ceil() 向上取整,返回大于或等于函数参数的数值。

即对 "目标 mode 的刷新率 / 目标渲染帧率范围的最大值 - 0.01 " 向上取整。

divisor 即表示 "目标 mode 的刷新率" 是 "目标渲染帧率范围的最大值" 的多少倍。

isRenderRateAchievable 即检查目标 mode 的刷新率是否能够满足目标 VoteSummary 的渲染帧率要求。

例如,假设 VoteSummary 的渲染帧率要求范围是 [50, 70]

  • 如果目标 mode 的刷新率是 120,那么平台的渲染帧率可以调整为 60,就能满足 VoteSummary 的渲染帧率要求

  • 如果目标 mode 的刷新率是 90,那么平台的渲染帧率只能是90、45、30...,不能满足 VoteSummary 的渲染帧率要求

// frameworks/base/services/core/java/com/android/server/display/mode/DisplayModeDirector.java554      private boolean isRenderRateAchievable(float physicalRefreshRate, VoteSummary summary) {
555          // Check whether the render frame rate range is achievable by the mode's physical
556          // refresh rate, meaning that if a divisor of the physical refresh rate is in range
557          // of the render frame rate.
558          // For example for the render frame rate [50, 70]:
559          //   - 120Hz is in range as we can render at 60hz by skipping every other frame,
560          //     which is within the render rate range
561          //   - 90hz is not in range as none of the even divisors (i.e. 90, 45, 30)
562          //     fall within the acceptable render range.
563          final int divisor =
564                  (int) Math.ceil((physicalRefreshRate / summary.maxRenderFrameRate)
565                          - FLOAT_TOLERANCE);
566          float adjustedPhysicalRefreshRate = physicalRefreshRate / divisor;
567          return adjustedPhysicalRefreshRate >= (summary.minRenderFrameRate - FLOAT_TOLERANCE);
568      }

5. 用户设置界面

最新版本的 Android "设置" 模块中,添加了一个自动提升刷新率的功能(高刷)。

该功能就是通过修改 system settings "peak_refresh_rate" 设置高刷的。

设置菜单配置

// packages/apps/Settings/res/xml/display_settings.xml17 <PreferenceScreen
18     xmlns:android="http://schemas.android.com/apk/res/android"
19     xmlns:settings="http://schemas.android.com/apk/res-auto"
20     android:key="display_settings_screen"
21     android:title="@string/display_settings"
22     settings:keywords="@string/keywords_display">
...97     <PreferenceCategory
98         android:title="@string/category_name_display_controls">
99 
...
133 
134         <SwitchPreference
135             android:key="peak_refresh_rate"
136             android:title="@string/peak_refresh_rate_title"
137             android:summary="@string/peak_refresh_rate_summary"
138             settings:controller="com.android.settings.display.PeakRefreshRatePreferenceController"/>
...

菜单标题是 "流畅画面";

菜单描述是 "自动将某些内容的刷新率从 60 Hz 调高到 %d Hz。但会增加耗电量。"

    <string name="peak_refresh_rate_title" msgid="1878771412897140903">"流畅画面"</string><string name="peak_refresh_rate_summary" msgid="3627278682437562787">"自动将某些内容的刷新率从 60 Hz 调高到 <xliff:g id="ID_1">%1$s</xliff:g> Hz。但会增加耗电量。"</string>

代码

  • 功能是否使能(菜单是否显示)

功能使能的条件是

  1. 设置模块的 config.xml 配置 R.bool.config_show_smooth_display 为 true

  2. 平台支持的最大屏幕刷新率(mPeakRefreshRate)超过60(DEFAULT_REFRESH_RATE)

如果平台支持的最大屏幕刷新率都不超过 60,提高刷新率是不可能的。

// packages/apps/Settings/src/com/android/settings/display/PeakRefreshRatePreferenceController.java98      @Override
99      public int getAvailabilityStatus() {
100          if (mContext.getResources().getBoolean(R.bool.config_show_smooth_display)) {
101              return mPeakRefreshRate > DEFAULT_REFRESH_RATE ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
102          } else {
103              return UNSUPPORTED_ON_DEVICE;
104          }
105      }
  • 使能方法

设置 system settings "peak_refresh_rate" 的值为平台支持的最大屏幕刷新率。

// packages/apps/Settings/src/com/android/settings/display/PeakRefreshRatePreferenceController.java39  public class PeakRefreshRatePreferenceController extends TogglePreferenceController
40          implements LifecycleObserver, OnStart, OnStop {
...
107      @Override
108      public boolean isChecked() {
109          final float peakRefreshRate =
110                  Settings.System.getFloat(
111                          mContext.getContentResolver(),
112                          Settings.System.PEAK_REFRESH_RATE,
113                          getDefaultPeakRefreshRate());
114          return Math.round(peakRefreshRate) == Math.round(mPeakRefreshRate);
115      }117      @Override
118      public boolean setChecked(boolean isChecked) {
119          final float peakRefreshRate = isChecked ? mPeakRefreshRate : DEFAULT_REFRESH_RATE;
120          Log.d(TAG, "setChecked to : " + peakRefreshRate);
121  
122          return Settings.System.putFloat(
123                  mContext.getContentResolver(), Settings.System.PEAK_REFRESH_RATE, peakRefreshRate);
124      }

6. 默认配置

在小米 pad 和 oppo findx3 上,device_config <"display_manager", "peak_refresh_rate_default"> 未设置,因此默认的刷新率由 com.android.internal.R.integer.config_defaultPeakRefreshRate 设置。

255|yudi:/ $ device_config get display_manager peak_refresh_rate_default
null

只要修改 com.android.internal.R.integer.config_defaultPeakRefreshRate 配置即可设置默认刷新率。例如添加下面的内容,设置默认刷新率为 60。

// frameworks/base/core/res/res/values/config.xm<!-- The default peak refresh rate for a given device. --><integer name="config_defaultPeakRefreshRate">60</integer>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/48931.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零开始:在linux系统安装MongoDB数据完整指南 新手常用命令

1 前言 MongoDB 是为快速开发互联网应用而设计的数据库系统。MongoDB 的设计目标是极简、灵活、作为 Web 应用栈的一部分。MongoDB 的数据模型是面向文档的&#xff0c;所谓文档是一种类似于json的结构。 官网教程&#xff1a;https://www.mongodb.com/docs/manual/ 2 安装部…

【接口自动化_11课_框架实战:基于封装的框架进行实战演练-下】

目标&#xff1a; 在原有基础上进行框架优化&#xff0c;本内容不做强制要求&#xff0c;了解即可。 1. Faker&#xff08;伪造器&#xff09;模块生成数据 2. 实战&#xff1a;结合框架动态生成请求数据 3. 实战&#xff1a;响应进行动态断言处理 一、 Faker&#xff…

LoFTR关键点特征匹配算法环境构建与图像匹配测试Demo

0&#xff0c;LoFTR CVPR 2021论文《LoFTR: Detector-Free Local Feature Matching with Transformers》开源代码 1&#xff0c;项目主页 LoFTR: Detector-Free Local Feature Matching with Transformers 2&#xff0c;GItHub主页 GitHub - zju3dv/LoFTR: Code for "…

微软蓝屏事件对企业数字化转型有什么影响?

引言&#xff1a;从北京时间2024年7月19日&#xff08;周五&#xff09;下午2点多开始&#xff0c;全球大量Windows用户出现电脑崩溃、蓝屏死机、无法重启等情况。事发后&#xff0c;网络安全公司CrowdStrike称&#xff0c;收到大量关于Windows电脑出现蓝屏报告&#xff0c;公司…

畅游时空|虚拟世界初体验,元宇宙游戏如何开发?

在元宇宙中&#xff0c;用户可以通过虚拟身份进行互动、社交、工作和娱乐&#xff0c;体验与现实世界平行的生活和活动。元宇宙不仅仅是一个虚拟空间&#xff0c;更是一个融合了虚拟和现实的生态系统&#xff0c;具有巨大的发展潜力和应用前景。 在不断发展的数字环境中&#x…

简单几步,教你使用scikit-learn做分类和回归预测

前言 scikit-learn是基于Python的一个机器学习库&#xff0c;你可以在scikit-learn库中选择合适的模型&#xff0c;使用它训练数据集并对新数据集作出预测。 对于初学者来说&#xff0c;有一个共同的困惑&#xff1a; 怎么使用scikit-learn库中的模型做预测&#xff1f; 本文…

IOS微软语音转文本,lame压缩音频

在IOS开发中&#xff0c;用微软进行语音转文本操作&#xff0c;并将录音文件压缩后返回 项目中遇到了利用微软SDK进行实时录音转文本操作&#xff0c;如果操作失败&#xff0c;那么就利用原始音频文件通过网络请求操作&#xff0c;最终这份文件上传到阿里云保存&#xff0c;考…

MongoDB教程(十五):MongoDB原子操作

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…

11 深度推荐模型演化中的“范式替换“灵活组合

上一课时&#xff0c;我们介绍了 DIEN 模型添加了 RNN中 的 GRU&#xff0c;使模型获得了对序列数据的建模能力&#xff1b;而 DSIN 模型不仅使用了 RNN 中的 bi-LSTM&#xff0c;还使用了 Transformer 组件。 由此可见&#xff0c;在新的演化模型中&#xff0c;根据场景和数据…

Lua脚本简单理解

目录 1.安装 2.语法 2.1Lua数据类型 2.2变量 2.3lua循环 2.4流程控制 2.5函数 2.6运算符 2.7关系运算符 3.lua脚本在redis中的使用 3.1lua脚本再redis简单编写 3.2普通锁Lua脚本 3.3可重入锁lua脚本 1.安装 centos安装 安装指令&#xff1a; yum -y update yum i…

本地部署VMware ESXi服务实现无公网IP远程访问管理服务器

文章目录 前言1. 下载安装ESXi2. 安装Cpolar工具3. 配置ESXi公网地址4. 远程访问ESXi5. 固定ESXi公网地址 前言 在虚拟化技术日益成熟的今天&#xff0c;VMware ESXi以其卓越的性能和稳定性&#xff0c;成为了众多企业构建虚拟化环境的首选。然而&#xff0c;随着远程办公和跨…

CCS光源的高输出TH2系列平面光源

光源在机器视觉系统中起着重要作用&#xff0c;不同环境、场景及应用合适光源都不一样&#xff0c;今天我们来看看高输出TH2系列平面光源。它可以对应高速化的生产线&#xff0c;为提高生产效率做出贡献。 TH2系列光源的特点&#xff1a; 1、实现了更高一级的高亮度 实现了更…

谷粒商城实战笔记-56~57-商品服务-API-三级分类-修改-拖拽功能完成

文章目录 一&#xff0c;56-商品服务-API-三级分类-修改-拖拽功能完成二&#xff0c;57-商品服务-API-三级分类-修改-批量拖拽效果1&#xff0c;增加按钮2&#xff0c;多次拖拽一次保存完整代码 在构建商品服务API中的三级分类修改功能时&#xff0c;拖拽排序是一个直观且高效的…

Java | Leetcode Java题解之第260题只出现一次的数字III

题目&#xff1a; 题解&#xff1a; class Solution {public int[] singleNumber(int[] nums) {int xorsum 0;for (int num : nums) {xorsum ^ num;}// 防止溢出int lsb (xorsum Integer.MIN_VALUE ? xorsum : xorsum & (-xorsum));int type1 0, type2 0;for (int n…

Prometheus配置alertmanager告警

1、拉取镜像并运行 1、配置docker镜像源 [rootlocalhost ~]# vim /etc/docker/daemon.json {"registry-mirrors": ["https://dfaad.mirror.aliyuncs.com"] } [rootlocalhost ~]# systemctl daemon-reload [rootlocalhost ~]# systemctl restart docker2、…

刷题了: 151.翻转字符串里的单词 |卡码网:55.右旋转字符串

151.翻转字符串里的单词 题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/description/ 文章讲解:https://programmercarl.com/0151.%E7%BF%BB%E8%BD%AC%E5%AD%97%E7%AC%A6%E4%B8%B2%E9%87%8C%E7%9A%84%E5%8D%95%E8%AF%8D.html 视频讲解:https://www.bilibi…

vue2之jessibuca视频插件使用教程

vue2之jessibuca视频插件使用教程 jessibuca简介前期准备下载相关jsvue index.html文件引入 组件封装使用小知识 引入iconfont jessibuca简介 Jessibuca是一款开源的纯H5直播流播放器&#xff0c;通过Emscripten将音视频解码库编译成Js&#xff08;ams.js/wasm)运行于浏览器之中…

基于PyCharm在Windows系统上远程连接Linux服务器中Docker容器进行Python项目开发与部署

文章目录 摘要项目结构项目开发项目上线参考文章 摘要 本文介绍了如何在Windows 10系统上使用PyCharm专业版2024.1&#xff0c;通过Docker容器在阿里云CentOS 7.9服务器上进行Python项目的开发和生产部署。文章详细阐述了项目结构的搭建、PyCharm的使用技巧、以及如何将开发项…

12.Spring事务和事务传播机制

文章目录 1.为什么需要事务2.Spring 中事务的实现2.1 MySQL 中的事务使⽤2.2 Spring 编程式事务2.3 Spring 声明式事务&#xff08;自动&#xff09;2.3.1 Transactional 作⽤范围2.3.2 Transactional 参数说明2.3.3 注意事项2.3.4 Transactional ⼯作原理 3.事务隔离级别3.1 事…

vue+element的table合并单元格(竖着合并行)及合计行添加并计算

1 效果: 代码分析: 1 表格头配置: 2 懒得写的:自己复制吧 <el-table:data"tableData"style"width: 98%":height"height"v-loading"isLoading"stripe"false" :span-method"objectSpanMethod"show-summary:summ…