google 进入分屏后在横屏模式按home键界面错乱(二)css
你肯定你了解分屏的整个流程?java
Android 关机对话框几率没有阴影故障分析
android recent key长按事件弹起触发近期列表故障分析
google 分屏 popup没法显示故障分析android
分享此文即是对代码GG的支持,也是爱的表达方式,因此让爱来的猛烈些吧。微信
代码阅读。请到此处http://androidxref.com 查看原生代码markdown
前情回想:
google 分屏 横屏模式 按home键界面错乱故障分析(一)
上一节咱们主要环绕着分屏的那个线进行展开。分析了状态栏出现问题的问题缘由。同一时候咱们深刻定位,跟踪了systemui的启动过程
系统WMS AMS关于分屏的一些方法。同一时候systemUI经过Divider的服务端检測AMS WMS给回来的分屏当前状态。这边进行更新viewapp
同一时候咱们找到了AMS WMS里面关于分屏的关键方法attachstack以及detachStackLocked。关注了它的堆栈信息,以及怎么触发到systemUi的界面更新过程框架
上一讲后面出现了一个笔误。ide
详细为
咱们继续跟踪detachStackLocked流程,会发现咱们的notifyDockedStackMinimizedChanged 方法被触发了。oop
这里因为当时本身的失误,写错了。post
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的类型,很是特殊。
咱们搜索这个keyword。可以看到很是多内容,咱们简单说下里面一些:
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交流群 下一讲,主要环绕分屏的退出过程