google 分屏 横屏模式 按home键界面错乱故障分析(二) 分屏的启动过程

google 进入分屏后在横屏模式按home键界面错乱(二)

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

Android 关机对话框概率没有阴影故障分析

android recent key长按事件弹起触发最近列表故障分析

google 分屏 popup无法显示故障分析

分享此文便是对代码GG的支持,也是爱的表达方式,所以让爱来的猛烈些吧。

代码阅读,请到此处http://androidxref.com 查看原生代码

前情回顾:

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

上一节我们主要围绕着分屏的那个线进行展开,分析了状态栏出现故障的问题原因。同时我们深入定位,跟踪了systemui的启动过程

系统WMS AMS关于分屏的一些方法,同时systemUI通过Divider的服务端检测AMS WMS给回来的分屏当前状态,这边进行更新view

同时我们找到了AMS WMS里面关于分屏的关键方法attachstack以及detachStackLocked,关注了它的堆栈信息,以及怎么触发到systemUi的界面更新过程

上一讲后面出现了一个笔误。具体为

我们继续跟踪detachStackLocked流程,会发现我们的notifyDockedStackMinimizedChanged 方法被触发了。这里由于当时自己的失误,写错了。notifyDockedStackMinimizedChanged这个是在最小化的时候触发的,我们可以在文章结尾看到,我说的这个就是最小化的流程。

关于detachStackLocked触发了哪个呢?我们看它代码:

看,我是写错了,好尴尬。好了,这个就此翻篇了。

我们此讲,开始围绕分屏的启动过程。

00

我们回到触发分屏的地方PhoneStatusBar.java 里面

(具体可以在android recent key长按事件弹起触发最近列表故障分析)进行阅读三个虚拟按键的代码,这里我们只关心最近列表长按事件:

这里我们看到,长按receents键(也就是虚拟按键),代码逻辑为:

mRecents为空

不支持分屏

这里supportsMultiWindow方法为:

判断了一个系统属性config_supportsMultiWindow为真  以及非低内存版本,则认为系统可以支持分屏

isSplitScreenFeasible 判断当前分屏的大小,是否是满足系统要求的最小的分屏像素值。

其中mMinimalSizeResizableTask 值为

所以这里的代码含义为:

如果mRecents为空

不支持分屏

屏幕当前不够分屏的最小值

则直接返回,不进入分屏模式

否则,进入分屏。

01

我们来到分屏的代码位置,这里有一个判断

dockSide == WindowManager.DOCKED_INVALID 此状态表示当前没有处在分屏模式下,因此我们需要进入分屏

我们看下这里的WindowManagerProxy.getInstance().getDockSide()如何处理的

这里可以看到,来到了WMS(WindowManagerServer.java)位置,看下getDockedStackSide方法

这里如何判断的呢?

找下当前的默认显示屏幕,然后判断下DockStack是否存在,如果存在,则在分屏模式,如果不存在,则当前不是分屏模式

我们这里在启动分屏,所以此时不在分屏模式模式,于是乎,我们来到代码:

千里之行,启程。

03

dockTopTask是由 mRecents调用的,那么 mRecents是谁呢?我们学习下。

这里我们要去找getComponent实现,然后我们记得之前讲过,SystemUIApplication里面有个services集合,系统会在启动systemui时候触发,创建每一个的实例,

这里我们也可以看到有Recents.class,于是我们看下这个类(关注start方法,启动systemui会触发每个实例的start方法)。

只看核心,其他忽略。我们看到了有个putComponent动作,将自己加入进来,于是我们这里就可以通过getComponent拿到它了。

于是我们来到Recents.java,去看下dockTopTask方法。我们需要慢慢品尝:

如果userSetup返回false,则不进入分屏,里面是获取两个值而已,不做深入扩展。

如果没有默认的屏幕大小initialbounds,我们获取一下。

紧跟着一个判断

这里为:是否有运行Task(一般都有),不在homestack(就是不要在桌面下瞎按,它不进入分屏的),是否在pinningActivity,这个是什么鬼呢?就是我们可锁定只在这个当前栈里面,你要跑出去,必须通过其他方式触发(这个模式开启了,肯定不允许分屏,因为我就是要限定你在这个TASK内)

经过这几个条件筛选,我们来到了真正代码位置

这里又有一个条件runningTask.isDockable,这个值是什么呢?我们需要看看:(脑子回路不再扩散,这里我们直接来到TaskRecord.java,看看)

这里有很多条件,来决定是否可以允许分屏。我们关注下一个线索:

ActivityInfo.isResizeableMode(mResizeMode),我们找下这个值从哪来,于是我们追到了:(PackageParser.java),有如下代码:

这段代码的含义为:我们在manifest.xml配置的分屏参数,resizeableActivity ,如果为true,意思为支持,然后如果还配置了supportsPictureInPicture,那么还支持画中画。

否则,我们判断当前apk的targetSdkVersion,如果大于N,你之前没有配置resizeableActivity,在N平台向上,认为你就不想支持分屏,其他的再进行判断,设置为强制分屏模式。(这条线没追,不敢贸然下结论,后续再扩展)

为什么将这个,原因是我们开发app在manifest.xml会配置分屏参数,这里就是代码的地方。

resizeMode都有哪些值呢?

我们可以看到都有哪些模式。

04

继续dockTopTask方法:

我们假设进入sSystemServicesProxy.isSystemUser(currentUser) 为true,对于其他用户的,不去关注,就是我们开机进入的默认用户,user0

于是我们看到代码走到mImpl.dockTopTask,我们直接过来(mImpl==RecentsImpl.java)

我们看到了代码走入了moveTaskToDockedStack,这里继续跟进,我们看到了:

这里mIam就是ActivityManagerServer的代理端。此时,此方法moveTaskToDockedStack则会通过binder,进入到ActivityManagerServer的对应方法里面。

看我们上一节打出来的attachstack 方法的栈信息,是否完美的匹配上了。

小有成就,继续狂奔:

我们来看下ActivityManagerService.java里面moveTaskToDockedStack方法的注释:

参数为:

需要移动到docked stack的task id

createMode 创建横屏的还是竖屏的分屏

toTop 是否将这个task 和stack移动到最上面

animate 是否需要一个动画

initialBounds 初始化docked stack的边界值

我们看下这里的实际传递的参数:

taskId 这个不用管,只需要知道当前正在运行的TASK的id值即可。

dragMode = NavigationBarGestureHelper.DRAG_MODE_NONE

stackCreateMode=ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT

initialBounds = 屏幕大小信息,这里为 0 0 720 1280

moveHomeStackFront = true

animate=false

onTop = true

于是我们来看moveTaskToStackLocked (ActivityStackSupervisor.java)这个地方代码:

我们这里需要慢慢看,于是停下休息会,转个圈,跳个舞先。

05

我们完整的看一遍代码.发现代码量还是巨大的。需要你有耐心,继续听我扯代码,一段段来

final TaskRecord task = anyTaskForIdLocked(taskId); 找到taskid的对应数据,找不到返回

task.stack != null && task.stack.mStackId == stackId(参数stackId= DOCKED_STACK_ID) 判断是否已经是进入了分屏模式了,如果是,返回

stackId == FREEFORM_WORKSPACE_STACK_ID这里判断是否进入了自由模式,但是系统又没有支持这个模式,报异常出来。

task.getTopActivity() 拿到栈最上面的activity信息

获取下task的对应栈值

mightReplaceWindow变量的意思 可能需要替换window(此分支未作关注,我们围绕主线走)

mWindowManager.deferSurfaceLayout(); 停止surface更新,我们需要更新数据,随后使用continueSurfaceLayout继续。我们可以理解成一个锁,锁住画布。

我们继续跟踪

来到moveTaskToStackUncheckedLocked方法处

我们看注释:

移动特定的任务到传入的stack id(我们传入的为DOCKED_STACK_ID,移动的是当前最上面的那个TASK)

task 需要移入的task

stackId 移入的stackid (DOCKED_STACK_ID)

toTop =  true

,默认取得反值

forceFocus =false(不需要强制Focus)

reason 就是个注释,我们不管

返回我们最终移入 的stack信息

06

来,互相伤害,我们贴出moveTaskToStackUncheckedLocked的完整代码:

stackId必须是多窗口的栈,并且系统要支持多窗口,否则出错。我们当前满足此情况,不会出错。

final ActivityRecord r = task.topRunningActivityLocked(); 获取task上的顶部Activity信息

final ActivityStack prevStack = task.stack; 获取对应的栈信息

final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);

是否是focus状态

final boolean wasResumed = prevStack.mResumedActivity == r; 是否是resume的

wasFront 是否是当前最前的栈

这里我们处理下,如果当前是需要移动到DOCKED_STACK_ID栈,但是当前task却是不可Resize的,我们需要将栈移动到自己的栈,或者全屏栈上

我们跳过stackId == FREEFORM_WORKSPACE_STACK_ID 这个case,我们当前不关注自由模式的状态处理

下来我们进入核心位置:

final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop); 获取这个栈,如果需要创建,创建它

mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop); 移动task到对应的stack上面

stack.addTask(task, toTop, reason);

然后我们当前的stack,存储下task

其中我们看下getStack的方法:

核心

这里我们看到的ActivityDisplay 为获取对应displayId的一个实例,所以我们系统是支持多种显示设备的。

创建一个ActivityContainer(stackId),用来实现stack栈信息,然后存储下来。

我们看下

activityContainer.attachToDisplayLocked(activityDisplay, onTop);

这里便是将这个stack挂在对应显示屏的列表上面(一般我们默认显示屏是手机)

我们继续深入去看:

我们看到了attachDisplay方法

关键方法attachStack,我们跟入看下:(首先看注释)

创建一个taskstack放置在对应的显示容器内

stackId ==栈Id,我们这里认为为DOCKED_STACK_ID

displayId =我们认为为默认屏,手机即可

onTop = true

这里接住了我们上节所讲

,我们创建了分屏,于是系统通知systemui,显示divider线。

07

下来我们继续追attachStack这个方法

这里又出现一个方法,stack.attachDisplayContent(displayContent);,我们仔细看下它

getStackDockedModeBounds方法为:

这里直接有值了,我们直接赋值返回了,于是我们说下这个值从哪赋值的mDockedStackCreateBounds

我们之前看到的

moveTaskToDockedStack –> 方法里面有个 mWindowManager.setDockedStackCreateState(createMode, initialBounds); 这里给了赋值。(需要看的可以向上重新回头阅读下这个信息)

我们继续跟踪,退回attachDisplay方法,看到如下代码:

我们需要调整task的大小信息。

我们这里不再深入细化,因为这里逻辑太多,我们当前需要了解的是:

系统这个时候,重新将所有的task大小计算,我们一般应用所在的FULL_SCREEN_STACK 会重新调整,然后给当前app通知进入分屏。

为什么讲这个呢?因为这里是系统向activity发出的回调,告知系统进入分屏模式,需要activity作出响应的地方。

我们看栈信息:

系统在此时发送了REPORT_MULTI_WINDOW_MODE_CHANGED_MSG消息出去

我们在ActivityStackSupervisor.java里面找到它的处理方法:

然后它调用了app.thread.scheduleMultiWindowModeChanged 向对应app转送消息

app.thread里面:

关于app.thread 我们暂时不做分析,原因是你就理解成AMS和APP的桥梁,这个app.thread会将事件带给我们的ActivityThread.java

这个ActivityThread.java熟悉吧我们的框架里面核心类,在系统创建新的进程(也就是第一次启动新的app)的时候,会进行fork新的进程,然后加载了ActivityThread.java,作为主线程。嗯,就这么多,就此打住。

ActivityThread.java继续内容为:

r.activity.dispatchMultiWindowModeChanged 这个就是调用我们的activity的回调去了

看,我们又找到一个方法onMultiWindowModeChanged,我们在写分屏app时候需要自己实现的一个方法。

又来总结下:

如此一来,我们就创建出来DOCKED_STACK_ID的一个栈了,其中stack是维护task任务的,task是维护activity的。它们就是如此的关系。然后我们系统将创建好的stack关联到WMS,调整task的大小,然后通知当前的activity,我们当前进入分屏模式下了,你要在你的onMultiWindowModeChanged 里面做出响应。(看到了吗?我们分屏在activity的一个生命周期方法,在此处出现了)

08

回退回来,我们整理下:

moveTaskToStackUncheckedLocked 里面主要做了几件事情

final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop); 获取DOCK_STACK,如果没有,就创建它

task.mTemporarilyUnresizable = false;

mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop); 移动当前的task进入DOCK_STACK里面,更新状态,传递divider显示与否的消息到systemui,传递给activity onMultiWindowModeChanged,来告知消息。

stack.addTask(task, toTop, reason); 加入当前的AMS的管理里面即可。

后面触发了mWindowPlacerLocked.performSurfacePlacement();方法,引发绘制动作,我们的分屏启动完成了。

09

我们再来看一个问题,就是我们的分屏,会在屏幕上画出一个分割线,这个线的位置如何定义出来的呢?

我们回到DividerWindowManager.java ,我们之前讲过,我们的分割线是在分屏开启后进行显示,加入到WMS里面去,我们可以看到一个关键信息

这里我们看到 TYPE_DOCK_DIVIDER,是这个View的类型,非常特殊。

我们搜索这个关键字,可以看到很多内容,我们简单说下里面一些:

WindowManagerService.java 里 addWindow,

这里为如果当前View的类型为TYPE_DOCK_DIVIDER 我们要加入到DockedDividerController里面,按照上一节的说法,这个DockedDividerController会在系统的Vsync里面,实时触发,这里则会判断是否有divider之类的状态。

PhoneWindowManager.java 里面的 layoutWindowLw 方法:

给TYPE_DOCK_DIVIDER 赋值绘制区域,系统边界值的信息。

我们再看一个类WindowState.java,里面的关键方法computeFrameLw

有段内容:

我们打个断点在这里:

我们惊奇的发现,我们的栈里面有performSurfacePlacementLoop,还有moveTaskToStackLocked–>mWindowManager.continueSurfaceLayout(); 这里触发了启动绘制。看到这条线路,我们可以找到整个窗体的计算过程路径。

这里我们关心的是这个分割线的位置:(这里mFrame便是计算好的位置信息了,我们当前值为 Rect(0, 568 - 720, 664),高96,看这里是不是在屏幕中间)

看代码:

依据我们的DOCKED_STACK的位置,去计算frame,这里我们为TOP

而这里的96如何来的呢?我们知道创建分割线的地方为 Divider.java的addDivider,里面有个信息:

我们这里的dp2px=2,所以会是96高。

10

如上,我们发现我们穿过层层阻碍,走完了分屏的创建过程的大半过程。分屏过程错复杂,我们还有个触发最近列表的过程需要讲解。

我们看到了这里,经历了dock的整个创建过程,我们再回到我们出发的起点位置,看个内容:

RecentsImpl.java的dockTopTask方法,我们启动分屏的开始部分。

我们看到了,如果创建成功,我们进入里面的方法EventBus的send我们不去关注了,想要了解的,去看看EventBus的总线方式,以及如何解耦的。核心便是通过注解,系统将需要接收的通过方法的参数类型进行匹配。

我们需要看下面的:showRecents ,这个便是我们进入分屏,下方出现的最近列表界面启动的地方。此方法我们不详细扩展了,本质就是启动了一个activity即可(startRecentsActivity)。

如果有收获,赞赏鼓励下作者。

更多内容,关注微信公众号:代码GG之家。

加微信 code_gg_boy  进入代码GG交流群

下一讲,主要围绕分屏的退出过程

时间: 2024-08-05 18:14:47

google 分屏 横屏模式 按home键界面错乱故障分析(二) 分屏的启动过程的相关文章

IOS 应用中从竖屏模式强制转换为横屏模式

在 iPhone 应用里,有时我们想强行把显示模式从纵屏改为横屏(反之亦然),CocoaChina 会员 "alienblue" 为我们提供了两种思路 第一种:通过人为的办法改变view.transform的属性. 具体办法: view.transform一般是View的旋转,拉伸移动等属性,类似view.layer.transform,区别在于View.transform是二维的,也就是使用仿射的办法通常就是带有前缀CGAffineTransform的类(可以到API文档里面搜索这个

Html之网页分屏浏览模式

URL网页可自定义--->就是改动data="……"的网页链接即可.但这种模式有局限性只能作为浏览模式,有的窗口是不能打开的. <head> <title>Html之网页分屏浏览模式</title> <style type="text/css"> <!-- body { font-family: "宋体"; font-size: 9pt; margin-top: 0px; margin-

iPhone开发之在应用中从竖屏模式强制转换为横屏模式

1.强制横屏模式,百度上找到很多方法,但是真正能用到项目上的却少之又少,有的是iOS版本太低的时候出的,过时了:有的方法被Apple官方私有化了. 2.开发工具设置 3.代码实现的两种方法 (1) 此方法已经被Apple官方私有化,不能通过审核,但是用来实现简易测试非常方便 1 [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft animated:NO]; (

[Android] 关于系统工具栏和全屏沉浸模式

关于System Bars,之前写过几篇相关的文章: [Android]获取系统顶部状态栏(Status Bar)与底部导航栏(Navigation Bar)的高度 [Android]状态栏的一些认识 [Android]锁定屏幕 这三篇是按顺序写的,本来只是项目上的应用,其实并不需要深究的,查到方法并能用起来就好.随着应用程序的一些深入设计,大家总想要更好的界面和体验,所以有些东西并不能只是知道方法就结束了,是得要去深入研究研究的.通过这个过程我觉得,从应用层面来讲,想实现一个功能很简单,但若想

让应用在横屏模式下启动

让应用在横屏模式下启动 为了让应用从竖屏模式切换为横屏模式,我们得完成三件事情: 1. 更改设备的”Supported Device Orientations”设置; 2. 让Main.storyboard中的视图控制器中的view使用横屏显示; 3. 更改ViewController.m中的一行代码,允许视图控制器自动旋转到横屏模式. // Returns a Boolean value indicating whether the view controller'??s contents s

iPad横屏模式研究

在iPad应用开发时如何让设备只支持横屏(landscape)模式,我做了多次尝试,并没有发现比较简捷的设置方法.我尝试了大概大概3种方式. 1.通过XCode设置“iPad Deployment info”,只选择横屏左和横屏右,部署测试后并没有生效,这种方法实质是在xxx_info.plist项目配置文件中添加如下信息: <key>UISupportedInterfaceOrientations~ipad</key> <array> <string>UI

关于python2.7交互模式&quot;退格键乱码&quot;

在RHEL上装好了python2.7.6, 进入到交互模式后(就是输入python之后进入的screen), 发现退格键和方向键都使用不了,(变成^H^H^H之类的乱码) 一个命令输入错误了只能从头开始,不能删除,很是痛苦. 查阅文档之后发现是readline模块没有安装的原因, 所以只能卸载掉python,先装上readline模块,然后再安装python来解决问题,T_T. 有没有readline模块可以在交互模式下输入 import readline来测试 需要安装的两个包为:readli

Linux关闭休眠和屏保模式

本人因为特殊需求,想让某台Linux主机始终显示某个程序,显示器不能关机或者休眠或进入屏保模式. 环境:Ubuntu 11.10 最小化模式安装并安装有轻量级桌面openbox(非gnome).因为X的屏幕保护,电源管理机制跟gnome不一样.所以无法使用对gnome的一套工具进行设置.所幸发现了名为xset的小工具 xset程序主要对启动X windows以后,对x windows属性进行设置的.具体用法可使用 man xset进行查看.此处不再描述. 可以使用xset命令设置各项: 1 2

Win10 Mobile开始屏幕横屏模式怎么变?看看这款设计

微软坚信Win10 Mobile系统更加适合在小尺寸平板上使用,不过遗憾的是,Win10 Mobile开始屏幕并不支持横屏模式,因此许多情况下用户使用横屏模式回到开始屏幕后不得不手动转正屏幕方向,十分不方便. ▲Win10 Mobile开始屏幕概念设计 事实上,目前Windows10桌面系统的开始菜单排布已经为Windows 10 Mobile开始屏幕的横屏模式指明了方向娱乐 城,这样大家更熟悉.Sofoklis根据Win10统一特性带来了Win10移动版系统的开始屏幕的横屏概念设计. ▲Win