原创无极限,欢迎加微信公众号 code_gg_home ,关注更多内容
问题描述
[Message][Input method]Display is wrong when message at split mode.
分屏模式下短信界面显示不正确
操作步骤
1.打开message然后退出
2.打开一个app如Call,然后长按recent键进入分屏模式
3.让message在分屏模式中处于底部,然后在message中编辑一些字符
4.长按这些字符串,不能显示出”CUT COPY SHARE”这3项
–KO
环境描述
android7.0.1
屏幕分辨率 720*1280
手机:eng版本
首先
按照bug描述,此时无法弹出pop框(cut copy…),我们第一步方向,去跟踪代码,追到此框弹出路径。(根据log分析, 使用调试工具Eclipse打断,跟踪流程),得到一条调用栈信息:
Editor.java
—>startSelectionActionModeInternal
—>mTextView.startActionMode
DecorView.java
—>startActionModeForChild
—>startActionMode
—>createFloatingActionMode
—>FloatingActionMode
FloatingActionMode.java
—>repositionToolbar
—>updateViewLocationInWindow
—>repositionToolbar
结论
确定位置FloatingActionMode.java 的 repositionToolbar方法上,此处 if (isContentRectWithinBounds()) 判断上:
前面的结论,写的非常粗糙,只是给出了大致结果,没有给出如何处理此问题的,如下我们慢慢展开。
01
使用hierarchyviewer 工具,我们全屏下操作出来copy 对话框,去看它的视图信息。
展看后我们看到了:
看下cut copy这个框中的元素,发现最终类为:FloatingActionMode.java,因此直接定位在了这个类。
大致去阅读下这个类,同时工程下搜索下这个类,很快可以看到一条轨迹。在轨迹路途加入断点,快速定位流程,搜索内容如下:
我们可以排除StatusBarWindowView.java(因为我们不属于这里),我们忽略本身的FloatingActionMode.java ,来到了DecorView.java位置,(一看代码,有戏)
于是我们上断点,定位出来流程。
02
通过跟踪,对比全屏和分屏下出错的流程,发现问题点在于updateToolbarVisibility 函数的调用上,全屏下会调用这个show,而出错的在分屏下的底部时,没有调用。
于是我们继续定位,去找谁调用了这个函数。再次筛选,我们需要调用到show方法上。寻着这个路径,我们看到了本文件里面的调用关系:mMovingOff调用了updateToolbarVisibility方法
继续找mMovingOff的调用位置,可以得到如下
两个条件,最终确定是第一个条件出现问题,出现点为:
这里代码的意思为:
mContentRectOnScreen 弹出框在全屏的显示区域
mScreenRect 全屏区域 (错误点在这里)
mViewRectOnScreen view在全屏的显示区域
此段代码做了校验,判断popup框是否在屏幕外,如果在,就不要画了(画了你也看不见)
错误是因为:此段代码判断结论为,popup不在可见范围,不用画。(我擦,有没搞错,我在编辑框上选个内容,需要复制,粘贴,怎么会不在可见范围,哭晕..)
然而错误的原因你会泪奔的,原因是
mContext.getResources().getDisplayMetrics().heightPixels 的值为558,而popup的位置是579(系统判断579>558 ,所以在屏幕外?OMG,我觉得是在开玩笑),郁闷的是我们手机屏幕是720*1280的,(579<1280,应该要画的)。于是我们愤怒转移到了getDisplayMetrics().heightPixels方法,此方法取出来的不是屏幕高,是不是有些崩溃,那么为什么不是呢?
03
让我们停止怀疑人生,继续来追踪
mContext.getResources().getDisplayMetrics().heightPixels 为什么会给错呢?
最终
我们发现:
系统getDisplayMetrics().heightPixels此方法给出的是当前task的高度值,并非屏幕的高度值。
由于之前我们没有分屏机制,所以task就是全屏的,这两个值一致,没有问题。当分屏产生时,此值大小则不是屏幕的高度了。这个属于分屏开发暴露的问题。
至于为什么分屏在上面时候,pop能弹出来,留个疑问给大家。
我们现在来查询heightPixels从何处来。此过程太过漫长,喝杯茶,容我慢慢道来。
mContext.getResources() 找到这个方法实现的地方,通过断点,找到此处的mContext在ContextImpl.java里面
getResources() 返回mResources ,于是我们要去找mResources的赋值地方,发现在ContextImpl的构造里面:
于是在ContextImpl的构造函数设置断点,发现确实此处传递的overrideConfiguration参数中有我们需要的错误值。
因此可以断定,此处之前已经有问题啦。
通过栈信息,我们可以看到,此时传递的路径为WindowManagerService.java的 handle里面的ADD_STARTING case里面,调用了addStartingWindow 方法
于是乎,我们看到了wtoken.mTask.mOverrideConfig 这个值决定了此处的overrideConfig。
如何去找哪里触发的这个case,我们搜索ADD_STARTING
在调用地方设置断点,如此可以找到调用路径。
于是我们发现setAppStartingWindow 里面调用了,我们向上去找,发现了此处的wtoken里面的值已经出错(此处为279,densityDpi值为2,和之前的558对应上了),于是我们的方向便是去找这个值从哪里来的。
通过栈信息,我们找到了ActivityStarter.java 里面的 startActivityUnchecked方法,看到了此处的mStartActivity.task值已经出错,于是我们需要在此处确认此值的来源。
于是我们向上跟踪,发现修改地方在setTaskFromReuseOrCreateNewTask函数里面,继续跟进去看:
结论已经出现:
由于我们的task的isResizeable()返回true,使得方法进入task.updateOverrideConfiguration(mBounds);此处task处在分屏时候,此时task的大小需要使用activity的边界值做覆盖,覆盖之后,使得我们最终调用mContext.getResources().getDisplayMetrics().heightPixels拿到的是task的高,并非屏幕的高。
等等,我们好像发现了什么?
这里我们再去细分析,发现此处逻辑没有问题,当前task如果是isResizeable的,那么我们是需要覆盖这个值的,因此这里值没有问题,此处逻辑追踪的只是想确定错误值的来源。通过看完,发现此值本身没有疑问,是task的大小,没有问题。
我们错了?why??
那我们再返回到我们定位的起点,此处判断错误,引起没有去显示popup框
mContentRectOnScreen 弹出框在全屏的显示区域
mScreenRect 全屏区域 (错误点在这里)
mViewRectOnScreen view在全屏的显示区域
mScreenRect 系统期望拿到的是屏幕大小,(task默认不分屏下是等于屏幕大小)而此处因为分屏了,task的大小不等于屏幕大小了。
而此段代码认为mContext.getResources().getDisplayMetrics().heightPixels拿到还是系统屏幕大小,导致出错的。
结论
mContext.getResources().getDisplayMetrics().heightPixels 真正意义上是task的大小,在不分屏下,和屏幕大小相等(当然这里屏幕大小不是真正物理屏幕大小,因为还有状态栏和虚拟按键不在task的范围内,具体就不扩展了)
于是我们的修改思路便是,需要找到此处可以拿到屏幕大小的方法,解决此问题。
修复
FloatingActionMode.java 里面,修改isContentRectWithinBounds的方法实现,具体修改为:
扫描下方二维码,关注代码GG ,或者微信搜索 code_gg_home