【问题分析】锁屏界面调起google语音助手后壁纸不可见【Android 14】

2024-05-10 20:12

本文主要是介绍【问题分析】锁屏界面调起google语音助手后壁纸不可见【Android 14】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

1 问题描述

为系统和锁屏分别设置两张不同的壁纸,然后在锁屏界面长按Power调起google语音助手后,有时候会出现壁纸不可见的情况,如以下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有的时候又是正常的,但显示的也是系统壁纸,并非是锁屏壁纸。

后面我本地多次尝试,发现了一些规律:

1)、同时设置系统和锁屏壁纸为壁纸A,此时不会有问题。

2)、在第1步的基础上,单独将锁屏壁纸设置为壁纸B,此时也不会有问题。

3)、在第2步的基础上,单独将系统壁纸设置为壁纸C,出现问题。

添加了log后,大概知道问题出现的原因了,不过这里先分析一下WallpaperController中关于计算壁纸可见性的一些代码逻辑吧,之前零零散散看过一点,希望这次能够趁分析这个问题的机会,做一些总结。

2 WallpaperController代码分析

2.1 WallpaperController.adjustWallpaperWindows

更新壁纸的起点在WallpaperController.adjustWallpaperWindows:

在这里插入图片描述

1)、WallpaperController.findWallpaperTarget用来寻找壁纸的目标窗口,将寻找的结果放到成员变量WallpaperController.mFindResults中。

2)、WallpaperController.updateWallpaperWindowsTarget根据成员变量WallpaperController.mFindResults更新成员变量WallpaperController.mWallpaperTarget。

3)、最后如果WallpaperController.mWallpaperTarget不为空,那么认为壁纸可见,再调用WallpaperController.updateWallpaperTokens更新壁纸的可见性。

这里有两个成员变量mFindResults和mWallpaperTarget要先介绍一下。

首先是mWallpaperTarget,定义为:

在这里插入图片描述

白话点说就是壁纸的目标窗口,比如一个Activity的窗口在显示的时候被设置为支持壁纸显示,比如Launcher的窗口,那么这个窗口就可以作为壁纸的目标窗口。如果我们遍历所有的窗口后,找不到一个窗口可以作为壁纸的目标窗口,那么就说明所有的窗口都不支持壁纸显示,那壁纸也就会被设置为不可见。如果可以找到一个壁纸的目标窗口,那么这个目标窗口就会被保存到成员变量WallpaperController.mWallpaperTarget中。

接着是成员变量mFindResults,定义为:

在这里插入图片描述

它是一个FindWallpaperTargetResult类型的成员变量,这里则需要知道FindWallpaperTargetResult这个类的作用,它定义在WallpaperController里:

在这里插入图片描述

从类的注释上以及类名来看,这个类是用来保存寻找壁纸目标窗口操作的结果。

再看它的成员变量,首先是TopWallpaper类型的mTopWallpaper,它又是定义在FindWallpaperTargetResult中的内部类,只有两个成员变量,mTopHideWhenLockedWallpaper和mTopShowWhenLockedWallpaper,结合这里的注释以及我看了代码后的理解:

mTopHideWhenLockedWallpaper和mTopShowWhenLockedWallpaper都可以设置为壁纸窗口,即“Window{d837259 u0 com.android.systemui.wallpapers.ImageWallpaper}”,并且同一时间它们两个中间只能有一个被设置:

1)、如果mTopHideWhenLockedWallpaper被设置(即不为空),说明此时壁纸只能在Home界面可见,锁屏界面不可见。

2)、如果mTopShowWhenLockedWallpaper被设置(即不为空),说明此时壁纸在Home界面以及锁屏界面均可见。

后面分析到相关代码的时候就能了解上面的意义了。

然后再解释几个后续会分析到的成员变量:

1)、mNeedsShowWhenLockedWallpaper,如果在一个Activity界面可以在锁屏界面上显示,比如通话界面,如果这个Activity或者窗口不是全屏的,那么就会把mNeedsShowWhenLockedWallpaper的值设置为true。在这种场景下,即使我们没有为锁屏壁纸找到一个目标窗口,那么我们可能也是需要将锁屏壁纸显示出来的。

2)、useTopWallpaperAsTarget,结合上面第一点来说,如果后续经过我们遍历所有窗口后,我们找不到任何一个窗口可以作为壁纸的目标窗口,但是在一些特殊场景下,我们又是需要将壁纸显示出来的,那么我们就将这个值设置为true,表示我们将TopWallpaper中的mTopHideWhenLockedWallpaper或者mTopShowWhenLockedWallpaper保存的壁纸WindowState本身作为壁纸的目标窗口,至于从这两者中的哪个里面取,则是看当前是否处于锁屏。

3)、wallpaperTarget,这个没什么好说的,在寻找壁纸的目标窗口阶段,我们将寻找的结果保存在FindWallpaperTargetResult.wallpaperTarget中,后续在WallpaperController.updateWallpaperWindowsTarget中,我们就从FindWallpaperTargetResult.wallpaperTarget中拿寻找的结果。

接下来看寻找壁纸的目标窗口的代码,WallpaperController.findWallpaperTarget。

2.2 WallpaperController.findWallpaperTarget

这个方法用来寻找壁纸的目标窗口,是我们本篇文章的分析重点。

在这里插入图片描述

2.2.1 FindWallpaperTargetResult.reset

调用FindWallpaperTargetResult.reset重置FindWallpaperTargetResult的保存的所有信息:

在这里插入图片描述

2.2.2 Freeform的情况

如果当前有WINDOWING_MODE_FREEFORM类型的App显示:

在这里插入图片描述

那么就设置FindWallpaperTargetResult.useTopWallpaperAsTarget为true:

在这里插入图片描述

即在Freeform的场景下直接将壁纸进行显示,不需要再额外找一个目标窗口了,这算是一种对需要显示壁纸的特殊场景的处理。

2.2.3 WallpaperController.mFindWallpapers

接着对所有的窗口进行第一次遍历:

在这里插入图片描述

如果这个窗口是壁纸类型的,那么继续判断,如果WallpaperWindowToken.canShowWhenLocked返回true,说明此时壁纸是允许在锁屏界面显示的,那么就将这个壁纸窗口保存在FindWallpaperTargetResult.mTopShowWhenLockedWallpaper中,后续如果我们检测到FindWallpaperTargetResult.mTopShowWhenLockedWallpaper不为空,那么就说明当前壁纸是允许在锁屏界面显示的。否则就将这个壁纸窗口保存在FindWallpaperTargetResult.mTopHideWhenLockedWallpaper中,后续如果我们检测到FindWallpaperTargetResult.mTopHideWhenLockedWallpaper不为空,那么就说明当前壁纸是只能在Home界面显示的。

另外根据我本地的测试,当同时设置系统壁纸和锁屏壁纸时,WallpaperWindowToken.setShowWhenLocked这个方法会被调用,设置WallpaperWindowToken.mShowWhenLocked为true,调用堆栈为:

在这里插入图片描述

2.2.4 WallpaperController.mFindWallpaperTargetFunction

对所有的窗口进行第二次遍历:

在这里插入图片描述

这里的逻辑也比较复杂,省略不太重要的部分,首先看一种特殊的情况:

如果在锁屏状态,并且此时正在遍历的这个窗口盖在锁屏界面,那么继续判断:

1)、如果现在锁屏的状态为“occluded”。

或者

2)、当前该在锁屏界面上的那个窗口处于Transition,那一般就是open或者close。

该窗口或者该窗口对应的ActivityRecord是否是全屏的,如果不是,那么将mNeedsShowWhenLockedWallpaper设置为true。很好理解,如果是一个非全屏的窗口盖在锁屏界面上,如果不显示锁屏壁纸,那么屏幕上没有被这个非全屏窗口覆盖的部分就会由于没有内容显示从而黑屏。

这里稍微提一下这个对窗口是否处于Transitiond的判断,如果只是判断锁屏的状态为“occluded”,那么可能会出现锁屏状态切换为“occluded”不够及时,从而出现短暂黑屏的现象,就比如我这里长按Power键唤起google语音助手的情况:

在这里插入图片描述

所以我们需要加上对窗口是否处于Transition的判断,确保壁纸在Transition早期阶段就显示。

判断过这种特殊场景后,接着对这个正在遍历的窗口进行判断,如果这个窗口在屏幕上,并且已经绘制完成了,那么调用WindowState.hasWallpaper方法去判断该窗口是否支持显示壁纸(这里就不分析动画过程中显示壁纸的情况了):

在这里插入图片描述

这是更一般的情况。

涉及LetterBox的情况比较少见,最常见的还是通过检查窗口是否配置了FLAG_SHOW_WALLPAPER这个窗口标志位来判断这个窗口是否支持显示壁纸,就比如Launcher。

2.2.5 FindWallpaperTargetResult.setUseTopWallpaperAsTarget

回到WallpaperController.findWallpaperTarget方法:

在这里插入图片描述

这里承接第4步,如果在第4步我们发现一个非全屏的窗口盖在了锁屏界面上,那么就会将FindWallpaperTargetResult.mNeedsShowWhenLockedWallpaper设置为true。

接着在这里,就调用FindWallpaperTargetResult.setUseTopWallpaperAsTarget设置FindWallpaperTargetResult.useTopWallpaperAsTarget为true:

在这里插入图片描述

来保证后续壁纸可以显示,这个场景和Freeform出现的场景处理方式一致,即对需要显示壁纸的特殊场景的一种处理。

2.2.6 FindWallpaperTargetResult.setWallpaperTarget

来看最后的一点内容:

在这里插入图片描述

如果FindWallpaperTargetResult.wallpaperTarget为空,说明通过两次遍历我们没有找到壁纸的目标窗口,但是FindWallpaperTargetResult.useTopWallpaperAsTarget为true,又说明我们的确想显示壁纸,那么就调用FindWallpaperTargetResult.getTopWallpaper看看能不能返回一个壁纸窗口,如果可以,那么就调用FindWallpaperTargetResult.setWallpaperTarget将壁纸的目标窗口设置为壁纸本身。但是也可能会返回null,看下FindWallpaperTargetResult.getTopWallpaper的内容:

在这里插入图片描述

前面说过了,如果将壁纸窗口保存在mTopHideWhenLockedWallpaper中,说明当前壁纸只能在Home界面显示,不能在锁屏界面显示。如果将壁纸窗口保存在mTopShowWhenLockedWallpaper中,说明当前壁纸可以在Home界面以及锁屏界面显示。这里的代码大概就是这种逻辑,比较简单,不再赘述。

需要注意的是这里的返回值可能为空,我们分析的这个问题就是因为这里返回空所以出现了壁纸不可见的情况导致了黑屏,我们后续分析问题产生的原因。

2.3 WallpaperController.updateWallpaperWindowsTarget

在这里插入图片描述

WallpaperController.updateWallpaperWindowsTarget这个方法,我看了下好像没有太多可以说的,就是把上一步寻找壁纸的目标窗口的结果保存到WallpaperController的成员变量mWallpaperTarget中。

2.4 WallpaperController.updateWallpaperTokens

回到WallpaperController.adjustWallpaperWindows中,如果WallpaperController.mWallpaperTarget不为空,那么调用WallpaperController.updateWallpaperTokens设置壁纸的可见性:

在这里插入图片描述

这里需要注意的一点就是,及时这里的传参visibility是true,后续可能也无法将壁纸变为可见,因为这里还有额外的判断。

首先这里的成员变量mWallpaperTokens定义为:

在这里插入图片描述

是一个WallpaperWindowToken的队列,在WallpaperWindowToken创建的时候,会把它加入到mWallpaperTokens中。

接着这里会调用FindWallpaperTargetResult.getTopWallpaper来获取当前的壁纸窗口,只有这个壁纸窗口不为空,并且在mWallpaperTokens中,我们才能将壁纸的可见性设置为true。

后续的WallpaperWindowToken.updateWallpaperWindows就不分析了。

3 问题分析

代码分析完了,现在分析问题。

根据我之前本地操作的结果:

1)、同时设置系统和锁屏壁纸为壁纸A,此时不会有问题。

2)、在第1步的基础上,单独将锁屏壁纸设置为壁纸B,此时也不会有问题。

3)、在第2步的基础上,单独将系统壁纸设置为壁纸C,出现问题。

3.1 同时设置系统和锁屏壁纸为壁纸A

这个路径下,会WallpaperWindowToken.setShowWhenLocked这个方法会被调用,设置WallpaperWindowToken.mShowWhenLocked为true,调用堆栈为:

在这里插入图片描述

对所有窗口进行mFindWallpapers遍历时,由于WallpaperWindowToken.mShowWhenLocked为true,因此会设置mTopShowWhenLockedWallpaper为壁纸窗口。

对所有窗口进行mFindWallpaperTargetFunction遍历时,由于google语音助手这个显示在锁屏界面之上的界面对应的ActivityRecord是非全屏的,因此会设置FindWallpaperTargetResult.mNeedsShowWhenLockedWallpaper为true:

在这里插入图片描述

并且由于所有窗口都不满足作为壁纸的目标窗口的条件,因此这一步没有找到目标窗口。

后续再回到WallpaperController.findWallpaperTarget:

在这里插入图片描述

现在FindWallpaperTargetResult.mNeedsShowWhenLockedWallpaper为true,所以这里会调用FindWallpaperTargetResult.setUseTopWallpaperAsTarget来将FindWallpaperTargetResult.useTopWallpaperAsTarget设置为true,那么接着就会调用FindWallpaperTargetResult.getTopWallpaper尝试获取壁纸窗口,并且将返回的结果作为目标窗口:

在这里插入图片描述

这里由于我们处于锁屏,因此会返回TopWallpaper.mTopShowWhenLockedWallpaper,并且根据我们的分析,因为WallpaperWindowToken.mShowWhenLocked为true,因此之前我们的确是将壁纸窗口保存在了TopWallpaper.mTopShowWhenLockedWallpaper中的,因此这里就可以返回壁纸窗口,并且将其设置为壁纸的目标窗口。

这种情况最终是会找到一个壁纸的目标窗口的,因此壁纸是可见的。

3.2 在第1步的基础上,单独将锁屏壁纸设置为壁纸B

这种情况下,和3.1节的分析内容不会有区别,所以也不会有什么问题。

唯一有点奇怪的是,此时显示的是系统壁纸,而非锁屏壁纸。也不能说奇怪,毕竟系统壁纸才是真正的壁纸,是有一个专门的壁纸窗口对应,而锁屏壁纸,应该只是锁屏界面为自己设置的一张背景图。

3.3 在第2步的基础上,单独将系统壁纸设置为壁纸C

这种情况下,就会出现问题,原因出在哪儿呢?

看了下log,发现此时WallpaperWindowToken.mShowWhenLocked变成了false,那么对所有窗口进行mFindWallpapers遍历时,由于WallpaperWindowToken.mShowWhenLocked为false,因此会设置TopWallpaper.mTopHideWhenLockedWallpaper为壁纸窗口。

而在后续调用FindWallpaperTargetResult.getTopWallpaper尝试获取壁纸窗口时,由于此时处于锁屏,因此返回的仍然是TopWallpaper.mTopShowWhenLockedWallpaper。这就是问题的原因所在了,我们将壁纸窗口保存在了TopWallpaper.mTopHideWhenLockedWallpaper中,那么TopWallpaper.mTopShowWhenLockedWallpaper就是空的,因此调用FindWallpaperTargetResult.getTopWallpaper返回的就是空的,最终结果就是没有为壁纸找到一个目标窗口,壁纸在锁屏状态下变为不可见。

那么为什么WallpaperWindowToken.mShowWhenLocked变成了false呢,我也没看到WallpaperWindowToken.setShowWhenLocked方法有调用将WallpaperWindowToken.mShowWhenLocked设置为false啊?原来是设置系统壁纸的时候,直接重新创建了一个新的WallpaperWindowToken对象:

在这里插入图片描述

WallpaperWindowToken.mShowWhenLocked默认是false,并且后续没有再调用WallpaperWindowToken.setShowWhenLocked将WallpaperWindowToken.mShowWhenLocked设置为true,所以就出现了问题。

最后再看下同时设置系统壁纸和锁屏壁纸的情况吧,同时设置了壁纸后,会重新创建一个WallpaperWindowToken对象,接着就是调用WallpaperWindowToken.setShowWhenLocked设置WallpaperWindowToken.mShowWhenLocked:

在这里插入图片描述

看调用堆栈,都在WallpaperManagerService$DisplayConnector.connectLocked方法中:

在这里插入图片描述

看到这里需要更正上面的一个说法,就是单独设置了系统壁纸的时候,其实也是调用了WallpaperWindowToken.setShowWhenLocked了,但是设置的是false,因为只是针对系统壁纸生效,而本来WallpaperWindowToken.mShowWhenLocked默认的就是false,所以我之前添加的log没有打印…

用白话总结一下这个问题,给我个人的感觉就是:

1)、setShowWhenLocked这个属性表示的壁纸自己支持不支持在锁屏界面显示,是壁纸自己决定的,或者说是Launcher在设置壁纸的时候决定的。

2)、WallpaperController用来决策壁纸是否需要在锁屏界面上显示。

这个问题很明显就是这两者冲突了,WallpaperController的逻辑觉得这个时候壁纸应该在锁屏界面显示,但是还是需要看看在Launcher设置壁纸的时候,设定壁纸是否可以在锁屏界面显示。如果壁纸不支持在锁屏界面显示,那么WallpaperController也不能强行让壁纸在锁屏界面上显示。

4 解决方案

分析到这里感觉这应该是google的原生问题,但是pixel却没有问题,不过发现了一个区别,就是将系统壁纸和锁屏壁纸分别设置为不同的壁纸图片后,在锁屏界面长按Power唤起语音助手时,发现pixel显示的是壁纸是锁屏壁纸,而我们的机器要么显示的是系统壁纸,要么就不显示。

dump信息看了下,原来pixel的机器有两个WallpaperWindowToken,分别管理系统壁纸和锁屏壁纸:

在这里插入图片描述

而我们的机器只有一个,是系统壁纸:

在这里插入图片描述

锁屏壁纸只是NotificationShade为自己设置的一张背景。

跟SystemUI的同事沟通了一下,得知pixel用的似乎不是aosp里的SystemUI,而是自己另外一套的SystemUI,并且将aosp的SystemUI推到手机里也有问题,那这个问题无法参考pixel进行修改了。

回顾一下问题发生的原因,即WallpaperController判断出锁屏界面需要显示壁纸,壁纸却又说我的出厂设定就是只能在Home界面显示,我就不显示,WallpaperController拗不过壁纸,所以出现了黑屏。

如果要解决这个黑屏问题,我目前能想到的就是围绕FindWallpaperTargetResult.mNeedsShowWhenLockedWallpaper这个变量做文章, 既然这个变量被设置为true了,就说明当下的确需要壁纸去显示了,不管壁纸它支持不支持在锁屏界面上显示,它都得直楞起来,先给我显示了再说。

这篇关于【问题分析】锁屏界面调起google语音助手后壁纸不可见【Android 14】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/977444

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

业务中14个需要进行A/B测试的时刻[信息图]

在本指南中,我们将全面了解有关 A/B测试 的所有内容。 我们将介绍不同类型的A/B测试,如何有效地规划和启动测试,如何评估测试是否成功,您应该关注哪些指标,多年来我们发现的常见错误等等。 什么是A/B测试? A/B测试(有时称为“分割测试”)是一种实验类型,其中您创建两种或多种内容变体——如登录页面、电子邮件或广告——并将它们显示给不同的受众群体,以查看哪一种效果最好。 本质上,A/B测

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k