詳細内容和具體分析流程也可以下載下傳PDF文檔:
手機開機後,按POWER鍵無法關閉螢幕,過了很長一段時間(20s左右)才能恢複正常。
Platform:MT6581
Android版本:4.4KK
BuildType:user
系統軟體版本:SWC1I+ZZ
系統RAM:512M
Android中對于輸入事件的擷取和分發分别由InputManagerService的InputReader以及InputDispatcher兩個線程負責,按鍵屬于輸入事件的一種。
InputReader首先通過EventHub去擷取各種類型的輸入事件,當擷取到之後會先通知InputDispatcher,并在進入輸入事件隊列之前先進行系統政策上的一些攔截,對于POWER鍵這樣的特殊按鍵會進行提前的特殊處理,下圖為具體的POWER按鍵處理流程:

通過問題現象、log以及POWER鍵的處理過程,發現POWER鍵的處理流程之是以沒有正常進行,是因為PowerManagerService沒有及時擷取到BOOT_COMPLETED廣播,進而導緻其相關狀态沒有被更新,關鍵代碼為goToSleepNoUpdateLocked函數中關于判斷目前狀态以決定是否繼續處理POWER鍵上下流程的代碼,其中有關于mBootCompleted狀态的判斷,具體代碼如下:
mBootCompleted狀态開機預設為false,在收到BOOT_COMPLETED廣播之後更新為ture,PowerManagerService接收BOOT_COMPLETED并更新mBootCompleted狀态的關鍵代碼如下:
既然mBootCompleted沒有被及時更新,那麼就說明BOOT_COMPLETED廣播沒有被及時收到,沒有收到的話就要看為什麼BOOT_COMPLETED廣播沒有被及時發出。
廣播(Broadcast)是一種在元件之間進行消息傳遞的方式,這些元件可以在相同或者不同的程序中,整個機制是在Binder程序間通信的基礎上實作的。
廣播機制存在一個注冊排程中心,就是AcitivityManagerService。廣播接收者訂閱消息的形式就是将自己注冊到ActivityManagerService中,并且指定要接收的廣播類型。當廣播發送者發送一個廣播時,會首先發送到ActivityManagerService中,然後ActivityManagerService根據這個廣播類型找到相應的廣播接收者,最後将這個廣播發送給它們。
廣播接收者的注冊方式分為靜态注冊和動态注冊兩種。靜态注冊是通過在配置檔案AndroidManifest.xml中聲明所感興趣的廣播的類型,以便ActivityManagerService能夠找到它們。動态注冊時在程式的代碼中調用指定的接口将廣播接收者注冊到ActivityManagerService中。在同等情況下動态注冊的廣播接收者會優先收到廣播。
廣播的發送方式分為有序和無序兩種。我們在注冊廣播接收者時可以指定它們的優先級,當ActivityManagerService接收到有序廣播時,它就會将這個有序廣播發送給符合條件的、優先級較高的廣播接收者處理,然後再發送給符合條件、優先較低的廣播接收者處理。當ActivityManagerService接收到無序廣播時,它就會忽略廣播接收者的優先級,先發送給動态注冊的廣播接收者,然後再将靜态注冊的廣播接收者放入有序廣播的處理隊列中進行處理,是以對于無序廣播動态注冊的接收者總是優先接收到廣播。
由于本文分析的問題出在廣播的發送過程中,是以這裡着重介紹廣播的發送過程,以下為KK4.4上ActivityManagerService中finishBooting函數發出BOOT_COMPLETED廣播的關鍵代碼:
從上圖的代碼中可以看到紅色框住的參數,此參數為true的話即以有序的方式發送廣播,false即為無序方式。具體的廣播發送過程如下:
1、廣播發送者将一個特定類型的廣播發送給ActivityManagerService(這裡是ActivityManagerService直接發送BOOT_COMPLETED廣播)
2、ActivityManagerService接收到一個廣播之後,首先找到與此廣播對應的廣播接收者,然後根據廣播的類型區分有序還是無序,有序廣播會将所有的廣播接收者無論靜态還是動态注冊都按照優先級進行排序放置到一個清單中,然後加入到有序廣播的排程隊列中。無序廣播會先将動态注冊的廣播接收者放置到一個清單中,然後加入到無序廣播的排程隊列并向ActivityManagerService所運作的線程的消息隊列發送一個消息,最後會将靜态注冊的廣播接收者放置到另外一個清單中,然後加入到有序廣播的排程隊列中,是以靜态注冊的廣播接收者總是會按照有序的方式進行處理,然後同樣會向ActivityManagerService所運作的線程的消息隊列發送一個處理廣播的消息。這個時候對于廣播發送者來講,一個廣播就發送完成了。
3、當ActivityManagerService所運作的線程的消息隊列中的處理廣播的消息被處理時,ActivityManagerService就會從廣播排程隊列中找到需要接收廣播的廣播接收者,并将對應的廣播發送給他們所運作在應用程式程序。在這裡需要特别說明的是ActivityManagerService對于無序廣播的動态注冊的廣播接收者沒有處理時間以及順序上的限制,不會主動産生ANR,隻是異步的将廣播發送給接收者處理就傳回。而對于有序廣播則會嚴格限制時間和處理順序,
4、廣播接收者所在的應用程式程序接收到ActivityManagerService發送過來的廣播之後,并不是直接調用廣播接收者進行處理,而是将接收到的廣播封裝成一個消息,發送到主線程的消息隊列中。當這個消息被處理時,應用程式程序才會将它所描述的廣播發送給相應的廣播接收者處理。
發送過程中各個元件的調用關系如下圖:
以上是對無序廣播的動态注冊的廣播接收者的發送過程,對靜态注冊的無序廣播接收者以及有序廣播的動态和靜态注冊的廣播接收者的發送過程則比上面更複雜一點,牽扯到發送逾時機制、有序同步回報機制、啟動靜态廣播接收者所在程序的機制等,大緻的原理及過程如下:
1、對無序以及有序廣播的靜态廣播接收者的調用過程需要先判斷其所附屬的程序是否存在,如果不存在需要先啟動其所附屬的程序,然後程序啟動之後就将廣播發送給其所在的應用程序,其所在的程序接收到廣播之後則繼續分發給具體的接收者,當廣播接收者執行完畢之後需要将結果回報給ActivityManagerService,以便讓ActivityManagerService繼續将廣播分發下一個接收者,如果在分發一個廣播給接收者的過程中逾時了則會産生ANR。
2、對有序廣播的動态注冊的廣播接收者的分發過程與靜态類似,隻是少了啟動應用程序的過程,同樣有分發完成的同步回報以及逾時無響應的ANR機制。
通過測試發現Android4.2不存在此問題,是以對比KK4.4以及JB4.2上同樣的代碼發現在發送BOOT_COMPLETED廣播時有一個很關鍵的地方有差別,就是KK4.4上BOOT_COMPLETED廣播由之前的無序發送變為有序發送,即上圖中的紅色框框圈住的位置,false即為無序發送,ture即為有序發送,首先通過檢視android官方原生代碼發現不是MTK修改,KK4.4原生即為true,然後檢視android的送出曆史,找到針對此修改的送出記錄如下:
從以上曆史記錄中可以看到google為了解決calendar的一個問題将BOOT_COMPLETED的廣播發送方式由之前的無序改為了有序,同時還進行了延時的發出處理,進而最終影響了整個BOOT_COMPLETED的發送和處理流程,導緻BOOT_COMPLETED的處理被安排在無序廣播之後,另外由于BOOT_COMPLETED之前還有其他有序廣播在待發送隊列,隻有那些有序廣播被順序處理完才會處理BOOT_COMPLETED,最終在處理有序的BOOT_COMPLETED的時候還會按照優先級進行排序,如果此時靜态注冊的接收者的優先級大于動态注冊的優先級則在發送時還會等待靜态注冊的接收者的程序起來,然後再繼續發送,是以結果就導緻系統動态注冊的接收者遲遲接收不到BOOT_COMPLETED,影響系統元件的整體功能體驗。
通過以上分析,為了避免在BOOT_COMPLETED廣播之前的其他有序廣播以及設定了高優先級的靜态注冊的BOOT_COMPLETED廣播的廣播接收者占用時間,影響系統元件的接收和功能狀态更新,我們給出以下解決方案:
1、将BOOT_COMPLETED廣播的發送方式由有序變為無序,這樣就是讓動态注冊的系統元件及時的接收到廣播消息,進而能夠正常的更新狀态,解決因有序而産生的問題。
應用解決方案之後系統的行為恢複正常,第三方以及其他應用程式對系統開機的影響被消除,與JB4.2行為基本保持一緻,最終達到要求和解決問題的目的。
#analysed by jinshi.song from SWD2 Framework team.
<a target="_blank" href="mailto:">#[email protected]</a>
#201407161600