google 进入分屏后在横屏模式按home键界面错乱( 三)
你确定你了解分屏的整个流程?
故障解析系列文章列表:
google 分屏 横屏模式 按home键界面错乱故障分析(一)
google 分屏 横屏模式 按home键界面错乱故障分析(二)
Android 关机对话框概率没有阴影故障分析
android recent key长按事件弹起触发最近列表故障分析
google 分屏 popup无法显示故障分析
代码阅读,请到此处http://androidxref.com 查看原生代码
分享此文,便是对代码GG的最大支持,欢迎分享。
前情回顾:
google 分屏 横屏模式 按home键界面错乱故障分析(二)
上一节我们主要围绕了分屏的启动过程,我们深入跟踪,追出整个创建的流程出来,同时我们也和分屏的divider的相结合,讲解了启动分屏流程。
在流程中,我们也分析了app在编写分屏的时候,需要处理的配置(manifest.xml配置 resizeableActivity )以及需要实现的方onMultiWindowModeChanged。我们还知道了系统如何给分屏的那个分界线设置位置,初始化的地方。掌握了这个启动过程,我们这节开始分析关闭过程
注意:我不是整体分析完,然后写架构之类的文章。我就是把我如何一步步跟踪代码,去带你一起学习,如何跟踪代码,确定代码逻辑,流程的。因此,我写的必须要实践,要不然会觉得不知所云,兴趣索然。
00
我们从上一章节知道,我们长按recent按键的触发流程为:PhoneStatusBar.java –>(onLongClick)mRecentsLongClickListener–>toggleSplitScreenMode
我们核心的就在这里。
我们这一章节从此处来开始展开。
我们知道,这里如果当前处在分屏模式下,dockSide == WindowManager.DOCKED_INVALID则为假,我们进入else的逻辑代码
EventBus.getDefault().send(new UndockingTaskEvent());这里就是我们需要关注的代码。
于是我们先讲下EventBus,EventBus是什么呢?目标是解决我们系统到处去写接口类,导致我们修改非常繁琐。那么,EventBus如何来解决此问题的呢。我们简单描述下它的原理。
在app启动的时候,EventBus初始化,然后我们在需要EventBus来解决消息传递的时候,进行注册,这个时候EventBus框架做了一件事情,将你注册的这个类存储下来,并且解析出来,你们可以接收我发送的哪些消息。
主要就是找方法里面有没有onBusEvent这个方法,它的参数则指明可以接收的类型,这个作为EventBus发送时候,决定是否发给你的原因。如此一来,我们在EventBus里面将自己注册进来,通过去写onBusEvent方法,通过它的参数(EventBus在注册的时候,会反射此类,拿到onBusEvent方法)给到EventBus我们接收的类型
然后我们在应用里面,使用EventBus.send(参数类型),EventBus则会在自己的注册表里面去找,谁可以接收此参数,然后发给它即可。如此,完成了简单使用的一个流程。
所以我们这里需要搜索谁接收UndockingTaskEvent这个类型。
我们可以看到DividerView.java
此处接收了这个参数类型,于是我们来这里看下具体代码。
01
我们看到代码
首先判断了是否处在分屏下,并且当前mDockedStackMinimized不能是true(这里true的意思就是已经最小化了,这里就不要重复去走)
我们看下startDragging方法:
先清除动画。重新计算initializeSnapAlgorithm位置,这个方法主要完成我们的退出到哪个位置。(里面包含了系统支持的1:1分屏 4:3分屏,16:9分屏等一系列数据,主要为滑动拖动分屏的分割线的时候,进行确定最近的一个位置点,然后我们就会分屏最终定格在此位置)关于这个分割线的讲解,会单独放在一节,暂时放过。
mWindowManagerProxy.setResizing(true); 设置当前开始resize task了。具体为:
这里先简单说下,就是通知所有Windowstate,我们现在要变化分屏大小了,这个时候需要在分割线变化的时候,实时的给计算下上下两个窗口(一个在
DOCKED_STACK 一个在FULLSCREEN_WORKSPACE_STACK这个上面)我们此处重新设置下这个值。
继续startDragging方法:
EventBus send一个消息StartedDragingEvent ,我们看下这个在干什么,根据我们讲的,这里我们搜索StartedDragingEvent,看下谁在处理。
我们看到ForcedResizableInfoActivityController 处理了,撤掉了一个消息即可。
然后我们在回到出发点:(接收退出分屏的地方)
继续分析startDargging:
计算我们的分屏情况,左右还是上下,这个来定位计算我们分屏需要的动画位置,方向。我们先放过这个,纯计算确定下一个位置。(简单说下,我们一般的上下分屏,我们退出的时候,是不是有分割线移动的方向,然后上下界面大小该如何变化)
拿到当前的view(分割线)的位置
我们核心要看的为:
这里传入当前位置,最终位置,动画时长,动画几秒后开始,结束后延时,动画效果. 嗯,我们就要去看这个地方了
02
所以我们的关注点在stopDragging方法里面:
动画的我们不去关注,我们直接看下flingTo方法
我们看这个getFlingAnimator,关键位置:
这里用了动画,动画进行实时的更新,从当前位置,到最后目标位置,启动动画,在实时检测此动画,做一个动作:resizeStack(就是一直实时变化,改变DIVIDER_STACK的TASK大小,同时修正全屏栈的大小,触发刷新,实时更新)
在动画完成后,做endAction动作(resizestack走完后,这里有关键的处理,在做退出分屏动作)。
于是我们需要跟踪resizeStack动作,去看完成了什么。我们只关心我们的退出过程。
可以看到核心为:计算下一个需要resizetask的位置,通过resizeDockedStack设置下去,同时通过setResizeDimLayer改变下阴影(这个可以不关心)我们看主线,发现就是围绕着resizeDockedStack来展看的,于是我们进入这边来瞧一下:
WindowManagerProxy.java 里面为:
计算出位置来,然后执行mResizeRunnable
看到没,又走到ActivityManagerServer 的resizeDockedStack方法了。
继续跟进,我们再来看:
ActivityStackSupervisor里面的 resizeDockedStackLocked方法:
我们需要围绕这个 resizeDockedStackLocked来扩展,我们仔细来看:
我们首先看这里的stack如何获取的
final ActivityStack stack = getStack(DOCKED_STACK_ID);于是它指的是分屏的那个DICKED_STACK栈。然后我们调用resizeStackUncheckedLocked,来对这个栈进行调整。
这里核心的思想为:首先判断参数是否有效,然后进行遍历stack的task列表,进行通知updateOverrideConfiguration,修改值,具体为:
updateOverrideConfiguration 里面主要完成的是更新
mOverrideConfig(calculateOverrideConfig)配置值,然后我们这里看到,如果task从全屏分屏变化的时候,会走一个函数:
scheduleReportMultiWindowModeChanged(又是一个多窗口开发的关键触发点,可以接收系统当前的变化信息。),最后调用的为
我们这里还需要关注的一个变量为:
这里的mFullscreen是个关键变量,决定了是否退出分屏的状态。
03
通知完updateOverrideConfiguration的时候,然后进行resize动作。
这个动作主要完成stack的Bounds数据更新,然后通过performSurfacePlacement让系统重新更新屏幕,触发绘制动作。返回的为是否为rawFullscreen,这个值决定着我们是否退出了分屏。
我们总结下resizeStackUncheckedLocked的方法:
更新之前被resize的task,调用updateOverrideConfiguration通知过去,引发task中的activity进行对应修改。如果退出分屏,会通知MultiWindowModeChanged到对应的task中所有activity。
更新每个task的bounds,config,然后使用mWindowManager.resizeStack将对应栈的bounds更改,触发一次绘制,引起界面更新。
完成这个resizeStackUncheckedLocked后,我们看到了有两个分支,我们当前忽略第一个,直接看else,原因是stack.mFullscreen为真的时候,意义为当前的stack已经进入全屏了,所以分屏模式就会退出。
我们先看else,这条线完成的是动画过程,一直修正当前的task的大小,引起系统绘制,完成分屏退出前的动画过程。
这里使用getStackDockedModeBounds计算出来dockedMode的边界,然后使用resizeStackLocked将其他栈进行调整,这里我们看下它的具体代码:
我们当前不是DOCKED_STACK_ID,于是乎我们往下继续看:
主要完成两个动作:
resizeStackUncheckedLocked(前面分析了,主要完成task的更新,然后调用resizestack执行调整栈大小,通知绘制)
于是我们看下面的代码:
ensureVisibleActivitiesConfigurationLocked,这里核心完成为:遍历stack上面的所有task,config是否变化,是否需要重启activity(ensureActivityConfigurationLocked)
如果需要updatedConfig的时候,我们调用resumeFocusedStackTopActivityLocked唤醒最上面的activity
这里我们不详细去看,因为变量太多,没法说全每个变量的变更状态。
我们这里需要描述的为:这里做了关键的几个事情:通知config变化(scheduleConfigurationChanged),如果需要重启,调用relaunchActivityLocked来实现,它完成了什么事情呢?
我们看下,(因为这个牵扯了activity的生命周期,所以我们看一下)
我们看到了这里有个scheduleRelaunchActivity,我们继续跟踪,看下代码
scheduleRelaunchActivity–>requestRelaunchActivity(ActivityThread.java),详细的自己关注下这里的handleRelaunchActivity方法即可。
完成了config的更新,我们可以看到代码会来到:
if(updatedConfig) resumeFocusedStackTopActivityLocked ,这里不仔细阅读了,因为又会走大量的ams ,wms的生命周期,导致我们分析无法继续下去,留个线,有兴趣的自己跟踪下。
04
我们来个总结:
我们在分屏的情况下,长按recent按键,引起退出分屏动作,有个动画,我们前面一直在看这个动画过程,主要实现方案,一直变更docked_stack的大小,然后变更其他栈的大小 ,通知acitvitys我们的task config有变化,需要重新reluncher的acitivty进入relauncher的状态
唤醒最上面的栈和acitivty(resumeFocusedStackTopActivityLocked ),于是,我们继续回来,看resizeDockedStackLocked函数。
05
resizeDockedStackLocked 经历了resizeStackUncheckedLocked –> resizeStackLocked –> ensureVisibleActivitiesConfigurationLocked,完成了这个动画过程的大量生命周期,紧挨着这里,我们再看个内容
这个在干什么呢?主要是防止我们resize过程出错,我们能够保证异常纠正过来,就是做这个事情的了。
这里代码流程为:10秒触发一个mTimeoutRunnable,将之前保留的bound信息,设置进去(resizeDockedStackLocked)。
这里我们要注意,这个是10s触发,但是有个关键地方hasTempBounds,如果这里为false,则会撤掉这个mTimeoutRunnable,返回。之后就不会触发mTimeoutRunnable的动作了。
整体来说,这段代码做这件事情:实时保存下当前分屏退出动画的bound值,然后我们10s后看下,如果这个mTimeoutRunnable还在,就表示系统当前在分屏动画过程出现问题了,于是我们想纠正这个错误,用之前保存的信息,再次触发resizeDockedStackLocked
分屏退出的动画流程则说完了,我们看下最终分屏栈是如何退掉的呢?
06
如上面所说,我们讲解resizeDockedStackLocked方法的时候,忽略了一个条件,具体为:
这里if我们之前跳过去了,因为我们想看下分屏动画过程,那么动画完了,是不是就会退出呢?我们回到触发分屏退出的原点,再次切入。
流程简单为:
DividerView.java 里面 :onBusEvent–>stopDragging–>flingTo–>getFlingAnimator
这里我们前面分析了resizeStack的过程,结束动画的时候,我们看到了endAction会触发,我们这里关注commitSnapFlags,看下它是做了什么。(endaciton其他的我们不去管,都是扫尾的变量而已)
这里我们看到当前分屏在退出的时候,是自身task的边界变大,直到屏幕大小,于是我们这里关注maximizeDockedStack这个方法。
于是,我们看到了核心为:resizeStack 这里参数是关健,我们继续去看:
参数情况:stackId = DOCKED_STACK_IDbounds = nullallowResizeInDockedMode = truepreserveWindows = trueanimate = falseanimationDuration = -1
于是我们逻辑走到mStackSupervisor.resizeStackLocked里面(前面分析的也是走到这里的),我们去看看:(我们关注下它里面的关键位置,我们这里stackId = DOCKED_STACK_ID)
然后resizeDockedStackLocked–>resizeStackUncheckedLocked 。我们又来到了resizeDockedStackLocked这里,此时我们要记住,这里的参数dockedBounds=null,这个是关健。我们关注下resizeStackUncheckedLocked里面的核心语句:
mWindowManager.resizeStack(stack.mStackId, bounds, mTmpConfigs,mTmpBounds, mTmpInsetBounds);
我们关注下bounds为null,看下这里为:(setbounds又调用了另一个setbounds方法)
这里我们看高亮的地方,mFullscreen为true了。因为我们传入的bounds=null
这里是我们需要关注的最核心的变化,我们回到resizeDockedStackLocked,去看这里退出来的条件:
根据我们之前的说法,此处mFullscreen为true,于是我们走入第一个状态,moveTasksToFullscreenStackLocked,此过程完成重要的使命,退出分屏。
07
经过层层分析,我们终于来到退出的地方,于是我们详细看下moveTasksToFullscreenStackLocked方法的代码:
我们需要关注的,都给你高亮了哦。resizeStackLocked完成的内容,之前详细讲过了,我们带过。这里需要将除了Docked_STACK栈,其余的都更新自己的TASK大小,STACK大小,然后通知activitys,通知绘制。然后我们走入moveTaskToStackLocked,完成最核心的代码:(当前我们moveTaskToStackLocked的目地为,将DOCKED_STACK上面的所有Task移动到全屏栈上),我们看个内容,按照我们之前第一节所讲,退出的时候,会走detachStackLocked方法,于是我们打断点,看下流程:
我们清晰的看到流程关系,根据这里的原因为,当你将一个task从一个栈移动到新的栈的时候,在旧的里面,则需要移除掉它。
我们直接看栈信息吧,这里判断,我们task移除之后,对应的stack为没有task了,于是我们需要将这个stack也一并移除掉,所以走到我们的detachStackLocked里面来了。
于是我们看下detachStackLocked的代码:
detachDisplay完成移除掉此stack里面的所有task对应的windows。然后我们进入notifyDockedStackExistsChanged,通知分屏退出了。
那我们就来看下这个notifyDockedStackExistsChanged方法:
这里主要做两件事情:
1:onDockedStackExistsChanged 通知退出,这个就会调用到我们systemui进程的Divider.java里面的DockDividerVisibilityListener类里面的onDockedStackExistsChanged方法,我们看下:
这里我们看下,都是用的post,为什么呢?原因为跨进程调用,过来是在binder线程,所以要更改view的变化,需要在主线程UI线程里面,于是我们使用了post将事件消息扔到主线程处理了。这里的动作先不去看了,关于ForcedResizableInfoActivityController,后续有时间,专门去扩展这个。
2:setMinimizedDockedStack,完成当前状态的变更通知,是否最小化,这里为false,于是是会最大化初始化一次变量。根据我们前面讲解,在系统将DOCKED_STACK上面的TASK 移动到FULL_STACK时候,我们DOCKED_STACK上面的TASK为空了,所以会移除掉DOCKED_STACK
08
我们延伸个地方,DockedStackDividerController.java类里面的一个关键方法checkMinimizeChanged ,触发调用它的地方为:notifyAppVisibilityChanged 和notifyAppTransitionStarting ,也就是显示与否和动画与否的时候,调用。我们看下它的代码:
getDockedStackVisibleForUserLocked 具体指什么呢?
这里判断是否存在DOCKED_STACK_ID并且是显示状态,如果返回null,则说明当前没有分屏,因此我们直接返回去了。代码核心的意义,注释如下:
这里是个关键地方,起着操作docked_stack的状态变更修正,是个核心代码方法,我们在学习分屏流程时,可以在此处设置断点,多次来回跟踪流程使用。
09
我们前面说过,系统在处理完后,会触发绘制,这里具体为:performLayoutLockedInner(WindowSurfacePlacer.java)我们关心下这里的代码,主要是最后一句:
这里我们会send一个msg出来,我们看下这个消息,用来更新DOCKED_STACK_DIVIDER.处理的地方在WindowManagerService.java
里面。
这里adjustForImeIfNeeded不做分析,主要完成输入Ime的调整。我们要关注上面的方法:reevaluateVisibility
这个在DockedStackDividerController.java文件里面,参数false。我们关心这里的notifyDockedDividerVisibilityChanged,完成分割线的显示隐藏
于是我们又要去systemui那边看响应了。
更新显示隐藏状态
如上,分屏的退出过程讲完。
如果有收获,赞赏鼓励下作者。
更多内容,关注微信公众号:代码GG之家。
加微信 code_gg_boy 进入代码GG交流群
下一讲:分屏状态下的旋转屏流程。