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

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

之前分析文章列表:

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

这里我们看到了代码属于packages\systemui下面,于是我们可以得出一个结论,分屏的线条是在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_home)

原文发表时间:2017-03-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏双十二技术哥

Android性能优化(十)之App瘦身攻略

如果你对App优化比较敏感,那么Apk安装包的大小就一定不会忽视。关于瘦身的原因,大概有以下几个方面:

17730
来自专栏北京马哥教育

LaTeXila:Linux 的多语言 LaTeX 编辑器简介

豌豆贴心提醒,本文阅读时间7分钟 LaTeXila 是一个多语言 LaTeX 编辑器,专为那些偏爱 GTK+ 外观的 Linux 用户设计。这个软件除了操作简...

36590
来自专栏编程

用在线RaxML构建系统发育树

本文将以在线的RAxML为例进行讲解: 测试数据及结果和相关处理软件已经上传至百度网盘:http://pan.baidu.com/s/1i5cPyXB密码:b2...

36370
来自专栏玉树芝兰

如何用Python智能批量压缩图片?

本文一步步为你介绍,如何用Python自动判断多张图片中哪些超出阈值需要压缩,且保持宽高比。如果你想了解Python图像处理的基础知识,欢迎动手来尝试。

43020
来自专栏JavaEdge

JDK7新特性概览JSR292:支持动态类型语言(InvokeDynamic)G1 垃圾回收器(Garbage-First Collector)JSR334:小的语言改进(Project Coin)核

799100
来自专栏小李刀刀的专栏

[译]HTML验证的价值探讨

[译]HTML验证的价值探讨 作者:Nicholas C. Zakas 原文:http://www.nczonline.net/blog/2010/08/17/...

36150
来自专栏小白安全

危险漫步利用图片链接完成注入渗透

利用图片链接完成注入渗透 最近危险漫步闲着无聊就翻墙去国外看了一看黑客新闻,有一条新闻引起了我的关注,那就是国外某黑客黑了Word文档,导致了一家公司...

32870
来自专栏数据小魔方

数据地图系列6|Stata数据地图(下)

今天要跟大家分享的是数据地图系列6——Stata数据地图(下)! 接着前一篇的节凑,这一篇会给大家介绍比较全面的Stata热力地图代码实现。 版本仍然是基于S...

74740
来自专栏ascii0x03的安全笔记

PySide——Python图形化界面入门教程(六)

PySide——Python图形化界面入门教程(六)             ——QListView和QStandardItemModel 翻译自:http:/...

44860
来自专栏GopherCoder

专栏:016:功能强大的“图片下载器”

14630

扫码关注云+社区

领取腾讯云代金券