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交流羣 下一講,主要環繞分屏的退出過程