google 分屏 横屏模式 按home键界面错乱故障分析(一)

你确定你了解分屏的整个流程?

代码也是有情感,你若爱她,就调试她吧。

代码阅读,请到此处http://androidxref.com 查看原生代码分享此文便是对代码GG的支持,也是爱的表达方式,所以让爱来的猛烈些吧。

之前分析文章列表:

Android 关机对话框概率没有阴影故障分析 android recent key长按事件弹起触发最近列表故障分析 google 分屏 popup无法显示故障分析

问题描述 [Dialer&&MMS]进入分屏后在横屏模式按home键界面错乱

操作步骤 1.进入拨号盘2.长按recent进入分屏,按home回主界面3.点击MMs进入短信,转到横屏模式4按home键,故障发生

环境描述

android7.0.1 屏幕分辨率 720*1280 手机:eng版本

故障效果(看状态栏,出现两条黑线) 分析 00 套路,使用hierarchyviewer 工具,去找下出错的内容属于谁,属于哪个类。(为什么频繁使用这个呢?快速便捷的定位,能不高呼)

展看DockedStackDivider,查看界面信息: 这里看到定位信息真多,我们看到这里三个View都是自定义的,这就让我们轻而易举的找到了地方 于是我们快马加鞭,来项目里面查找下DividerView 这里我们看到了代码属于packagessystemui下面,于是我们可以得出一个结论,分屏的线条是在SystemUI进程,于是乎,我们是可以调试SystemUI的,我们先不去调试,直接看代码分析。 01

缓下,我们先要去看DividerView.java是个什么内容。按照之前的讲法,我们来看看(只说重点了,详细的去之前文章阅读了)

非常普通了,就是个简单的自定义View而已这个onFinishInflate方法就是初始化View的地方,于是我们看到一些View,我们此处关注mBackground 和mMinimizedShadow(为什么,因为我们出错的就是这两个显示出来了) 这里我高亮了mHandle,这个是拖动分割线的响应View哦。(内心激动的人,可以先去看本文件里面的onTouch函数即可) 我们先处理此问题,关于分屏流程,后面展开。

本文搜索mBackground,核心关注它变为隐藏的时候。我们看到只找关键的了,定义的和设置变量的一些乱七八糟的就没截图了。我们看下 mBackground.setScaleY(MINIMIZE_DOCK_SCALE);的代码上下文然后我们看到完整代码:(有两处,一个是有动画,一个没有而已) MINIMIZE_DOCK_SCALE的定义为:看到这里,有隐藏view的逻辑,setScaleX(MINIMIZE_DOCK_SCALE=0)便会将此View隐藏。我们看下这里的逻辑,如果不是最小化minimized(就是显示的了),那就走resetBackground方法。这里我们看到,系统将mBackground设置了锚点居中,缩放还原为1,设置mMinimizedShadow隐藏如果mDockSide == WindowManager.DOCKED_TOP 设置PivotY为0(锚点为0,作为缩放的原点)然后将Y方向缩为0如果mDockSide == WindowManager.DOCKED_LEFT或者DOCKED_RIGHT ,设置锚点,设置X缩放为0 (此处代码有些搞笑,都是隐藏,你这时候缩放X Y方向为0有区别吗?并且在上面resetBackground是直接将XY的缩放都回到1),人家还原的时候都不管你之前到底缩放了哪个方位,你自己缩放判断个鬼。so。。。出错就在这里了,你说你搞笑不?

这里有个很有意思的套路:mMinimizedShadow.setAlpha(minimized ? 1f : 0f);看这里是不是反的? 如果最小化,显示这个,如果不是最小化,隐藏。这里他做这个是干嘛的呢?其实google这个在最小化的时候显示mMinimizedShadow,按照这个名字,它会是个shadow(让你知道这个是分屏了,有个阴影效果),如果显示分屏的时候,它就隐藏了。(它就是想在你分屏隐藏的时候,在状态栏上做个阴影,让你知道你处在分屏模式下而已) 我们看下除了DOCKED_TOP ,此枚举都有哪几个值: 看这个的目的,我们可以看出上面的代码,是否忽略掉了一些状态,于是我们继续来看。

02 我们再看下我们的代码 会发现我们的else是不是没有全部的选择,少了DOCKED_BOTTOM 和 DOCKED_INVALID,于是我们假如这里的mDockSide==这两个的其中一个,会发现什么问题呢? mBackground 没有隐藏哦mMinimizedShadow 是设置了显示,但是我们再去它的类去瞅瞅吧。

mMinimizedShadow 类的方法里面有:(onLayout 和onDraw都是自定义view的关键实现,还有一个是onMeasure,此处没有复写而已) 我们看下这几个方法: onLayout updatePaint方法:

onDraw 看到了没这里它也没管这个值DOCKED_INVALID(DOCKED_BOTTOM),于是用了默认的颜色,而默认画出来是黑色,你说这就没意思了吧。忽略DOCKED_BOTTOM这个枚举我们可以解释,因为当前系统设计不会放置在下面的,于是DOCKED_BOTTOM值可以忽略,所以我们看到,此处有一个 DOCKED_INVALID状态,会导致在隐藏分割线的时候,没有处理代码,引起分割线显示在上面。而系统自以为所有手机都跟它一样,配置很高,but现实是还有低配机子的啦,于是此状态会产生,引出此问题。于是,我们看完了代码,从逻辑上分析出来是这个原因,那么事实觉得这个情况会发生吗? 我们补充log,查看下这里的错误时候的值,发现,此处为-1( DOCKED_INVALID),于是得出结论了。到这里,此问题就算完结了,但是,但是,我要讲故障修复,就没必要这么繁琐了,因此,我们还要继续深入,去看看代码。我要去讲下分屏这条线索,追个路径出来。 03

搜索DividerView,我们继续来深入 我们打开Divider.java,发现了很多内容。

首先,我们看下它继承了谁?SystemUI,这个要干嘛呢?我们搜索Divider,通过筛选(只在SystemUI包下,为什么,之前已经说过,这个类在这个包下,别的应用引用的机会基本为0)我们看到如下内容:(筛检过)

然后我们在SystemUIApplication.java 里面稍微停留下:看到 startServicesIfNeeded方法

这里遍历了我们上面的mServices里面的所有元素,有我们的Divider.java(看这里都转为了SystemUI类处理了,所以我们Divider要继承SystemUI,没毛病)主要走里面的start方法 和onBootCompleted方法我们先不回到Divider的start方法,我们再继续深入看下,看startServicesIfNeeded如何调用起来的。

我们跟了下KeyguardService.java 发现不对路,放弃掉。我们忽略自己本身的方法,于是来到SystemUIService.java里面

发现onCreate里面调用了startServicesIfNeeded方法,于是我们继续看,搜索SystemUIService

我们忽略注释和xml,以及本身的文件,于是我们看到了SystemServer.java(熟悉不?系统server创建的地方),我们看看去 看到了不?系统创建完server,会到mActivityManagerService的systemReady,这里面启动了systemUI,然后调用了一个SystemUIService,这个在创建自己的时候,调用了SystemUIApplication里面的startServicesIfNeeded,完成了systemui的组件创建和初始化,而这里,也有我们分屏的Divider 04 逛完了系统创建systemui的过程,我们再次回来,慢慢来看我们的分隔线Divider.java 从上面的流程来看,我们需要调用关注它的start方法,我们看下:这里我高亮了几个内容:DividerWindowManager ,分隔线管理者。(等会细看)update 这个主要更新我们的参数,主要为移除Divider,然后添加(依据当前屏幕的横竖屏处理),判断是否为最小化,是的话就要想办法隐藏了。mDockDividerVisibilityListener 这个类DockDividerVisibilityListener,我们看下:

这里我们发现,它的继承为:IDockedStackListener.Stub,于是乎,它是跨进程调用这里我们使用ssp.registerDockedStackListener(mDockDividerVisibilityListener);将这个监听注册到系统里面去(后面分析它)mForcedResizableController 暂时先放过,后面分析。 05 我们说完了大概,然后我们回来看下DividerWindowManager这个类

没有继承,只有方法,于是我们看方法add(关键),直接通过windowmanager给系统加入了一个View。高亮一个信息TYPE_DOCK_DIVIDER(专门给它量身定做的类型,是不是很开心,我们又找到了关键字) remove,移除这个View。

setSlippery 设置是否在滑动中,中间的那个线是可拖拽的。

setTouchable是否可点击。

我们回过头看看Divider里面的update方法:

(看下这里的removeDivider 和addDivider)removeDivider 这里mWindowManager就是DividerWindowManager,我们不用说了吧。 addDivider 加载布局,设置大小,设置宽高,加载到windowmanager里面去。 06

总结上面的内容: systemui里面有个Divider,里面管理着分割线的布局,有个监测系统的服务端(mDockDividerVisibilityListener),系统要不要显示,通过这条线回调回来,再通知界面显示与否即可。这里Divider还有个方法:onConfigurationChanged,当系统属性发生改变时候,会通过这条线路发送回来(这里会做简单的事情,先移除view,然后依据当前的屏幕方向,新建view,然后根据是否要显示,默认是显示的。如果不需要显示,则隐藏掉view--用了缩放XY大小为0和Alpha来做的) 07

总结看完,继续奔波,我们先向系统层迈进,于是我们仔细来看下DockDividerVisibilityListener 看看它有哪些实现onDividerVisibilityChanged显示隐藏的通知onDockedStackExistsChanged 存在与否的通知onDockedStackMinimizedChanged最小化的通知(我们当前在这个里面,因为我们没有退出分屏,只是进入了主界面,分割线会最小化)onAdjustedForImeChanged 当有输入法的时候,调整大小和位置的通知onDockSideChanged 当dock的位置调整的时候,主要就是dock在左边还是右边,这种信息。我们先不去看这里面每个方法的具体实现,我们找下它们在哪里被调用的(我们以onDockedStackMinimizedChanged来作为搜索依据)

自己本身可以忽略NavigationBarView里面是个空实现,忽略我们看到了DockedStackDividerController.java

继续搜索

ActivityManagerInternal.java,注释忽略

ActivityManagerService.java 关键地方,主要完成dockstack的动作 DockedStackDividerController.java registerDockedStackListener 注册的地方(我们之前在Divider的start里面,有注册这个的动作) setMinimizedDockedStack

这是个内部方法,我们说过,内部方法外部不能直接调用,所以我们要找这个在本文里哪里调用了 animateForMinimizedDockedStack 内部,我们还是要找谁用它了 WindowManagerService.java 是个case,然后调用了 mAmInternal(ActivityManagerInternal)的通知即可。mAmInternal是谁呢?

几经周转,在ActivityManagerService.java里面有

这么多,发现线索主要在 setMinimizedDockedStack方法 (DockedStackDividerController.java) animateForMinimizedDockedStack 方法 (DockedStackDividerController.java) 和ActivityManagerService.java里面

于是我们线索收敛了。我们继续向下走

08

我们不做太多扩散,我们从setMinimizedDockedStack切入下在DockedStackDividerController.java里面找setMinimizedDockedStack (突然发现,这里面代码错综复杂,如此分析下去,我要陷入其中,系统还是调用太复杂,要讲清千丝万缕,不能如此细致入微去讲了,汗,于是我们先从单向切入看下)我们看这个是退出与否的状态切换 **我们先继续看看notifyDockedStackExistsChanged的调用地方,我现在不去用编辑器来只是简单搜搜了(如此下去,没有尽头,调用地方太多,于是我们换个思路),开始调试system_server我们关注下WindowManagerService里面的 代码

这个是多用户时候,需要判断当前用户是否处在分屏模式下,我们暂时可以忽略。

按照注释,是attachstack的时候有通知 (DOCKED_STACK_ID ,特殊栈id,主要就是标志谁在分屏的那个栈上)所以这两个函数是个关键点,我们可以下断点,去跟踪代码流程。detachStackLocked 退出也有调用notifyDockedStackExistsChanged,于是乎我们上断点,调试下

attachstack 方法的栈信息为:

detachStackLocked 的栈信息为:

这里关注的栈方法为:

通过两个栈信息,我们便可以得到关键的两个东西:启动分屏的栈,关闭分屏的栈,这两个在分屏模式如此重要的方法,已经被我们拦到,其余的不是迎刃而解吗?

我们继续跟踪detachStackLocked流程,会发现我们的notifyDockedStackMinimizedChanged 方法被触发了。

这个是我们退出分屏的时候,发送回来的消息,于是我们需要看下这个是谁调用的

看看看,又是windowAnimator,又是animateLocked方法,我们清晰的看到了 doFrame(如呼吸一般)如期的出现在眼前。 我们看下animateLocked方法里面的一行

来到了DockedStackDividerController的animate方法

看此处,如果mAnimatingForMinimizedDockedStack为真,则走入我们的最小化方法了。

先消化下,下一章节再见。

下一章节,继续分析分屏,主要讲解分屏的启动过程。

如果有收获,赞赏鼓励下作者。 更多内容,关注微信公众号:代码GG之家。 加微信 code_gg_boy 进入代码GG交流群