问题环境
系统版本:OpenHarmony-3.2-Release
问题现象
- 配置设备默认方向,例如修改为横屏显示,修改文件display_manager_config.xml 的buildInDefaultOrientation 参数值为2(Orientation::HORIZONTAL)。
源码中文件位于foundation/window/window_manager/resources/config/rk3568/display_manager_config.xml。
系统中文件位于/etc/window/resources/display_manager_config.xml。
- 系统启动后开机动画横竖屏切换,Launcher显示异常(偶现,去掉锁屏应用和锁屏服务后大概率出现)。
异常效果:
正常效果:
问题原因
- ScreenRotationController 初始化会设置rotationLockedRotation_属性初始值,而ScreenRotationController 初始化的触发点在开机动画窗口销毁时,此时间点在Launcher 的Window加载之后。
- Launcher 加载Window 时会设置SetScreenRotation (屏幕旋转角度),因为Launcher 的方向加载配置为AUTO_ROTATION_RESTRICTED (方向随传感器旋转),所以SetScreenRotation 会根据rotationLockedRotation_属性值设置旋转角度,而此时 rotationLockedRotation_属性并未被设置初始值,所以SetScreenRotation 设置的值取得是默认值0(如果配置为Orientation::HORIZONTAL,则应旋转90度,取值为1),导致问题的产生。
解决方案
调整ScreenRotationController 初始化时序,使ScreenRotationController 在Launcher 加载Window时触发。修改源码文件:foundation/window/window_manager/wmserver/src/window_node_container.cpp
-
WindowNodeContainer::RemoveWindowNode函数中,移除以下代码:
if (node->GetWindowType() == WindowType::WINDOW_TYPE_BOOT_ANIMATION) {
DisplayManagerServiceInner::GetInstance().SetGravitySensorSubscriptionEnabled();
}
修改后WindowNodeContainer::RemoveWindowNode函数代码:
// foundation/window/window_manager/wmserver/src/window_node_container.cpp
WMError WindowNodeContainer::RemoveWindowNode(sptr<WindowNode>& node, bool fromAnimation)
{
···
NotifyIfAvoidAreaChanged(node, AvoidControlType::AVOID_NODE_REMOVE);
DumpScreenWindowTree();
UpdateCameraFloatWindowStatus(node, false);
if (node->GetWindowType() == WindowType::WINDOW_TYPE_KEYGUARD) {
isScreenLocked_ = false;
SetBelowScreenlockVisible(node, true);
}
WLOGFD("RemoveWindowNode windowId: %{public}u end", node->GetWindowId());
RSInterfaces::GetInstance().SetAppWindowNum(GetAppWindowNum());
return WMError::WM_OK;
}
-
WindowNodeContainer::AddWindowNode函数中,在WLOGFD("AddWindowNode windowId: %{public}u end", node->GetWindowId());行代码前添加以下代码:
if (node->GetWindowType() == WindowType::WINDOW_TYPE_DESKTOP) {
DisplayManagerServiceInner::GetInstance().SetGravitySensorSubscriptionEnabled();
}
修改后WindowNodeContainer::AddWindowNode函数代码:
WMError WindowNodeContainer::AddWindowNode(sptr<WindowNode>& node, sptr<WindowNode>& parentNode, bool afterAnimation)
{
···
if (node->GetWindowType() == WindowType::WINDOW_TYPE_WALLPAPER) {
RemoteAnimation::NotifyAnimationUpdateWallpaper(node);
}
if (node->GetWindowType() == WindowType::WINDOW_TYPE_DESKTOP) {
DisplayManagerServiceInner::GetInstance().SetGravitySensorSubscriptionEnabled();
}
WLOGFD("AddWindowNode windowId: %{public}u end", node->GetWindowId());
RSInterfaces::GetInstance().SetAppWindowNum(GetAppWindowNum());
return WMError::WM_OK;
}
定位过程
-
落盘异常开机日志,查找SetRotation相关日志,发现系统启动过程中横竖屏被设置两次。
08-05 18:39:55.002 622 811 I C04201/AbstractScreenController: <722>SetRotation: Enter SetRotation, screenId: 0, rotation: 1, isFromWindow: 1
08-05 18:39:58.487 622 811 I C04201/AbstractScreenController: <722>SetRotation: Enter SetRotation, screenId: 0, rotation: 0, isFromWindow: 1 -
查找对应源码发现rotation代表含义。在系统启动时已成功设置旋转90度(水平),但又被设置为旋转0度(垂直),导致异常。
// foundation/window/window_manager/interfaces/innerkits/dm/dm_common.h
enum class Rotation : uint32_t {
ROTATION_0, // 不旋转,垂直
ROTATION_90, // 旋转90度,水平
ROTATION_180,
ROTATION_270,
};
// foundation/window/window_manager/dmserver/src/abstract_screen_controller.cpp
bool AbstractScreenController::SetRotation(ScreenId screenId, Rotation rotationAfter, bool isFromWindow)
{
WLOGFI("Enter SetRotation, screenId: %{public}" PRIu64 ", rotation: %{public}u, isFromWindow: %{public}u",
screenId, rotationAfter, isFromWindow);
···
} -
追踪设置旋转0度(垂直)操作日志。发现set orientation 时,orientation 被设置为8 ,对应源码含义为AUTO_ROTATION_RESTRICTED。
08-05 18:39:58.487 622 811 D C04201/AbstractScreenController: <627>set orientation. screen 0 orientation 8
08-05 18:39:58.487 622 811 D C04201/AbstractScreenController: <144>GetAbstractScreen: screenId: 0
08-05 18:39:58.487 622 811 D C04201/AbstractScreenController: <177>GetDefaultAbstractScreenId: GetDefaultAbstractScreenId, screen:0
08-05 18:39:58.487 622 811 D C04201/DisplayManagerService: <190>GetDefaultDisplayInfo: GetDefaultDisplayInfo 0
08-05 18:39:58.487 622 811 D C04201/AbstractScreenController: <177>GetDefaultAbstractScreenId: GetDefaultAbstractScreenId, screen:0
08-05 18:39:58.487 622 811 D C04201/DisplayManagerService: <190>GetDefaultDisplayInfo: GetDefaultDisplayInfo 0
08-05 18:39:58.487 622 811 I C04201/AbstractScreenController: <722>SetRotation: Enter SetRotation, screenId: 0, rotation: 0, isFromWindow: 1
// foundation/window/window_manager/dmserver/src/abstract_screen_controller.cpp
bool AbstractScreenController::SetOrientation(ScreenId screenId, Orientation newOrientation, bool isFromWindow)
{
WLOGD("set orientation. screen %{public}" PRIu64" orientation %{public}u", screenId, newOrientation);
···
}
// foundation/window/window_manager/interfaces/innerkits/dm/dm_common.h
enum class Orientation : uint32_t {
BEGIN = 0,
UNSPECIFIED = BEGIN,
VERTICAL = 1,
HORIZONTAL = 2,
REVERSE_VERTICAL = 3,
REVERSE_HORIZONTAL = 4,
SENSOR = 5,
SENSOR_VERTICAL = 6,
SENSOR_HORIZONTAL = 7,
AUTO_ROTATION_RESTRICTED = 8,
AUTO_ROTATION_PORTRAIT_RESTRICTED = 9,
AUTO_ROTATION_LANDSCAPE_RESTRICTED = 10,
LOCKED = 11,
END = LOCKED,
};
-
Launcher 在创建window 时会把PreferredOrientation 设置为Window.Orientation.AUTO_ROTATION_RESTRICTED。
// common/src/main/ets/default/manager/WindowManager.ts
createWindow(context: ServiceExtensionContext, name: string, windowType: number, loadContent: string,
isShow: boolean, callback?: Function) {
Window.create(context, name, windowType).then((win) => {
void win.setPreferredOrientation(Window.Orientation.AUTO_ROTATION_RESTRICTED);
···
}, (error) => {
Log.showError(TAG,createWindow, create error: ${JSON.stringify(error)}
);
});
} -
当Launcher 显示窗口时执行SetOrientation,isFromWindow 参数为true。
// foundation/window/window_manager/dmserver/src/abstract_screen_controller.cpp
bool AbstractScreenController::SetOrientation(ScreenId screenId, Orientation newOrientation, bool isFromWindow)
{
WLOGD("set orientation. screen %{public}" PRIu64" orientation %{public}u", screenId, newOrientation);
auto screen = GetAbstractScreen(screenId);
···
if (isFromWindow) {
ScreenRotationController::ProcessOrientationSwitch(newOrientation); // 执行方向选择
} else {
Rotation rotationAfter = screen->CalcRotation(newOrientation);
SetRotation(screenId, rotationAfter, false);
screen->rotation_ = rotationAfter;
}
if (!screen->SetOrientation(newOrientation)) {
WLOGE("fail to set rotation, screen %{public}" PRIu64"", screenId);
return false;
}
···
return true;
} -
因orientation 为AUTO_ROTATION_RESTRICTED ,会执行ProcessSwitchToSensorRelatedOrientation函数。
// foundation/window/window_manager/dmserver/src/screen_rotation_controller.cpp
void ScreenRotationController::ProcessOrientationSwitch(Orientation orientation)
{
if (!IsSensorRelatedOrientation(orientation)) {
ProcessSwitchToSensorUnrelatedOrientation(orientation);
} else {
ProcessSwitchToSensorRelatedOrientation(orientation, lastSensorRotationConverted_);
}
}
bool ScreenRotationController::IsSensorRelatedOrientation(Orientation orientation)
{
if ((orientation >= Orientation::UNSPECIFIED && orientation <= Orientation::REVERSE_HORIZONTAL) ||
orientation == Orientation::LOCKED) {
return false;
}
// AUTO_ROTATION_RESTRICTED 返回 true
return true;
} -
当rotationLockedRotation_与 GetCurrentDisplayRotation()不一致时会切换旋转角度。在此处增加日志打印 rotationLockedRotation_和 GetCurrentDisplayRotation()的值,发现在开机触发Launcher 设置屏幕旋转角度时GetCurrentDisplayRotation()函数获取的当前屏幕旋转角度为1 (水平)是正确的。而rotationLockedRotation_为0(垂直)。
// foundation/window/window_manager/dmserver/src/screen_rotation_controller.cpp
void ScreenRotationController::ProcessSwitchToSensorRelatedOrientation(
Orientation orientation, DeviceRotation sensorRotationConverted){
lastOrientationType_ = orientation;
switch (orientation) {
case Orientation::AUTO_ROTATION_RESTRICTED: {
if (isScreenRotationLocked_) {
SetScreenRotation(rotationLockedRotation_);
return;
}
[[fallthrough]];
}
···
}
}
void ScreenRotationController::SetScreenRotation(Rotation targetRotation){
if (targetRotation == GetCurrentDisplayRotation()) {
return;
}
DisplayManagerServiceInner::GetInstance().GetDefaultDisplay()->SetRotation(targetRotation);
DisplayManagerServiceInner::GetInstance().SetRotationFromWindow(defaultDisplayId_, targetRotation);
WLOGFI("dms: Set screen rotation: %{public}u", targetRotation);
} -
查看rotationLockedRotation_被设置的场景。分别增加日志,发现开机启动时SetScreenRotationLocked 函数不会被触发,而Init 函数则是在Launcher 启动后被触发,此时Launcher 已经把屏幕旋转角度设置为0 (垂直),rotationLockedRotation_的初始化值则会变成Launcher 设置后的参数0 (垂直)。而在Launcher 触发SetScreenRotation 时,rotationLockedRotation_还未被设置,此时取默认值0(垂直),导致异常的产生。
// foundation/window/window_manager/dmserver/src/screen_rotation_controller.cpp
void ScreenRotationController::Init()
{
ProcessRotationMapping();
currentDisplayRotation_ = GetCurrentDisplayRotation();
lastSensorDecidedRotation_ = currentDisplayRotation_;
rotationLockedRotation_ = currentDisplayRotation_;
}
void ScreenRotationController::SetScreenRotationLocked(bool isLocked)
{
if (isLocked) {
rotationLockedRotation_ = GetCurrentDisplayRotation();
}
isScreenRotationLocked_ = isLocked;
} -
ScreenRotationController::Init()的触发时机是在系统检测到启动完成后,关闭开机动画窗口时触发。如果此操作在Launcher 加载Window 之后,则会导致问题。改变ScreenRotationController::Init()的初始化时序,在Launcher 的window加载时初始化可以修复此问题。
// foundation/window/window_manager/wmserver/src/window_node_container.cpp
WMError WindowNodeContainer::RemoveWindowNode(sptr<WindowNode>& node, bool fromAnimation)
{
···
if (node->GetWindowType() == WindowType::WINDOW_TYPE_BOOT_ANIMATION) {
DisplayManagerServiceInner::GetInstance().SetGravitySensorSubscriptionEnabled();
}
···
return WMError::WM_OK;
}// foundation/window/window_manager/dmserver/src/display_manager_service.cpp
void DisplayManagerService::SetGravitySensorSubscriptionEnabled()
{
···
SensorConnector::SubscribeRotationSensor();
}// foundation/window/window_manager/dmserver/src/sensor_connector.cpp
void SensorConnector::SubscribeRotationSensor()
{
WLOGFI("dms: subscribe rotation-related sensor");
ScreenRotationController::Init();
···
}
知识分享
如果应用的方向需要随系统切换,可以在module.json5 的ability 中配置orientation 为auto_rotation_restricted。
为了能让大家更好的学习鸿蒙 (OpenHarmony) 开发技术,这边特意整理了《鸿蒙 (OpenHarmony)开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05
《鸿蒙 (OpenHarmony)开发学习手册》
入门必看:https://qr21.cn/FV7h05
- 应用开发导读(ArkTS)
- ......
HarmonyOS 概念:https://qr21.cn/FV7h05
- 系统定义
- 技术架构
- 技术特性
- 系统安全
如何快速入门?:https://qr21.cn/FV7h05
- 基本概念
- 构建第一个ArkTS应用
- 构建第一个JS应用
- ......
开发基础知识:https://qr21.cn/FV7h05
- 应用基础知识
- 配置文件
- 应用数据管理
- 应用安全管理
- 应用隐私保护
- 三方应用调用管控机制
- 资源分类与访问
- 学习ArkTS语言
- ......
基于ArkTS 开发:https://qr21.cn/FV7h05
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.......