1.问题描述
在OpenFDE 14上缩放应用窗口大小时,会出现标题栏宽度与应用窗口宽度无法保持同步变化的问题,在一些简单布局的应用缩放场景下,同步效果比较好,但对于较复杂布局的应用场景下,不同步的现象就比较明显,如下图所示。
2.原因分析
从Android 14开始,自由窗口模式下缩放窗口,CaptionWindow被放到SystemUI进程中刷新,应用窗口还是在应用进程中刷新,由于CaptionWindow的布局非常简单,只有几个Button控件,所以layout速度非常快,而对于Actvitity来说,会选择合适的时机进行刷新图层信息,因此Activity窗口的刷新频率远远跟不上CaptionWindow的刷新频率,且两者之间没有任何同步机制,所以在拖动时标题栏与Acitivity窗口的宽度就无法保持一致。
既然是由于Activity窗口刷新太慢导致,那么只能让标题栏窗口去同步Activity窗口大小,这样改动带来的副作用是最小的,这里我们从SurfaceFlinger的角度进行分析,因为所有图层都会由SurfaceFlinger提交给hwc显示,那么我们可以在SurfaceFlinger中对标题栏窗口和应用窗口进行一次宽度同步,防止标题栏宽度显示超出应用窗口。
根据dumpsys SurfaceFlinger信息,可以看到应用窗口和标题栏窗口的layer的关系,如下所示。
ROOT ├─ Display 0 name="内置屏幕"#1 │ ├─ WindowedMagnification:0:31#2 │ │ ├─ HideDisplayCutout:0:14#3 │ │ │ └─ OneHanded:0:14#4 │ │ │ ├─ FullscreenMagnification:0:12#5 │ │ │ │ ├─ DefaultTaskDisplayArea#7 │ │ │ │ │ └─ Task=224#326 │ │ │ │ │ ├─ ActivityRecord{bdc594a u0 com.tencen[...]ntv2.activity.MainActivity t224}#339 │ │ │ │ │ │ ├─ 942c0bb ActivityRecordInputSink com.[...]ssistantv2.activity.MainActivity#342 │ │ │ │ │ │ └─ d666f1 com.tencent.android.qqdownloa[...]ssistantv2.activity.MainActivity#343 │ │ │ │ │ │ └─ com.tencent.android.qqdownloader/com[...]ssistantv2.activity.MainActivity#344 │ │ │ │ │ └─ Decor container of Task=224#334 │ │ │ │ │ ├─ TaskInputSink of Surface(name=Decor container of Task=224)/@0x54597fa#338 │ │ │ │ │ └─ Caption container of Task=224#335 │ │ │ │ │ └─ Caption of Task=224Leash#336 │ │ │ │ │ └─ Caption of Task=224#337其中com.tencent.android.qqdownloader/com[...]ssistantv2.activity.MainActivity#344就是应用窗口的layer,Caption of Task=224#337则是标题栏的layer,他们存在一个相同的父layer,即Task=224#326,那么每一次更新LayerSnapshot时,会从上至下遍历更新所有需要更新的layer,即会先更新应用窗口的layerBounds,然后再更新标题栏的layerBounds,所以我们可以根据顶层Activity的包名先计算出应用窗口的宽度,最后在更新标题栏时重设其geomLayerBounds的值,这样就可以保证每一次提交给hwc的图层中CaptionWindow和Activity窗口宽度一致。
3.解决方案
3.1.确定需要更新的Activity窗口和标题栏窗口
在WindowDecoration中,当CaptionWindow触发relayout时,记录下当前的标题栏名称,顶层应用包名,父layer的名称,这3个属性将提供给SurfaceFlinger来查找对应layer的关键字。同时可以同步SurfaceFlinger计算出来的真实的标题栏宽度。参考修改如下:
fix caption and window are not synchronized when window is scaling by pngcui · Pull Request #82 · openfde/lineageos_android_frameworks_base
3.2.SurfaceFlinger同步窗口宽度
在LayerSnapshotBuilder中,当调用updateLayerBounds时,根据顶层应用名包的geomLayerBounds来计算出最终窗口总宽度,这里需要考虑多窗口并排显示的场景,所以需要对窗口宽度进行累加,多窗口场景的SurfaceFlinger信息如下所示:
ROOT ├─ Display 0 name="内置屏幕"#1 │ ├─ WindowedMagnification:0:31#2 │ │ ├─ HideDisplayCutout:0:14#3 │ │ │ └─ OneHanded:0:14#4 │ │ │ ├─ FullscreenMagnification:0:12#5 │ │ │ │ │ └─ Task=226#108 │ │ │ │ │ ├─ TaskFragment{6bfc908 mode=freeform}#124 │ │ │ │ │ │ └─ ActivityRecord{a55c0bb u0 com.android.settings/.Settings t226}#121 │ │ │ │ │ │ ├─ 2de39d8 ActivityRecordInputSink com.[...]omepage.SettingsHomepageActivity#123 │ │ │ │ │ │ └─ 351bf02 com.android.settings/com.android.settings.Settings#127 │ │ │ │ │ │ └─ com.android.settings/com.android.settings.Settings#131 │ │ │ │ │ ├─ TaskFragment{31938a1 mode=freeform}#125 │ │ │ │ │ │ └─ ActivityRecord{e845ac6 u0 com.androi[...]s$NetworkDashboardActivity t226}#126 │ │ │ │ │ │ ├─ d272a87 ActivityRecordInputSink com.[...]ettings$NetworkDashboardActivity#130 │ │ │ │ │ │ └─ abbecb2 com.android.settings/com.and[...]ettings$NetworkDashboardActivity#128 │ │ │ │ │ │ └─ com.android.settings/com.android.set[...]ettings$NetworkDashboardActivity#132 │ │ │ │ │ └─ Decor container of Task=226#113 │ │ │ │ │ ├─ TaskInputSink of Surface(name=Decor container of Task=226)/@0xf9bc044#120 │ │ │ │ │ └─ Caption container of Task=226#114 │ │ │ │ │ └─ Caption of Task=226Leash#116 │ │ │ │ │ └─ Caption of Task=226#117当被查找的layer图层的父layer名称与WindowDecoration标记的父layer名称一致时,表示此layer是当前Activity的有效图层,需要记录下他的宽度,直到所有图层都遍历完,当更新的layer是标题栏时,则把记录下来的最终宽度值设置到标题栏的geomLayerBounds数据结构中,并把该值同步给WindowDecoration模块,参考修改如下:
fix caption and window are not synchronized when window is scaling by pngcui · Pull Request #4 · openfde/lineageos_android_frameworks_native
fix x11-windows and captionbar are not synchronized during window scaling by pngcui · Pull Request #5 · openfde/lineageos_android_frameworks_native
fix captionbar appears too short by pngcui · Pull Request #17 · openfde/lineageos_android_frameworks_native
4.方案不足之处
4.1.多应用并排显示场景
对于双Activity类的应用,当2个Activity分别属于不同的包名时,此方案无法精准识别完整的标题栏宽度,此时会丢弃本次优化,此场景的SurfaceFlinger信息如下所示:
ROOT ├─ Display 0 name="内置屏幕"#1 │ ├─ WindowedMagnification:0:31#2 │ │ ├─ HideDisplayCutout:0:14#3 │ │ │ └─ OneHanded:0:14#4 │ │ │ ├─ FullscreenMagnification:0:12#5 │ │ │ │ │ └─ Task=243#117 │ │ │ │ │ ├─ TaskFragment{fafa2f1 mode=freeform}#133 │ │ │ │ │ │ └─ ActivityRecord{c208d76 u0 com.android.settings/.Settings t243}#122 │ │ │ │ │ │ ├─ 43a5f77 ActivityRecordInputSink com.[...]omepage.SettingsHomepageActivity#132 │ │ │ │ │ │ └─ d47c986 com.android.settings/com.android.settings.Settings#136 │ │ │ │ │ │ └─ com.android.settings/com.android.settings.Settings#140 │ │ │ │ │ ├─ TaskFragment{e6f7587 mode=freeform}#144 │ │ │ │ │ │ └─ ActivityRecord{9f85b75 u0 com.androi[...]ustomizationPickerActivity t243}#150 │ │ │ │ │ │ ├─ c1ac70a ActivityRecordInputSink com.[...]cker.CustomizationPickerActivity#152 │ │ │ │ │ │ └─ 5249d0d com.android.wallpaper/com.an[...]cker.CustomizationPickerActivity#153 │ │ │ │ │ │ └─ com.android.wallpaper/com.android.wa[...]cker.CustomizationPickerActivity#154 │ │ │ │ │ └─ Decor container of Task=243#126 │ │ │ │ │ ├─ TaskInputSink of Surface(name=Decor container of Task=243)/@0xa589666#130 │ │ │ │ │ └─ Caption container of Task=243#127 │ │ │ │ │ └─ Caption of Task=243Leash#128 │ │ │ │ │ └─ Caption of Task=243#1294.2.录屏场景
由于Android14上录屏是录制的虚拟屏,会把录制的窗口映射到虚拟屏上,导致在虚拟屏上存在同样的一份Layer信息,所以SurfaceFlinger在计算窗口宽度时,会重复计算2次,导致异常,针对这种情况,也只能丢弃本次优化,相关修改如下:
fix captionbar appears too long when srceenrecording by pngcui · Pull Request #14 · openfde/lineageos_android_frameworks_native