當前位置:編程學習大全網 - 源碼下載 - 安卓視圖層級大揭秘

安卓視圖層級大揭秘

最近接了壹個語音控制的功能,UI上的具體實現就是在應用上遮蓋壹個透明防觸層,在語音狀態下阻止用戶點擊,但不能影響物理返回鍵的Dialog呼出即控制,同時對於非物理返回鍵呼出的Dialog也要阻止操作。功能看起來很繞,我們用壹張圖片來具體說明壹下。

通過圖片不難看出,我們要實現的語音控制層其實是介於應用視圖與視圖內部提示框之上,同時又在Back返回鍵彈窗之下的壹個層級。因為壹直以來對安卓視圖層級的探究不是很深入,所以借著做這個功能對安卓視圖層級這壹塊的知識進行了壹下總結梳理。

首先讓我們通過壹張層級圖來明確幾個重要的概念Window,DecorView和mContentParent。

在Android中不管是Activity、Toast、ActionBar還是Dialog,他們的視圖都是附加到Window上,其實基本上所有的view同時通過Window來呈現的,因此Window可以理解為是view的承載者和管理者。Window 有三種類型,分別是應用 Window、子 Window 和系統 Window。應用類 Window 對應壹個 Acitivity,子 Window 不能單獨存在,需要依附在特定的父 Window 中,比如常見的壹些 Dialog 就是壹個子 Window。系統 Window是需要聲明權限才能創建的 Window,比如 Toast 和系統狀態欄都是系統 Window。

DecorView是Windows中的View的最頂層View。其實DecorView是FrameLayout的子類,它裏面包含了壹個存有ActionBar以及mContentParent的LinearLayout。

mContentParent這個名字可能會有些陌生,其實他就是我們經常使用的應用根布局,即android.R.id.content。Activity中的setContentView其實就是通過LayoutInflater將XML布局轉換成View並添加到mContentParent中。

每個Activity都會持有壹個Window,而在安卓中,Window只有唯壹的壹個實現類PhoneWindow ,所以每個Activity都會持有壹個PhoneWindow,在PhoneWindow中會持有頂層視圖DecorView。那麽Activity是怎麽建立與PhoneWindow的聯系的呢,讓我們通過源碼來探究壹下:

在Activity的啟動過程中會執行ActivityThread的performLaunchActivity方法,其中調用Activity的attach。在attach()方法中實例化Activity持有的mWindow。由於 Activity 實現了 Window 的 Callback 接口,因此當 Window 接受到外界的狀態改變時就會回調 Activity 的方法。

可以看到,在PhoneWindow裏面,出現了成員變量DecorView。而這裏,DecorView則是PhoneWindow裏面的壹個內部類,它是繼承於FrameLayout。

這是我們每次寫Activity都會調用的setContentView方法,它的內部調用了getWindow()的setContentView,這個mWindow就是PhoneWindow。

我們看到在PhoneWindow中有三個setContentView的重載方法。在setContentView(int layoutResID)中,首先判斷了mContentParent ,如果mContentParent 為空即為第壹次調用的時候,就執行installDecor()方法,創建DecorView,並添加到mContentParent上。如果mContentParent不為空,那麽將mContentParent中的view移除。接著通過mLayoutInflater將XML轉換為View樹,並且添加至mContentParent視圖中。 添加完成後回調通知onContentChanged,表示完成界面加載。

首先判斷mDecor是否為空,如果為空則通過generateDecor創建壹個DecorView,緊接著設置DecorView的獲取焦點能力為FOCUS_AFTER_DESCENDANTS,即先分發給Child View進行處理,如果所有的Child View都沒有處理,則自己再處理。第壹次DecorView未加載到mContentParent,所以mContentParent為空,調用generateLayout將setContentView內容添加到mContentParent。

定制過Acitivity的Actionbar或是Fullscreen的同學壹定都知道,requesetFeature方法需要在setContentView之前調用,這就是原因。setContentView的實質顯示是觸發了Activity的resume狀態,也就是觸發了makeVisible方法。

這裏將getWindow().getAttributes()作為了LayoutParams,在WindowManager中:

可以看到Activity的窗口類型是TYPE_APPLICATION,這個TYPE類型決定了在Window層的顯示層級,TYPE類型總覽如下:

Dialog不屬於View,他是應用的子window,所以這也是為什麽我們通過給mContentParent添加view無法實現遮擋Dialog的原因。Dialog 中 Window 同樣是通過 PolicyManager 的 makeNewWindow 方法來完成的,普通的 Dialog 必須采用 Activity 的 Context,如果采用 Application 的 Context 就會報錯。這是因為沒有應用 token 導致的,而應用 token 壹般只有 Activity 擁有。常規Dialog的TYPE為TYPE_APPLICATION_ATTACHED_DIALOG,通過不同的TYPE層級劃分我們可以找到置於常規Dialog之上的WindowManager LayoutParams 屬性,例如TYPE_SYSTEM_ALERT與TYPE_TOAST,設置了這兩個屬性的布局是可以將常規Dialog完全遮蓋的。他們的區別在於壹個是系統級別的Dialog壹個是Toast,系統Dialog需要申請權限。所以我們的第壹個方案就是可遮擋的Dialog使用常規Dialog,語音提示框采用TYPE_SYSTEM_ALERT。但是都知道安卓有壹個無法逃避的問題,就是廠商定制,在MUI的framework層,出於對“安全”的考慮,默認為用戶關閉了懸浮窗權限,也就是是說設置了TYPE_SYSTEM_ALERT屬性的視圖默認是無法顯示的,需要用戶手動開啟權限以後方可顯示。

雖然可以在用戶啟動的時候根據用戶機型選擇跳轉開啟權限頁,但作為壹個有情懷的開發這種不完美的體驗還是不能接受的。根據之前對安卓視圖層級的學習,我們有了第二套方案。應用視圖是存放於mContentParent他與Activity同屬TYPE_APPLICATION Window層級屬於最下層,常規Dialog的層級是TYPE_APPLICATION_ATTACHED_DIALOG,所以我們將常規Dialog作為最上層不可遮擋的提示框,下面只需考慮可遮擋的彈窗與語音控制兩層即可。因為語音控制層需要能夠遮擋提示彈窗,所以需要語音控制層在彈窗的上層,經過之前的學習,我們把彈窗加入到mContentParent,把語音控制層添加到DecorView層即可完美的解決問題。mContentParent為壹個FrameLayout,應用視圖通過sentContentView率先添加到mContentParent中,作為提示彈窗,添加順序壹定相對應用視圖置後,所以當提示彈窗再次向mContentParent添加的時候,即會添加到應用視圖之上。而DecorView是mContentParent的父容器,也是壹個FrameLayout,添加語音提示框的時候mContentParent壹定已經存在,所以添加的時候壹定會在mContentParent之上。

就這樣,壹個看似復雜的需求通過對安卓源碼的探究完美的解決了,很多時候當我們遇到難以解決的問題,不妨試試回到問題的原點,思考壹下問題的本質,很多時候都會有不壹樣的發現。

  • 上一篇:短線趨勢買賣點技術指標組合SAR+KDJ如何使用?附圖詳解
  • 下一篇:課題:PC機與單片機之間數據的傳送與實現。急用~
  • copyright 2024編程學習大全網