1. 程式人生 > >google 分屏 橫屏模式 按home鍵界面錯亂故障分析(二) 分屏的啟動過程

google 分屏 橫屏模式 按home鍵界面錯亂故障分析(二) 分屏的啟動過程

activity 根據 動作 home鍵 更新 lean 全屏 擴展 ddt

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鍵界面錯亂故障分析(二) 分屏的啟動過程