news 2026/10/22 3:38:23

RK平台android12 动态调整屏幕方向

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RK平台android12 动态调整屏幕方向

RK平台 Android12 动态调整屏幕方向(persist 属性方案,免重新编译)

适用场景:RK 平台 Android 12 / Android 14,整机为固定安装方向(竖屏广告机、工控一体机、自助终端、门禁机等),无 gsensor,需要在不重新编译烧录整包的前提下调整系统开机默认方向。

0. 先说结论

原生方案用的是ro.surface_flinger.primary_display_orientation。它是只读属性,值在编译期就固化进build.prop,所以每改一次方向都要重新编译、重新烧录整包 —— 对经常要换屏、换安装方式的整机项目来说,这个代价太高。

本文的改法是把属性名换成persist.surface_flinger.primary_display_orientation。persist.前缀的属性运行时可写,setprop之后重启即生效,一个整机一个值,不用动编译产物。

但这条路有个很容易踩空的前提:persist.属性是落盘在/data/property/下的。如果/data因为分区加密被推迟到 late-fs 才挂载,而 SurfaceFlinger 启动得更早,那它读到的永远是默认值0—— 补丁打了、setprop 也设了,方向就是不变。第 2.2 节讲的就是这个坑。

⚠️ 两件事先讲清楚,避免预期错位:

  1. 这个方案解决的是「不用重新编译」,不是「不用重启」。SurfaceFlinger 只在启动时读一次这个属性,改完仍需重启。
  2. 第 2.2 节的修复要放弃/data分区加密,这是有安全代价的。只适合开发板、内部样机、数据不敏感的设备。量产出货机型请走第 5 节的替代思路。

1. 为什么原生方案不够用

先把两个属性的差别摆清楚,后面所有改动都围绕这张表:

对比项ro.surface_flinger.primary_display_orientation(原生)persist.surface_flinger.primary_display_orientation(本文方案)
可写性只读,编译期固化运行时可写(setprop)
取值来源SurfaceFlingerProperties::primary_display_orientation()property_get()直接读属性
存储位置/system/build.prop或vendor/build.prop/data/property/下
生效时机重新编译 + 重新烧录setprop后重启
前置依赖无/data必须已挂载
恢复出厂后不受影响被清掉,回落到默认值

关键点在于「存储位置」这一行。ro.属性住在系统分区,怎么重启都在;persist.属性住在数据分区,这既是它能被动态修改的原因,也是它读不到的原因 —— 同一个特性,两面性。

2. Android 12:改属性来源

2.1 修改方向属性判断

改动点在frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp的构造函数里:把原来那段编译期取值整段注释掉,换成读persist属性。

--- a/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp +++ b/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp @@ -427,7 +427,7 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI }(); useContextPriority = use_context_priority(true); - +/* using Values = SurfaceFlingerProperties::primary_display_orientation_values; switch (primary_display_orientation(Values::ORIENTATION_0)) { case Values::ORIENTATION_0: @@ -442,6 +442,25 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI internalDisplayOrientation = ui::ROTATION_270; break; } +*/ + char value_private[PROPERTY_VALUE_MAX]; + property_get("persist.surface_flinger.primary_display_orientation", value_private, "0"); + int temp = atoi(value_private); + ALOGD("primary_display_orientation is: %d",temp); + + switch (temp) { + case 0: + break; + case 90: + internalDisplayOrientation = ui::ROTATION_90; + break; + case 180: + internalDisplayOrientation = ui::ROTATION_180; + break; + case 270: + internalDisplayOrientation = ui::ROTATION_270; + break; + }

几个实现细节:

  • 默认值传"0"。/data没挂上、或者整机还没设过这个属性时,property_get会回落到"0",atoi得到 0,走case 0保持ui::ROTATION_0—— 也就是原生默认行为。这样补丁不会让没配置过的机器行为发生意外变化,很重要。
  • case直接用角度 90 / 180 / 270,而不是 1 / 2 / 3。现场调试、和客户对参数时不用在脑子里做映射,少一类低级错误。
  • 加一行ALOGD。整个方案最怕的就是「属性没读到」,有这行日志能立刻分辨是「读到的值不对」还是「读到了但方向逻辑没生效」。本文第 4 节的验证步骤会用到它。

2.2 关键坑:data 分区加密导致开机过程中读不到属性

现象:补丁打好了,setprop persist.surface_flinger.primary_display_orientation 90也执行了,getprop能查到这个值,但重启后方向纹丝不动。而如果把值写回ro.属性重新编译,一切正常。

这说明属性写进去了,但 SurfaceFlinger没读到。根因链条是这样的:

  1. SurfaceFlinger 在构造函数里执行property_get("persist.surface_flinger.primary_display_orientation", ...);
  2. persist.系列属性的持久化文件存放在/data/property/下;
  3. 而在默认 fstab 里,userdata分区带了fileencryption=...和latemount标记 —— 它需要等密钥服务就绪、在 late-fs 阶段才被挂载;
  4. 挂载时机和 SurfaceFlinger 的启动时机对不上,property_get执行时/data还不可用,属性服务拿不到值,返回默认"0";
  5. 结果就是temp = 0,方向保持 0 度。

修复思路:让/data在 SurfaceFlinger 读到属性之前就可用。需要改两个文件。

① 挂载时机:把 early / late 两段拆分的挂载合并成一次完整挂载

diff --git a/device/rockchip/common/rootdir/init.mount_all_early.rc b/device/rockchip/common/rootdir/init.mount_all_early.rc index 200d053ebf..6ec6af3d3b 100644 --- a/device/rockchip/common/rootdir/init.mount_all_early.rc +++ b/device/rockchip/common/rootdir/init.mount_all_early.rc @@ -4,16 +4,7 @@ on early-fs start vold on fs - mount_all /vendor/etc/fstab.${ro.hardware} --early + mount_all /vendor/etc/fstab.${ro.hardware} on late-fs # Start services for bootanim - start servicemanager - start hwcomposer-2-1 - start gralloc-2-0 - start surfaceflinger - start bootanim - - exec_start wait_for_keymaster - # Mount RW partitions which need run fsck - mount_all /vendor/etc/fstab.${ro.hardware} --late

原来on fs阶段只挂--early那一批,userdata这类latemount分区要等到on late-fs才挂 —— 而on late-fs里同时还在拉起surfaceflinger。改完之后,on fs阶段一次mount_all(不带 flag)把分区挂齐,时序就不会倒挂。

② 分区加密:去掉 userdata 的 fileencryption 标记

diff --git a/device/rockchip/common/scripts/fstab_tools/fstab.in b/device/rockchip/common/scripts/fstab_tools/fstab.in index 2ec6c26565..b8cef766e9 100755 --- a/device/rockchip/common/scripts/fstab_tools/fstab.in +++ b/device/rockchip/common/scripts/fstab_tools/fstab.in @@ -23,6 +23,6 @@ ${_block_prefix}odm /odm ext4 ro,barrier=1 ${_flags},first_stage_mount # For sdmmc /devices/platform/${_sdmmc_device}/mmc_host* auto auto defaults voldmanaged=sdcard1:auto # Full disk encryption has less effect on rk3326, so default to enable this. -/dev/block/by-name/userdata /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065 latemount,wait,check,fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,reservedsize=128M,checkpoint=fs +/dev/block/by-name/userdata /data f2fs noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065 latemount,wait,check,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,reservedsize=128M,checkpoint=fs # for ext4 #/dev/block/by-name/userdata /data ext4 discard,noatime,nosuid,nodev,noauto_da_alloc,data=ordered,user_xattr,barrier=1 latemount,wait,formattable,check,fileencryption=software,quota,reservedsize=128M,checkpoint=block

去掉fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized之后,/data不再需要解密即可挂载,也就不会卡在 late-fs 阶段等密钥。

两处改动是配套的:只改 rc 不改 fstab,/data依然要等加密密钥;只改 fstab 不改 rc,/data可能仍然被排在 late-fs 挂载序列里。要一起上。

⚠️这一步是有代价的,必须说清楚:注释掉fileencryption等于放弃/data分区加密。用户数据、账号 token、本地缓存全部明文可读。建议只在开发板、内部样机、或设备本身不承载敏感数据的场景(比如公网广告机)使用。如果整机要出货给客户、涉及账号 / 支付 / 客户数据,不要照抄这一段,请走第 5 节的替代思路。

3. Android 14:改动点变了

Android 14 上不能照搬 Android 12 的改法,因为物理显示方向的取值位置和来源都变了:从构造函数移到了SurfaceFlinger::getPhysicalDisplayOrientation(),而且原来的值是从 HWC(getHwComposer().getPhysicalDisplayOrientation(*id))上报上来的,不是 ro 属性。

diff --git a/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp b/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp index 53f6d379416..87cfc400b71 100644 --- a/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp +++ b/frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp @@ -2795,15 +2795,20 @@ ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId, if (!id) { return ui::ROTATION_0; } + + char value_private[PROPERTY_VALUE_MAX]; + property_get("persist.surface_flinger.primary_display_orientation", value_private, "0"); + int temp = atoi(value_private); + ALOGD("primary_display_orientation is: %d",temp); if (!mIgnoreHwcPhysicalDisplayOrientation && getHwComposer().getComposer()->isSupported( Hwc2::Composer::OptionalFeature::PhysicalDisplayOrientation)) { - switch (getHwComposer().getPhysicalDisplayOrientation(*id)) { - case Hwc2::AidlTransform::ROT_90: + switch (temp) { + case 90: return ui::ROTATION_90; - case Hwc2::AidlTransform::ROT_180: + case 180: return ui::ROTATION_180; - case Hwc2::AidlTransform::ROT_270: + case 270: return ui::ROTATION_270; default: return ui::ROTATION_0;

改动本质很简单:switch的入参从「HWC 上报的物理方向」换成「我们自己读的属性值」,其余控制流不动。属性名、取值范围(0/90/180/270)、默认值都和 Android 12 保持一致 —— 一套配置在两个版本上通用,这一点对做多版本 SDK 的项目很省事。

⚠️ 有一个坑要注意:if条件里的mIgnoreHwcPhysicalDisplayOrientation由属性debug.sf.ignore_hwc_physical_display_orientation控制。它一旦为true,整个if块会被跳过,你新加的switch根本不会执行,函数会直接落到return ui::ROTATION_0。

RK 官方文档在 Android 14 的ro属性方案里建议加上这个属性,但那是给框架的另一条取值路径准备的。采用本文的 persist 方案时,请确认这个属性没有被设成true(或者干脆把if条件里这个判断一并去掉),否则会出现「属性读到了、日志也打了、方向还是不动」的二次踩坑。这一点建议在你的 SDK 上实测确认一遍。

4. 验证步骤

改完之后按这个顺序验证,每一步都能定位到具体环节:

# 1) 写入属性(需要 root 权限)adb root adb remount adb shell setprop persist.surface_flinger.primary_display_orientation90# 2) 确认值写进去了adb shell getprop persist.surface_flinger.primary_display_orientation# 3) 重启adb shellreboot# 4) 看 SurfaceFlinger 是否读到了值(就是补丁里那行 ALOGD)adb shell logcat-ball|grep"primary_display_orientation is"# 5) 看最终生效的物理方向adb shell dumpsys SurfaceFlinger|grep-iorientation

失败情况分两种,对应的问题完全不同:

情况 A:第 1 步setprop就被拒(Failed to set property)。说明这个新属性名没有在 SELinux 的property_contexts里登记,被策略拦了。先用adb shell setenforce 0复测一次:如果放行后能写进去,就确认是 SELinux 问题,补一条该属性名的 context 即可(persist.前缀允许运行时写,但具体哪个域能写仍由 SELinux 决定,这一点容易漏)。

情况 B:第 1 步成功、第 2 步能查到值,但第 4 步日志里temp是 0。说明属性没被读到,直接回到第 2.2 节 ——/data挂载时机没对上,或者/data被清过。

另外提醒一句:persist.属性存在/data/property/下,恢复出厂设置会连同/data一起清掉,方向会掉回默认值 0。如果需要「出厂永久固定方向、怎么恢复都变不回来」,那最终还是得回落到编译期的ro属性方案,persist 方案只适合「运行时现场调整」这个诉求。

5. 这个方案的边界与替代思路

能解决什么:一台设备不用重编整包就能改开机方向,现场换屏、换安装方式时非常省事。

不能解决什么:

  • 不能免重启 —— SurfaceFlinger 只在启动时读一次;
  • 不能免/data依赖 —— 属性住在数据分区,这是结构性约束;
  • 恢复出厂会重置;
  • 如果要绕开/data加密,就得分清这是开发设备还是出货设备。

替代思路(本文未实测,供参考):如果不想动/data加密,可以让属性值换一个「更早就能读到」的存放位置。思路是走 uboot 传参链路,把方向值作为androidboot.*之类的参数带进内核 cmdline,SurfaceFlinger 改为读/proc/cmdline里的这个参数。这样/data加密保留、/data挂载时序也不用改,改方向只需重烧 uboot 环境变量,比整包重编小得多。适合需要保留数据加密的量产机型。代价是多一条传参链路要维护,量产阶段要确认 uboot 与内核的参数约定稳定。

6. 完整改动清单

Android 版本文件改动内容
12frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp构造函数:把 ro 属性的编译期取值整段注释,改为property_get读 persist 属性后 switch
12device/rockchip/common/rootdir/init.mount_all_early.rcon fs阶段合并为一次完整mount_all,去掉--early/--late拆分
12device/rockchip/common/scripts/fstab_tools/fstab.inuserdata 去掉fileencryption(⚠️ 放弃 /data 加密)
14frameworks/native/services/surfaceflinger/SurfaceFlinger.cppgetPhysicalDisplayOrientation():switch入参由 HWC 上报值改为属性值

7. 小结

整件事的核心其实只有一句话:把「编译期只读的ro属性」换成「运行时可写的persist属性」,然后保证读属性的时候/data已经挂上了。

坑也都集中在这句话的后半段:

  1. persist属性住在/data/property/,而/data因为加密被排到 late-fs,比 SurfaceFlinger 启动还晚 —— 这是「补丁有效但方向不变」的唯一原因;
  2. 修它要动挂载时机和分区加密两处,而放弃/data加密是有安全代价的,得区分开发设备和出货设备;
  3. Android 14 的改动点从构造函数挪到了getPhysicalDisplayOrientation(),而且多了一个debug.sf.ignore_hwc_physical_display_orientation可以让整段逻辑失效。

深耕 Android 嵌入式与 RK 平台系统级开发多年,日常处理显示、存储加密、开机时序这类系统级问题。如果你在做 RK 平台整机项目时也遇到系统级的疑难问题,欢迎留言或私信交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 3:02:34

Autoware.Universe 样例运行:高精地图部署到 ~/autoware-map 与排错指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 18:55:54

MFC与Halcon图像显示桥接:窗口句柄绑定与双缓冲实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 7:29:39

Linux进程并发设计:从fork()到wait()的底层原理与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 22:02:11

Qt桌面软件授权期限控制:防篡改与跨平台实现方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 10:42:57

基于MP2642GR-Z与ATmega3250的串联电池组均衡方案设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/21 9:59:31

51单片机汇编驱动16×16点阵屏实现汉字左移动画

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华