google 分屏 橫屏模式 按home鍵界面錯亂故障分析(二) 分屏的啟動過程
google 進入分屏後在橫屏模式按home鍵界面錯亂(二)
你確定你了解分屏的整個流程?
__biz=MzI1MjMyOTU2Ng==&mid=2247484263&idx=1&sn=031d1a44696364a75bbd173cf0cdb303&chksm=e9e42856de93a140d79686be8d92c067eecd6312f6584638e361ac9e46f9b80ebac52909403f&scene=21#wechat_redirect">Android 關機對話框概率沒有陰影故障分析
android recent key長按事件彈起觸發近期列表故障分析
__biz=MzI1MjMyOTU2Ng==&mid=2247484145&idx=1&sn=589d41467e4c8927057278dc271a6488&chksm=e9e429c0de93a0d67edd470622f1750d3735ffd7ba1e4000e25c99e6eafc563c2604d7914378&scene=21#wechat_redirect">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的類型,非常特殊。
我們搜索這個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交流群
下一講,主要環繞分屏的退出過程
google 分屏 橫屏模式 按home鍵界面錯亂故障分析(二) 分屏的啟動過程