1. 程式人生 > >google 分屏 橫屏模式 按home鍵介面錯亂故障分析

google 分屏 橫屏模式 按home鍵介面錯亂故障分析

問題描述
[Dialer&&MMS]進入分屏後在橫屏模式按home鍵介面錯亂

操作步驟
1.進入撥號盤2.長按recent進入分屏,按home回主介面3.點選MMs進入簡訊,轉到橫屏模式4按home鍵,故障發生

環境描述

android7.0.1
螢幕解析度 720*1280
手機:eng版本

故障效果(看狀態列,出現兩條黑線)



分析
00
套路,使用hierarchyviewer 工具,去找下出錯的內容屬於誰,屬於哪個類。(為什麼頻繁使用這個呢?快速便捷的定位,能不高呼)



展看DockedStackDivider,檢視介面資訊:


這裡看到定位資訊真多,我們看到這裡三個View都是自定義的,這就讓我們輕而易舉的找到了地方
於是我們快馬加鞭,來專案裡面查詢下DividerView


這裡我們看到了程式碼屬於packages\systemui下面,於是我們可以得出一個結論,分屏的線條是在SystemUI程序,於是乎,我們是可以除錯SystemUI的,我們先不去除錯,直接看程式碼分析。
01

緩下,我們先要去看DividerView.java是個什麼內容。按照之前的講法,我們來看看(只說重點了,詳細的去之前文章閱讀了)



非常普通了,就是個簡單的自定義View而已


這個onFinishInflate方法就是初始化View的地方,於是我們看到一些View,我們此處關注mBackground 和mMinimizedShadow(為什麼,因為我們出錯的就是這兩個顯示出來了)
這裡我高亮了mHandle,這個是拖動分割線的響應View哦。(內心激動的人,可以先去看本檔案裡面的onTouch函式即可)
我們先處理此問題,關於分屏流程,後面展開。

本文搜尋mBackground,核心關注它變為隱藏的時候。我們看到


只找關鍵的了,定義的和設定變數的一些亂七八糟的就沒截圖了。我們看下 mBackground.setScaleY(MINIMIZE_DOCK_SCALE);的程式碼上下文然後我們看到完整程式碼:(有兩處,一個是有動畫,一個沒有而已)



MINIMIZE_DOCK_SCALE的定義為:


看到這裡,有隱藏view的邏輯,setScaleX(MINIMIZE_DOCK_SCALE=0)便會將此View隱藏。我們看下這裡的邏輯,如果不是最小化minimized(就是顯示的了),那就走resetBackground方法。這裡我們看到,系統將mBackground設定了錨點居中,縮放還原為1,設定mMinimizedShadow隱藏如果mDockSide == WindowManager.DOCKED_TOP 設定PivotY為0(錨點為0,作為縮放的原點)然後將Y方向縮為0如果mDockSide == WindowManager.DOCKED_LEFT或者DOCKED_RIGHT ,設定錨點,設定X縮放為0



(此處程式碼有些搞笑,都是隱藏,你這時候縮放X Y方向為0有區別嗎?並且在上面resetBackground是直接將XY的縮放都回到1),人家還原的時候都不管你之前到底縮放了哪個方位,你自己縮放判斷個鬼。so。。。出錯就在這裡了,你說你搞笑不?

這裡有個很有意思的套路:mMinimizedShadow.setAlpha(minimized ? 1f : 0f);看這裡是不是反的? 如果最小化,顯示這個,如果不是最小化,隱藏。這裡他做這個是幹嘛的呢?其實google這個在最小化的時候顯示mMinimizedShadow,按照這個名字,它會是個shadow(讓你知道這個是分屏了,有個陰影效果),如果顯示分屏的時候,它就隱藏了。(它就是想在你分屏隱藏的時候,在狀態列上做個陰影,讓你知道你處在分屏模式下而已)
我們看下除了DOCKED_TOP ,此列舉都有哪幾個值:



看這個的目的,我們可以看出上面的程式碼,是否忽略掉了一些狀態,於是我們繼續來看。

02
我們再看下我們的程式碼



會發現我們的else是不是沒有全部的選擇,少了DOCKED_BOTTOM 和 DOCKED_INVALID,於是我們假如這裡的mDockSide==這兩個的其中一個,會發現什麼問題呢?
mBackground 沒有隱藏哦mMinimizedShadow 是設定了顯示,但是我們再去它的類去瞅瞅吧。

mMinimizedShadow 類的方法裡面有:(onLayout 和onDraw都是自定義view的關鍵實現,還有一個是onMeasure,此處沒有複寫而已)
我們看下這幾個方法:
onLayout



updatePaint方法:


onDraw


看到了沒這裡它也沒管這個值DOCKED_INVALID(DOCKED_BOTTOM),於是用了預設的顏色,而預設畫出來是黑色,你說這就沒意思了吧。忽略DOCKED_BOTTOM這個列舉我們可以解釋,因為當前系統設計不會放置在下面的,於是DOCKED_BOTTOM值可以忽略,所以我們看到,此處有一個 DOCKED_INVALID狀態,會導致在隱藏分割線的時候,沒有處理程式碼,引起分割線顯示在上面。而系統自以為所有手機都跟它一樣,配置很高,but現實是還有低配機子的啦,於是此狀態會產生,引出此問題。於是,我們看完了程式碼,從邏輯上分析出來是這個原因,那麼事實覺得這個情況會發生嗎? 我們補充log,檢視下這裡的錯誤時候的值,發現,此處為-1( DOCKED_INVALID),於是得出結論了。到這裡,此問題就算完結了,但是,但是,我要講故障修復,就沒必要這麼繁瑣了,因此,我們還要繼續深入,去看看程式碼。我要去講下分屏這條線索,追個路徑出來。
03

搜尋DividerView,我們繼續來深入



我們開啟Divider.java,發現了很多內容。


首先,我們看下它繼承了誰?SystemUI,這個要幹嘛呢?我們搜尋Divider,通過篩選(只在SystemUI包下,為什麼,之前已經說過,這個類在這個包下,別的應用引用的機會基本為0)我們看到如下內容:(篩檢過)


然後我們在SystemUIApplication.java 裡面稍微停留下:看到


startServicesIfNeeded方法


這裡遍歷了我們上面的mServices裡面的所有元素,有我們的Divider.java(看這裡都轉為了SystemUI類處理了,所以我們Divider要繼承SystemUI,沒毛病)主要走裡面的start方法 和onBootCompleted方法我們先不回到Divider的start方法,我們再繼續深入看下,看startServicesIfNeeded如何呼叫起來的。


我們跟了下KeyguardService.java 發現不對路,放棄掉。我們忽略自己本身的方法,於是來到SystemUIService.java裡面



發現onCreate裡面呼叫了startServicesIfNeeded方法,於是我們繼續看,搜尋SystemUIService



我們忽略註釋和xml,以及本身的檔案,於是我們看到了SystemServer.java(熟悉不?系統server建立的地方),我們看看去




看到了不?系統建立完server,會到mActivityManagerService的systemReady,這裡面啟動了systemUI,然後呼叫了一個SystemUIService,這個在建立自己的時候,呼叫了SystemUIApplication裡面的startServicesIfNeeded,完成了systemui的元件建立和初始化,而這裡,也有我們分屏的Divider
04
逛完了系統建立systemui的過程,我們再次回來,慢慢來看我們的分隔線Divider.java



從上面的流程來看,我們需要呼叫關注它的start方法,我們看下:這裡我高亮了幾個內容:DividerWindowManager ,分隔線管理者。(等會細看)update 這個主要更新我們的引數,主要為移除Divider,然後新增(依據當前螢幕的橫豎屏處理),判斷是否為最小化,是的話就要想辦法隱藏了。mDockDividerVisibilityListener 這個類DockDividerVisibilityListener,我們看下:


這裡我們發現,它的繼承為:IDockedStackListener.Stub,於是乎,它是跨程序呼叫這裡我們使用ssp.registerDockedStackListener(mDockDividerVisibilityListener);將這個監聽註冊到系統裡面去(後面分析它)mForcedResizableController 暫時先放過,後面分析。
05
我們說完了大概,然後我們回來看下DividerWindowManager這個類


沒有繼承,只有方法,於是我們看方法add(關鍵),直接通過windowmanager給系統加入了一個View。高亮一個資訊TYPE_DOCK_DIVIDER(專門給它量身定做的型別,是不是很開心,我們又找到了關鍵字)



remove,移除這個View。


setSlippery 設定是否在滑動中,中間的那個線是可拖拽的。



setTouchable是否可點選。


我們回過頭看看Divider裡面的update方法:


(看下這裡的removeDivider 和addDivider)removeDivider



這裡mWindowManager就是DividerWindowManager,我們不用說了吧。
addDivider



載入佈局,設定大小,設定寬高,載入到windowmanager裡面去。
06

總結上面的內容:
systemui裡面有個Divider,裡面管理著分割線的佈局,有個監測系統的服務端(mDockDividerVisibilityListener),系統要不要顯示,通過這條線回調回來,再通知介面顯示與否即可。這裡Divider還有個方法:onConfigurationChanged,當系統屬性發生改變時候,會通過這條線路傳送回來(這裡會做簡單的事情,先移除view,然後依據當前的螢幕方向,新建view,然後根據是否要顯示,預設是顯示的。如果不需要顯示,則隱藏掉view--用了縮放XY大小為0和Alpha來做的)
07

總結看完,繼續奔波,我們先向系統層邁進,於是我們仔細來看下DockDividerVisibilityListener



看看它有哪些實現onDividerVisibilityChanged顯示隱藏的通知onDockedStackExistsChanged 存在與否的通知onDockedStackMinimizedChanged最小化的通知(我們當前在這個裡面,因為我們沒有退出分屏,只是進入了主介面,分割線會最小化)onAdjustedForImeChanged 當有輸入法的時候,調整大小和位置的通知onDockSideChanged 當dock的位置調整的時候,主要就是dock在左邊還是右邊,這種資訊。我們先不去看這裡面每個方法的具體實現,我們找下它們在哪裡被呼叫的(我們以onDockedStackMinimizedChanged來作為搜尋依據)


自己本身可以忽略NavigationBarView裡面是個空實現,忽略我們看到了DockedStackDividerController.java


繼續搜尋


ActivityManagerInternal.java,註釋忽略


ActivityManagerService.java 關鍵地方,主要完成dockstack的動作



DockedStackDividerController.java


registerDockedStackListener 註冊的地方(我們之前在Divider的start裡面,有註冊這個的動作)



setMinimizedDockedStack


這是個內部方法,我們說過,內部方法外部不能直接呼叫,所以我們要找這個在本文裡哪裡呼叫了
animateForMinimizedDockedStack 內部,我們還是要找誰用它了


WindowManagerService.java 是個case,然後呼叫了



mAmInternal(ActivityManagerInternal)的通知即可。mAmInternal是誰呢?





幾經週轉,在ActivityManagerService.java裡面有


這麼多,發現線索主要在
setMinimizedDockedStack方法
(DockedStackDividerController.java)
animateForMinimizedDockedStack 方法 (DockedStackDividerController.java)
和ActivityManagerService.java裡面

於是我們線索收斂了。我們繼續向下走

08

我們不做太多擴散,我們從setMinimizedDockedStack切入下在DockedStackDividerController.java裡面找setMinimizedDockedStack



(突然發現,這裡面程式碼錯綜複雜,如此分析下去,我要陷入其中,系統還是呼叫太複雜,要講清千絲萬縷,不能如此細緻入微去講了,汗,於是我們先從單向切入看下)我們看這個是退出與否的狀態切換
**


我們先繼續看看notifyDockedStackExistsChanged的呼叫地方,我現在不去用編輯器來只是簡單搜搜了(如此下去,沒有盡頭,呼叫地方太多,於是我們換個思路),開始除錯system_server我們關注下WindowManagerService裡面的 程式碼


這個是多使用者時候,需要判斷當前使用者是否處在分屏模式下,我們暫時可以忽略。



按照註釋,是attachstack的時候有通知 (DOCKED_STACK_ID ,特殊棧id,主要就是標誌誰在分屏的那個棧上)所以這兩個函式是個關鍵點,我們可以下斷點,去跟蹤程式碼流程。detachStackLocked 退出也有呼叫notifyDockedStackExistsChanged,於是乎我們上斷點,除錯下

attachstack 方法的棧資訊為:


detachStackLocked 的棧資訊為:


這裡關注的棧方法為:


通過兩個棧資訊,我們便可以得到關鍵的兩個東西:啟動分屏的棧,關閉分屏的棧,這兩個在分屏模式如此重要的方法,已經被我們攔到,其餘的不是迎刃而解嗎?

我們繼續跟蹤detachStackLocked流程,會發現我們的notifyDockedStackMinimizedChanged 方法被觸發了。


這個是我們退出分屏的時候,傳送回來的訊息,於是我們需要看下這個是誰呼叫的


看看看,又是windowAnimator,又是animateLocked方法,我們清晰的看到了
doFrame(如呼吸一般)如期的出現在眼前。 我們看下animateLocked方法裡面的一行


來到了DockedStackDividerController的animate方法


看此處,如果mAnimatingForMinimizedDockedStack為真,則走入我們的最小化方法了。

先消化下,下一章節再見。

下一章節,繼續分析分屏,主要講解分屏的啟動過程。

相關推薦

google 模式 home介面錯亂故障分析

問題描述 [Dialer&&MMS]進入分屏後在橫屏模式按home鍵介面錯亂 操作步驟 1.進入撥號盤2.長按recent進入分屏,按home回主介面3.點選MMs進入簡訊,轉到橫屏模式4按home鍵,故障發生 環境描述 android7.0.1 螢幕解析度 720*1280 手機:eng版

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

activity 根據 動作 home鍵 更新 lean 全屏 擴展 ddt google 進入分屏後在橫屏模式按home鍵界面錯亂(二) 你確定你了解分屏的整個流

Android橫豎切換, 手機鎖以及Home和返回時的Activity的生命週期

用手機做了實驗,結果如下: 豎屏切橫屏會呼叫完整的生命週期: 03-09 17:08:35.170: I/MainActivity(14086): MainActivity Paused 03-09 17:08:35.200: I/MainActivity(14086):

Home後繼續播放音樂

參考 一般聽音樂的app在鎖屏狀態和按home鍵後都能繼續播放音樂,有些強大的甚至在來簡訊後能夠繼續播放,不被中斷,比如:百度ting。本文主要介紹鎖屏和按home鍵後仍播放音樂實現。 主要依靠“音訊會話”(AVAudioSession)來實現。詳細關於AVAudio

android橫豎切換、home返回所觸發的生命週期

程式執行:onCreate()->onStart()->onResume() 按home鍵:onPause()->onSaveInstanceState()->onStop

iOS 全域性禁止,但UIWebView 全播放視訊的解決辦法

如有侵犯,請來信[email protected] 我參考這個設法實現了自己的需求,下一篇文章會寫出來,嗯嗯,終於有原創的啦,好吧,是微創新。 iOS 全域性禁止橫屏,但UIWebView 全屏播放視訊,橫屏,解決辦法 UIWebview在播放網頁視訊的時候

Activity橫豎切換時先看到/豎再變換回豎/

因為公司APP上的一個Bug遇到一個問題,跟了下這個問題,稍微記錄一下。 需求:當App從後臺回到前臺的時候,要彈出一個廣告頁Activity A ,而廣告頁希望是豎屏顯示的,這樣才符合引人入勝的效果咯。 開發的實現:做這個需求,之前開發的實現是,在Application中註冊A

Android螢幕控制一:強制豎

  預設情況下,app會隨著手機方向的改變進行螢幕切換,手機橫向擺放的時候,app螢幕就會切換到橫屏,反之切換到豎屏,但是app是一般都是根據豎屏設計的,一旦切換到橫屏,佈局就會亂掉,所以沒有考慮橫屏設計的app一般要強制設定為豎屏,強制豎屏有兩種方法:   

Android WebView 真正播放視訊 全 播放

WebView是個大坑,需求是希望點選一個視訊連結跳轉到網頁上播放視訊,然後點選全屏能自動橫屏。。。查了很多東西,最後直接上程式碼吧,註釋都在,直接看吧,使用也很簡單,直接把URL傳到這個Activity就行了,如果你的網站的視訊是flash播放的話,會出現空白的情況,無法

ios 只讓播放視訊的 時候能夠全 其他全部非全 目前已經測試很多 可以

- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { if ([NSStringFrom

home,再次點選頁面按鈕,回到退出頁面

第一步:去除application中的 android:launchMode=“singleTask” 第二步:然後新增:android:alwaysRetainTaskState=“true” (作用:是否保持原來的狀態。當我們按HOME鍵返回桌面,"true"表示任務棧的狀

正在執行的android程式,home之後退回到桌面,再次點選桌面圖示避免再次重新啟動程式的終極解決辦法

在網上找了好多方法,有的說 在AndroidManifest.xml中修改,MainActivity的launchMode,去掉android:launchMode="singleTask"啟動模式即可。 AndroidManifest.xml的配置程式碼 可是我的程式碼就是這樣

Android關於專案中遇到的home退出到桌面,再次開啟重新啟動程式的解決方法

我的專案是使用高德地圖做交通類的,主要是Activity和Fragment之間的切換。 我遇到的問題是:我在執行打包後的apk時,進入程式後,無論在哪個介面按home鍵回到桌面,當再次開啟需要重新啟動而不是回到開啟之前的操作介面;而在程式碼除錯的時候不會出現這種問題。 解決方法:在網上搜了好

Android打release包時Home,再桌面icon時出現app重新啟動的問題

在專案開發中遇到了按Home鍵,再按桌面icon時出現app重新啟動的問題,這個時候我們將啟動的那個activity(一般為廣告頁面GuideActivity)的啟動模式android:launchMode="singleTask"去掉即可,但是有些APP很奇怪,debug版

home後Activity啟動慢的問題

場景: APP中有兩個Activity, MainActivity和TestActivity。首先開啟MainActivity,然後按home鍵回到launcher。這是從後臺(service)startActivity, 這個過程要等5秒左右,google官網有介紹,從後

app退出時,讓app在後臺執行,apphome到桌面,再返回app不重啟app

第一個問題:app退出時,讓app在後臺執行,類似於home鍵的功能,最小化 解決方法: public boolean onKeyDown(int keyCode, KeyEvent event) {     if (keyCode == KeyEvent.KEYCODE

Activity上有Dialog的時候Home時的生命週期

        對於學習Android的朋友來說Activity的7個生命週期肯定不陌生,但是有些極端情況對於剛接觸Android的朋友們來說分析生命週期可能就有些困難。問題如題,下來我們就結合結果來分析一下整個過程。        如下為我們邏輯的程式碼其實就是一個按鈕的監

Home後重新進入app又重新啟動問題解決

問題: 安裝完第一次使用的時候,點選home鍵,再進去會進入啟動頁。退出app第二次使用,就沒這問題。 解決方法: 在根activity的oncreate方法中 貼上這行程式碼 if (!is

home後,重新開啟app後2-3秒鐘顯示上次的頁面

按home鍵後,重新開啟app後2-3秒鐘顯示上個直播間的頁面(該問題出現的前提進入直播間,然後點選開始錄音的情況下,home間退到後臺)原因:在applicationDidEnterBackground傳送通知,對app退到後臺進行相關處理操作(如停止錄音操作);    

解決apphome執行到後臺,再次執行程式避免再次開啟app的MAIN頁面

在專案中使用了一張啟動頁。 正在執行的Android程式,按home鍵之後退回到桌面,在次執行程式啟動頁會再次執行一次。 例如:一個android程式包含兩個Activity,分別為MainActivity和SplashActivity(也可以是登陸頁面等)