前言
你好!
我是一只修仙的猿,歡迎閱讀我的文章,
Window,讀者可能更多的認識是windows系統的視窗,在windows系統上,我們可以多個視窗同時運行,每個視窗代表著一個應用程式,但在安卓上貌似并沒有這個東西,但讀者可以馬上想到,不是有小視窗模式嗎,像米UI最新的系統,不就是可以隨意創建一個小視窗,然后兩個應用同時操作?是的,那是屬于android中,window的一種表現方式,但是手機螢屏終究不能和電腦相比,因為螢屏太小了,小到只能操作一款應用,多個視窗就顯得非常不習慣,所以Android上關于視窗方面的知識讀者可能接觸不多,那window的意思就只是小米系統中那種小視窗嗎?
當然不是,Android框架層意義上的window和我們認識的window其實是有點不一樣的,我們日常最直觀的,每個應用界面,都有一個應用級的window,再例如popupWindow、Toast、dialog、menu都是需要通過創建window來實作,所以其實window我們一直都見到,只是不知道那就是window,了解window的機制原理,可以更好地了解window,進而更好地了解android是怎么管理螢屏上的view,這樣,當我們需要使用dialog或者popupWindow的時候,可以懂得他背后究竟做了什么,才能夠更好的運用dialog、popupWindow等,
當然,到此如果你有很多的疑問,甚至質疑我的理論,那就希望你可以閱讀完這一篇文章,我會從window是什么,有什么用,內部機制是什么,各種組件是如何創建window等等方面來闡述Android中的window,文章內容非常多,讀者可自選章節閱讀,
什么是window機制
先假設如果沒有window,會發生什么:
我們看到的界面ui是view,如我們的應用布局,更簡單是一個button,假如螢屏上現在有一個Button,如圖1,現在往螢屏中間添加一個TextView,那么最終的結果是圖2,還是圖3:
在上圖的圖2中,如果我要實作點擊textView執行他的監聽事件邏輯,點擊不是textView的區域讓textView消失,需要怎么實作呢?讀者可能會說,我們可以在Activity中添加這部分的邏輯,那如果我們需要讓一個懸浮窗在所有界面顯示呢,如上文我講到的小米懸浮窗,兩個不用應用的view,怎么確定他們的顯示次序?又例如我們需要彈出一個dialog來提示用戶,怎么樣可以讓dialog永遠處于最頂層呢,包括顯示dialog期間應用彈出的如popupWindow必須顯示在dialog的低下,但toast又必須顯示在dialog上面,
很明顯,我們的螢屏可以允許多個應用同時顯示非常多的view,他們的顯示次序或者說顯示高度是不一樣的,如果沒有一個統一的管理者,那么每一家應用都想要顯示在最頂層,那么螢屏上的view會非常亂,
同時,當我們點擊螢屏時,這個觸摸事件應該傳給哪個view?很明顯我們都知道應該傳給最上層的view,但是接受事件的是螢屏,是另一個系統服務,他怎么知道觸摸位置的最上層是哪個view呢?即時知道,他又怎么把這個事件準確地傳給他呢?
為了解決等等這些問題,急需有一個管理者來統一管理螢屏上的顯示的view,才能讓程式有條不紊地走下去,而這,就是Android中的window機制,
window機制就是為了管理螢屏上的view的顯示以及觸摸事件的傳遞問題,
什么是window?
那什么是window,在Android的window機制中,每個view樹都可以看成一個window,為什么不是每個view呢?因為view樹中每個view的顯示次序是固定的,例如我們的Activity布局,每一個控制元件的顯示都是已經安排好的,對于window機制來說,屬于“不可再分割的view”,
什么是view樹?例如你在布局中給Activity設定了一個布局xml,那么最頂層的布局如LinearLayout就是view樹的根,他包含的所有view就都是該view樹的節點,所以這個view樹就對應一個window,
舉幾個具體的例子:
- 我們在添加dialog的時候,需要給他設定view,那么這個view他是不屬于antivity的布局內的,是通過WindowManager添加到螢屏上的,不屬于activity的view樹內,所以這個dialog是一個獨立的view樹,所以他是一個window,
- popupWindow他也對應一個window,因為它也是通過windowManager添加上去的,不屬于Activity的view樹,
- 當我們使用使用windowManager在螢屏上添加的任何view都不屬于Activity的布局view樹,即使是只添加一個button,
view樹(后面使用view代稱,后面我說的view都是指view樹)是window機制的操作單位,每一個view對應一個window,view是window的存在形式,window是view的載體,我們平時看到的應用界面、dialog、popupWindow以及上面描述的懸浮窗,都是window的表現形式,注意,我們看到的不是window,而是view,window是view的管理者,同時也是view的載體,他是一個抽象的概念,本身并不存在,view是window的表現形式,這里的不存在,指的是我們在螢屏上是看不到window的,他不像windows系統,如下圖:
有一個很明顯的標志:看,我就是window,但在Android中我們是無法感知的,我們只能看到view無法看到window,window是控制view需要怎么顯示的管理者,每個成功的男人背后都有一個女人,每個view背后都有一個window,
window本身并不存在,他只是一個概念,舉個栗子:如班集體,就是一個概念,他的存在形式是這整個班的學生,當學生不存在那么這個班集體也就不存在,但是他的好處是得到了一個新的概念,我們可以以班為單位來安排活動,因他不存在,所以也很難從原始碼中找到他的痕跡,window機制的操作單位都是view,如果要說他在原始碼中的存在形式,筆者目前的認知就是在WindowManagerService中每一個view對應一個windowStatus,WindowManagerService是什么如果沒了解過可以先忽略后面會講到,讀者可以慢慢思考一下這個抽象的概念,后面會慢慢深入講原始碼幫助理解,
- view是window的存在形式,window是view的載體
- window是view的管理者,同時也是view的載體,他是一個抽象的概念,本身并不存在,view是window的表現形式
思考:Android中不是有一個抽象類叫做window還有一個PhoneWindow實作類嗎,他們不就是window的存在形式,為什么說window是抽象不存在的?讀者可自行思考,后面會講到,
Window的相關屬性
在了解window的操作流程之前,先補充一下window的相關屬性,
window的type屬性
前面我們講到window機制解決的一個問題就是view的顯示次序問題,這個屬性就決定了window的顯示次序,window是有分類的,不同類別的顯示高度范圍不同,例如我把1-1000m高度稱為低空,1001-2000m高度稱為中空,2000以上稱為高空,window也是一樣按照高度范圍進行分類,他也有一個變數Z-Order,決定了window的高度,window一共可分為三類:
- 應用程式視窗:應用程式視窗一般位于最底層,Z-Order在1-99
- 子視窗:子視窗一般是顯示在應用視窗之上,Z-Order在1000-1999
- 系統級視窗:系統級視窗一般位于最頂層,不會被其他的window遮住,如Toast,Z-Order在2000-2999,如果要彈出自定義系統級視窗需要動態申請權限,
Z-Order越大,window越靠近用戶,也就顯示越高,高度高的window會覆寫高度低的window,
window的type屬性就是Z-Order的值,我們可以給window的type屬性賦值來決定window的高度,系統為我們三類window都預設了靜態常量,如下(以下常用引數介紹轉自參考文獻第一篇文章):
-
應用級window
// 應用程式 Window 的開始值 public static final int FIRST_APPLICATION_WINDOW = 1; // 應用程式 Window 的基礎值 public static final int TYPE_BASE_APPLICATION = 1; // 普通的應用程式 public static final int TYPE_APPLICATION = 2; // 特殊的應用程式視窗,當程式可以顯示 Window 之前使用這個 Window 來顯示一些東西 public static final int TYPE_APPLICATION_STARTING = 3; // TYPE_APPLICATION 的變體,在應用程式顯示之前,WindowManager 會等待這個 Window 繪制完畢 public static final int TYPE_DRAWN_APPLICATION = 4; // 應用程式 Window 的結束值 public static final int LAST_APPLICATION_WINDOW = 99; -
子window
// 子 Window 型別的開始值 public static final int FIRST_SUB_WINDOW = 1000; // 應用程式 Window 頂部的面板,這些 Window 出現在其附加 Window 的頂部, public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; // 用于顯示媒體(如視頻)的 Window,這些 Window 出現在其附加 Window 的后面, public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1; // 應用程式 Window 頂部的子面板,這些 Window 出現在其附加 Window 和任何Window的頂部 public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2; // 當前Window的布局和頂級Window布局相同時,不能作為子代的容器 public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3; // 用顯示媒體 Window 覆寫頂部的 Window, 這是系統隱藏的 API public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4; // 子面板在應用程式Window的頂部,這些Window顯示在其附加Window的頂部, 這是系統隱藏的 API public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5; // 子 Window 型別的結束值 public static final int LAST_SUB_WINDOW = 1999; -
系統級window
// 系統Window型別的開始值 public static final int FIRST_SYSTEM_WINDOW = 2000; // 系統狀態欄,只能有一個狀態欄,它被放置在螢屏的頂部,所有其他視窗都向下移動 public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW; // 系統搜索視窗,只能有一個搜索欄,它被放置在螢屏的頂部 public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1; // 已經從系統中被移除,可以使用 TYPE_KEYGUARD_DIALOG 代替 public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4; // 系統對話框視窗 public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8; // 鎖屏時顯示的對話框 public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9; // 輸入法視窗,位于普通 UI 之上,應用程式可重新布局以免被此視窗覆寫 public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11; // 輸入法對話框,顯示于當前輸入法視窗之上 public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12; // 墻紙 public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13; // 狀態欄的滑動面板 public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14; // 應用程式疊加視窗顯示在所有視窗之上 public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38; // 系統Window型別的結束值 public static final int LAST_SYSTEM_WINDOW = 2999;
Window的flags引數
flag標志控制window的東西比較多,很多資料的描述是“控制window的顯示”,但我覺得不夠準確,flag控制的范圍包括了:各種情景下的顯示邏輯(鎖屏,游戲等)還有觸控事件的處理邏輯,控制顯示確實是他的很大部分功能,但是并不是全部,下面看一下一些常用的flag,就知道flag的功能了(以下常用引數介紹轉自參考文獻第一篇文章):
// 當 Window 可見時允許鎖屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
// Window 后面的內容都變暗
public static final int FLAG_DIM_BEHIND = 0x00000002;
// Window 不能獲得輸入焦點,即不接受任何按鍵或按鈕事件,例如該 Window 上 有 EditView,點擊 EditView 是 不會彈出軟鍵盤的
// Window 范圍外的事件依舊為原視窗處理;例如點擊該視窗外的view,依然會有回應,另外只要設定了此Flag,都將會啟用FLAG_NOT_TOUCH_MODAL
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
// 設定了該 Flag,將 Window 之外的按鍵事件發送給后面的 Window 處理, 而自己只會處理 Window 區域內的觸摸事件
// Window 之外的 view 也是可以回應 touch 事件,
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
// 設定了該Flag,表示該 Window 將不會接受任何 touch 事件,例如點擊該 Window 不會有回應,只會傳給下面有聚焦的視窗,
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
// 只要 Window 可見時螢屏就會一直亮著
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
// 允許 Window 占滿整個螢屏
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
// 允許 Window 超過螢屏之外
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
// 全屏顯示,隱藏所有的 Window 裝飾,比如在游戲、播放器中的全屏顯示
public static final int FLAG_FULLSCREEN = 0x00000400;
// 表示比FLAG_FULLSCREEN低一級,會顯示狀態欄
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
// 當用戶的臉貼近螢屏時(比如打電話),不會去回應此事件
public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
// 則當按鍵動作發生在 Window 之外時,將接收到一個MotionEvent.ACTION_OUTSIDE事件,
public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
@Deprecated
// 視窗可以在鎖屏的 Window 之上顯示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
// 表示負責繪制系統欄背景,如果設定,系統欄將以透明背景繪制,
// 此 Window 中的相應區域將填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的顏色,
public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
// 表示要求系統壁紙顯示在該 Window 后面,Window 表面必須是半透明的,才能真正看到它背后的壁紙
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
window的solfInputMode屬性
這一部分就是當軟體盤彈起來的時候,window的處理邏輯,這在日常中也經常遇到,如:我們在微信聊天的時候,點擊輸入框,當軟鍵盤彈起來的時候輸入框也會被頂上去,如果你不想被頂上去,也可以設定為被軟鍵盤覆寫,下面介紹一下常見的屬性(以下常見屬性介紹選自參考文獻第一篇文章):
// 沒有指定狀態,系統會選擇一個合適的狀態或者依賴于主題的配置
public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
// 當用戶進入該視窗時,隱藏軟鍵盤
public static final int SOFT_INPUT_STATE_HIDDEN = 2;
// 當視窗獲取焦點時,隱藏軟鍵盤
public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
// 當用戶進入視窗時,顯示軟鍵盤
public static final int SOFT_INPUT_STATE_VISIBLE = 4;
// 當視窗獲取焦點時,顯示軟鍵盤
public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
// window會調整大小以適應軟鍵盤視窗
public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
// 沒有指定狀態,系統會選擇一個合適的狀態或依賴于主題的設定
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
// 當軟鍵盤彈出時,視窗會調整大小,例如點擊一個EditView,整個layout都將平移可見且處于軟體盤的上方
// 同樣的該模式不能與SOFT_INPUT_ADJUST_PAN結合使用;
// 如果視窗的布局引數標志包含FLAG_FULLSCREEN,則將忽略這個值,視窗不會調整大小,但會保持全屏,
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
// 當軟鍵盤彈出時,視窗不需要調整大小, 要確保輸入焦點是可見的,
// 例如有兩個EditView的輸入框,一個為Ev1,一個為Ev2,當你點擊Ev1想要輸入資料時,當前的Ev1的輸入框會移到軟鍵盤上方
// 該模式不能與SOFT_INPUT_ADJUST_RESIZE結合使用
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
// 將不會調整大小,直接覆寫在window上
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
window的其他屬性
上面的三個屬性是window比較重要也是比較復雜 的三個,除此之外還有幾個日常經常使用的屬性:
- x與y屬性:指定window的位置
- alpha:window的透明度
- gravity:window在螢屏中的位置,使用的是Gravity類的常量
- format:window的像素點格式,值定義在PixelFormat中
如何給window屬性賦值
window屬性的常量值大部分存盤在WindowManager.LayoutParams類中,我們可以通過這個類來獲得這些常量,當然還有Gravity類和PixelFormat類等,
一般情況下我們會通過以下方式來往螢屏中添加一個window:
// 在Activity中呼叫
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
windParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
TextView view = new TextView(this);
getWindowManager.addview(view,windowParams);
我們可以直接給WindowManager.LayoutParams物件設定屬性,
第二種賦值方法是直接給window賦值,如
getWindow().flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
除此之外,window的solfInputMode屬性比較特殊,他可以直接在AndroidManifest中指定,如下:
<activity android:windowSoftInputMode="adjustNothing" />
最后總結一下:
- window的重要屬性有type、flags、solfInputMode、gravity等
- 我們可以通過不同的方式給window屬性賦值
- 沒必要去全部記下來,等遇到需求再去尋找對應的常量即可
Window的添加程序
通過理解原始碼之后,可以對之前的理論理解更加的透徹,window的添加程序,指的是我們通過WindowManagerImpl的addView方法來添加window的程序,
想要添加一個window,我們知道首先得有view和WindowManager.LayoutParams物件,才能去創建一個window,這是我們常見的代碼:
Button button = new Button(this);
WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
// 這里對windowParam進行初始化
windowParam.addFlags...
// 獲得應用PhoneWindow的WindowManager物件進行添加window
getWindowManager.addView(button,windowParams);
然后接下來我們進入addView方法中看看,我們知道這個windowManager的實作類是WindowManagerImpl,上面講過,進入他的addView方法看一看:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
可以發現他把邏輯直接交給mGlobal去處理了,這個mGlobal是WindowManagerGlobal,是一個全域單例,是WindowManager介面的具體邏輯實作,這里運用的是橋接模式,那我們進WindowManagerGlobal的方法看一下:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 首先判斷引數是否合法
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
// 如果不是子視窗,會對其做引數的調整
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
synchronized (mLock) {
...
// 這里新建了一個viewRootImpl,并設定引數
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 添加到windowManagerGlobal的三個重要list中,后面會講到
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// 最后通過viewRootImpl來添加window
try {
root.setView(view, wparams, panelParentView);
}
...
}
}
代碼有點長,一步步看:
- 首先對引數的合法性進行檢查
- 然后判斷該視窗是不是子視窗,如果是的話需要對視窗進行調整,這個好理解,子視窗要跟隨父視窗的特性,
- 接著新建viewRootImpl物件,并把view、viewRootImpl、params三個物件添加到三個list中進行保存
- 最后通過viewRootImpl來進行添加
補充一點關于WindowManagerGlobal中的三個list,他們分別是:
private final ArrayList<View> mViews = new ArrayList<View>(); private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();每一個window所對應的這三個物件都會保存在這里,之后對window的一些操作就可以直接來這里取物件了,當window被洗掉的時候,這些物件也會被從list中移除,
可以看到添加的window的邏輯就交給ViewRootImpl了,viewRootImpl是window和view之間的橋梁,viewRootImpl可以處理兩邊的物件,然后聯結起來,下面看一下viewRootImpl怎么處理:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 這里呼叫了windowSession的方法,呼叫wms的方法,把添加window的邏輯交給wms
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
...
}
}
viewRootImpl的邏輯很多,重要的就是呼叫了mWindowSession的方法呼叫了WMS的方法,這個mWindowSession很重要重點講一下,
mWindowSession是一個IWindowSession物件,看到這個命名很快地可以像到這里用了AIDL跨行程通信,IWindowSession是一個IBinder介面,他的具體實作類在WindowManagerService,本地的mWindowSession只是一個Binder物件,通過這個mWindowSession就可以直接呼叫WMS的方法進行跨行程通信,
那這個mWindowSession是從哪里來的呢?我們到viewRootImpl的構造器方法中看一下:
public ViewRootImpl(Context context, Display display) { ... mWindowSession = WindowManagerGlobal.getWindowSession(); ... }可以看到這個session物件是來自WindowManagerGlobal,再深入看一下:
public static IWindowSession getWindowSession() { synchronized (WindowManagerGlobal.class) { if (sWindowSession == null) { try { ... sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { ... }); } ... } return sWindowSession; } }這熟悉的代碼格式,可以看出來這個session是一個單例,也就是整個應用的所有viewRootImpl的windowSession都是同一個,也就是一個應用只有一個windowSession,對于wms而言,他是服務于多個應用的,如果說每個viewRootImpl整一個session,那他的任務就太重了,WMS的物件單位是應用,他在內部給每個應用session分配了一些資料結構如list,用于保存每個應用的window以及對應的viewRootImpl,當需要操作view的時候,通過session直接找到viewRootImpl就可以操作了,
后面的邏輯就交給WMS去處理了,WMS就會創建window,然后結合引數計算window的高度等等,最后使用viewRootImpl進行繪制,這后面的代碼邏輯就不講了,這是深入到WMS的內容,再講進去就太復雜了(筆者也還沒讀懂WMS),讀原始碼的目的是了解整個系統的本質與作業流程,對系統整體的感知,而不用太深入代碼細節,Android系統那么多的代碼,如果深入進去會出不來的,所以點到為止就好了,
我們知道windowManager介面是繼承viewManager介面的,viewManager還有另外兩個介面:removeView、updateView,這里就不講了,有興趣的讀者可以自己去閱讀原始碼,講添加流程主要是為了理解window系統的運作,對內部的流程感知,以便于更好的理解window,
最后做個總結:
window的添加程序是通過PhoneWindow對應的WindowManagerImpl來添加window,內部會呼叫WindowManagerGlobal來實作,WindowManagerGlobal會使用viewRootImpl來進行跨行程通信讓WMS執行創建window的業務,
每個應用都有一個windowSession,用于負責和WMS的通信,如ApplicationThread與AMS的通信,
window機制的關鍵類
前面的原始碼流程中涉及到很多的類,這里把相關的類統一分析一下,先看一張圖:
這基本上是我們這篇文章涉及到的所有關鍵類,且聽我慢慢講,(圖中綠色的window并不是一個類,而是真正意義上的window)
window相關
window的實作類只有一個:PhoneWindow,他繼承自Window抽象類,后面我會重點分析他,
WindowManager相關
顧名思義,windowManager就是window管理類,這一部分的關鍵類有windowManager,viewManager,windowManagerImpl,windowManagerGlobal,windowManager是一個介面,繼承自viewManager,viewManager中包含了我們非常熟悉的三個介面:addView,removeView,updateView,
windowManagerImpl和PhoneWindow是成對出現的,前者負責管理后者,WindowManagerImpl是windowManager的實作類,但是他本身并沒有真正實作邏輯,而是交給了WindowManagerGlobal,WindowManagerGlobal是全域單例,windowManagerImpl內部使用橋接模式,他是windowManager介面邏輯的真正實作
view相關
這里有個很關鍵的類:ViewRootImpl,每個view樹都會有一個,當我使用windowManager的addView方法時,就會創建一個ViewRootImpl,ViewRootImpl的作用很關鍵:
- 負責連接view和window的橋梁事務
- 負責和WindowManagerService的聯系
- 負責管理和繪制view樹
- 事件的中轉站
每個window都會有一個ViewRootImpl,viewRootImpl是負責繪制這個view樹和window與view的橋梁,每個window都會有一個ViewRootImpl,
WindowManagerService
這個是window的真正管理者,類似于AMS(ActivityManagerService)管理四大組件,所有的window創建最終都要經過windowManagerService,整個Android的window機制中,WMS絕對是核心,他決定了螢屏所有的window該如何顯示如何分發點擊事件等等,
window與PhoneWindow的關系
解釋一下標題,window是指window機制中window這個概念,而PhoneWindow是指PhoneWindow這個類,后面我在講的時候,如果是指類,我會在后面加個‘類’字,如window是指window概念,window類是指window這個抽象類,讀者不要混淆,
還記得我在講window的概念的時候留了一個思考嗎?
思考:Android中不是有一個抽象類叫做window還有一個PhoneWindow實作類嗎,他們不就是window的存在形式,為什么說window是抽象不存在的
這里我再拋出幾個問題:
- 有一些資料認為PhoneWindow就是window,是view容器,負責管理容器內的view,windowManagerImpl可以往里面添加view,如上面我們講過的addView方法,但是,同時它又說每個window對應一個viewRootImpl,但卻沒解釋為什么每次addView都會新建一個viewRootImpl,前后發送矛盾,
- 有一些資料也是認為PhoneWindow是window,但是他說addView方法不是添加view而是添加window,同時拿這個方法的名字作為論據證明view就是window,但是他沒解釋為什么在使用addView方法創建window的程序卻沒有創建PhoneWindow物件,
我們一步步來看,我們首先來看一下原始碼中對于window抽象類的注釋:
Abstract base class for a top-level window look and behavior policy. An
instance of this class should be used as the top-level view added to the
window manager. It provides standard UI policies such as a background, title
area, default key processing, etc.
頂層視窗外觀和行為策略的抽象基類,此類的實體應用作添加到視窗管理器的頂層視圖,
它提供標準的UI策略,如背景、標題區域、默認鍵處理等,
大概意思就是:這個類是頂級視窗的抽象基類,頂級視窗必須繼承他,他負責視窗的外觀如背景、標題、默認按鍵處理等,這個類的實體被添加到windowManager中,讓windowManager對他進行管理,PhoneWindow是一個top-level window(頂級視窗),他被添加到頂級視窗管理器的頂層視圖,其他的window,都需要添加到這個頂層視圖中,所以更準確的來說,PhoneWindow并不是view容器,而是window容器,
那PhoneWindow的存在意義是什么?
第一、提供DecorView模板,如下圖:
我們的Activity是通過setContentView把布局設定到DecorView中,那么DecorView本身的布局,就成為了Activity界面的背景,同時DecorView是分為標題欄和內容兩部分,所以也可以可界面設定標題欄,同時,由于我們的界面是添加在的DecorView中,屬于DecorView的一部分,那么對于DecorView的window屬性設定也會對我們的布局界面生效,還記得谷歌的官方給window類注釋的最后一句話嗎:它提供標準的UI策略,如背景、標題區域、默認鍵處理等,這些都可以通過DecorView實作,這是PhoneWindow的第一個作用,
第二、抽離Activity中關于window的邏輯,Activity的職責非常多,如果所有的事情都自己做,那么會造成本身代碼極其臃腫,閱讀過Activity啟動的讀者可能知道,AMS也通過ActivityStarter這個類來抽離啟動Activity啟動的邏輯,這樣關于window相關的事情,就交給PhoneWindow去處理了,(事實上,Activity呼叫的是WindowManagerImpl,但因PhoneWindow和WindowManagerImpl兩者是成對存在,他們共同處理window相關的事務,所以這里就簡單寫成交給PhoneWindow處理,)當Activity需要添加界面時,只需要一句setContentView,呼叫了PhoneWindow的setContentView方法,就把布局設定到螢屏上了,具體怎么完成,Activity不必管,
第三、限制組件添加window的權限,PhoneWindow內部有一個token屬性,用于驗證一個PhoneWindow是否允許添加window,在Activity創建PhoneWindow的時候,就會把從AMS傳過來的token賦值給他,從而他也就有了添加token的權限,而其他的PhoneWindow則沒有這個權限,因而也無法添加window,這部分內容我在另一篇文章有詳細講解,感興趣的讀者可以前往了解一下傳送門,
當然,PhoneWindow的作用肯定遠不止如此,這里列出很重要的三條,也是筆者目前學習到的三個最重要的作用,官方對于一個類的設計的考慮肯定是非常多,不是筆者簡單的分析所能闡述,而只是給出一個新的思考方向,帶大家認識真正的window,
總結一下:
- PhoneWindow本身不是真正意義上的window,他更多可以認為是輔助Activity操作window的工具類,
- windowManagerImpl并不是管理window的類,而是管理PhoneWindow的類,真正管理window的是WMS,
- PhoneWindow可以配合DecorView可以給其中的window按照一定的邏輯提供標準的UI策略
- PhoneWindow限制了不同的組件添加window的權限,
常見組件的window創建流程
上面講的是通過windowManagerImpl創建window的程序,我們通過前面的講解了解到,WindowManagerImpl是管理PhoneWindow的,他們是同時出現的,因而有兩種創建window的方式:
- 已經存在PhoneWindow,直接通過WindowManagerImpl創建window
- PhoneWindow尚未存在,先創建PhoneWindow,再利用windowManagerImpl來創建window
當我們在Activity中使用getWindowManager方法獲取到的就是應用的PhoneWindow對應的WindowManagerImpl,下面來講一下不同的組件是如何創建window的,
Activity
如果有閱讀過Activity的啟動流程的讀者,會知道Activity的啟動最后來到了ActivityThread的handleLaunchActivity這個方法,
關于Activity的啟動流程,我寫過一篇文章,有興趣的讀者可以點擊下方鏈接前往:
Activity啟動流程詳解(基于api28)
至于為什么是這個方法這里就不講了,有興趣的讀者可以去看上面的文章,我們直接來看這個方法的代碼:
public void handleLaunchActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...;
// 這里對WindowManagerGlobal進行初始化
WindowManagerGlobal.initialize();
// 啟動Activity并回呼activity的onCreate方法
final Activity a = performLaunchActivity(r, customIntent);
...
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
try {
// 這里創建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
// 這里將window作為引數傳到activity的attach方法中
// 一般情況下這里window==null
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback,
r.assistToken);
...
// 最后這里回呼Activity的onCreate方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
}
...
}
handleLaunchActivity的代碼中首先對WindowManagerGlobal進行初始化,然后呼叫了performLaunchActivity方法,代碼很多,這里只截取了重要部分,首先會創建Application物件,然后再呼叫Activity的attach方法,把window作為引數傳進去,最后回呼activity的onCreate方法,所以這里最有可能創建window的方法就是Activity的attach方法了,我們進去看一下:
final void attach(...,Context context,Window window, ...) {
...;
// 這里新建PhoneWindow物件,并對window進行初始化
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// Activity實作window的callBack介面,把自己設定給window
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
// 這里初始化window的WindowManager物件
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
}
同樣只截取了重要代碼,attach方法引數非常多,我只留下了window相關的引數,在這方法里首先利用傳進來的window創建了PhoneWindow,Activity實作window的callBack介面,可以把自己設定給window當觀察者,當window發生變化的時候可以通知activity,然后再創建WindowManager和PhoneWindow系結在一起,這樣我們就可以通過windowManager操作PhoneWindow了,(這里不是setWindowManager嗎,windowManager是什么時候創建的?)我們進去setWindowManager方法看一下:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 這里創建了windowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
這個方法里首先會獲取到應用服務的WindowManager(實作類也是WindowManagerImpl),然后通過這個應用服務的WindowManager創建了新的windowManager,
從這里可以看到是利用系統服務的windowManager來創建新的windowManagerImpl,因而這個應用所有的WindowManagerImpl都是同個內核windowManager,而創建出來的僅僅是包了個殼,
這樣PhoneWindow和WindowManagerImpl就系結在一起了,Activity可以通過WindowManagerImpl來操作PhoneWindow,
到這里Activity的PhoneWindow和WindowManagerImpl物件就創建完成了,接下來是如何把Activity的布局檔案設定給PhoneWindow,在上面我講到呼叫Activity的attach方法之后,會回呼Activity的onCreate方法,在onCreate方法我們會呼叫setContentView來設定布局,如下:
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
這里的getWindow就是獲取到我們上面創建的PhoneWindow物件,我們繼續看下去:
// 注意他有多個多載的方法,要選擇引數對應的方法
public void setContentView(int layoutResID) {
// 創建DecorView
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 這里根據布局id加載布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// 回呼activity的方法
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
同樣我們只看重點代碼:
- 首先看decorView創建了沒有,沒有的話創建DecorView
- 把布局加載到DecorView中
- 回呼Activity的callBack方法
這里補充一下什么是DecorView,DecorView是在PhoneWindow中預設好的一個布局,這個布局長這樣:
![]()
他是一個垂直排列的布局,上面是ActionBar,下面是ContentView,他是一個FrameLayout,我們的Activity布局就加載到ContentView里進行顯示,所以Decorview是Activity布局最頂層的viewGroup,
然后我們看一下怎么初始化DercorView的:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 這里創建了DecorView
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 對DecorView進行初始化,得到ContentView
mContentParent = generateLayout(mDecor);
...
}
}
installDecor方法中主要是新建一個DecorView物件,然后加載預設好的布局對DecorView進行初始化,(預設好的布局就是上面講述的布局)并獲取到這個預設布局的ContentView,好了然后我們再回到window的setContentView方法中,初始化了DecorView之后,把Activity布局加載到DecorView的ContentView中如下代碼:
// 注意他有多個多載的方法,要選擇引數對應的方法
public void setContentView(int layoutResID) {
...
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 這里根據布局id加載布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// 回呼activity的方法
cb.onContentChanged();
}
}
所以可以看到Activitiy的布局確實是添加到DecorView的ContentView中,這也是為什么onCreate中使用的是setContentView而不是setView,最后會回呼Activity的方法告訴Activity,DecorView已經創建并初始化完成了,
到這里DecorView創建完成了,但還缺少了最重要的一步:把DecorView作為window添加到螢屏上,從前面的介紹我們知道添加window需要用到WindowManagerImpl的addView方法,這一步是在ActivityThread的handleResumeActivity方法中被執行:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// 呼叫Activity的onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
// 讓decorView顯示到螢屏上
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
這一步方法有兩個重點:回呼onResume方法,把decorView添加到螢屏上,我們看一下makeVisible方法做了什么:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
是不是非常熟悉?直接呼叫WindowManagerImpl的addView方法來吧decorView添加到螢屏上,至此,我們的Activity界面就會顯示在螢屏上了,
好了,這部分很長,最后來總結一下:
- 從Activity的啟動流程可以得到Activity創建Window的程序
- 創建PhoneWindow -> 創建WindowManager -> 創建decorView -> 利用windowManager把DecorView顯示到螢屏上
- 回呼onResume方法的時候,DecorView還沒有被添加到螢屏,所以當onResume被回呼,指的是螢屏即將到顯示,而不是已經顯示
PopupWindow
popupWindow日常使用的也比較多,最常見的需求是彈一個選單出來等,popupWindow也是利用windowManager來往螢屏上添加window,但,popupWindow是依附于activity而存在的,當Activity未運行時,是無法彈出popupWindow的,通過原始碼可以知道,當呼叫onResume方法的時候,其實后續還有很多事情在做,這個時候Activity也是尚未完全啟動,所以popupWindow不能在onCreate、onStart、onResume方法中彈出,
彈出popupWindow的程序分為兩個:創建view;通過windowManager添加window,首先看到PopupWindow的構造方法:
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
mContext = contentView.getContext();
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
他有多個多載方法,但最終都會呼叫到這個有四個引數的方法,主要是前面的得到context和根據context獲得WindowManager,
然后我們看到他的顯示方法,顯示方法有兩個:showAtLocation和showAsDropDown,主要是處理顯示的位置不同,其他都是相似的,我們看到第一個方法:
public void showAtLocation(View parent, int gravity, int x, int y) {
mParentRootView = new WeakReference<>(parent.getRootView());
showAtLocation(parent.getWindowToken(), gravity, x, y);
}
邏輯很簡單,父view的根布局存盤了起來,然后呼叫另外的多載方法:
public void showAtLocation(IBinder token, int gravity, int x, int y) {
// 如果contentView是空直接回傳
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
detachFromAnchor();
mIsShowing = true;
mIsDropdown = false;
mGravity = gravity;
// 得到WindowManager.LayoutParams物件
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
// 做一些準備作業
preparePopup(p);
p.x = x;
p.y = y;
// 執行popupWindow顯示作業
invokePopup(p);
}
這個方法的邏輯主要有:
- 判斷contentView是否為慷訓者是否進行顯示
- 做一些準備作業
- 進行popupWindow顯示作業
這里我們看一下他的準備作業做了什么:
private void preparePopup(WindowManager.LayoutParams p) {
...
if (mBackground != null) {
mBackgroundView = createBackgroundView(mContentView);
mBackgroundView.setBackground(mBackground);
} else {
mBackgroundView = mContentView;
}
// 創建了DecorView
// 注意,這里的DecorView并不是我們之前講的DecorView,而是他的內部類:PopupDecorView
mDecorView = createDecorView(mBackgroundView);
mDecorView.setIsRootNamespace(true);
...
}
接下來再看他的顯示作業:
private void invokePopup(WindowManager.LayoutParams p) {
...
// 呼叫windowManager添加window
mWindowManager.addView(decorView, p);
...
}
到這里popupWindow就會被添加到螢屏上了,
最后總結一下:
- 根據引數構建popupDecorView
- 把popupDecorView添加到螢屏上
Dialog
dialog的創建程序Activity比較像:創建PhoneWindow,初始化DecorView,添加DecorView,我這里就簡單講解一下,首先看到他的構造方法:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
// 獲取windowManager
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 構造PhoneWindow
final Window w = new PhoneWindow(mContext);
mWindow = w;
// 初始化PhoneWindow
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setOnWindowSwipeDismissedCallback(() -> {
if (mCancelable) {
cancel();
}
});
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
這里和前面的Activity創建程序非常像,但是有個重點需要注意mWindowManager其實是Activity的WindowManager,這里的context一般是activity(實際上也只能是activity,非activity會拋出例外,相關內容讀者有興趣可以閱讀這篇文章window的token驗證),我們看到activity的getSystemService方法:
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
// 獲取activity的windowManager
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
可以看到這里的windowManager確實是Activity的WindowManager,接下來看到他的show方法:
public void show() {
...
// 回呼onStart方法,獲取前面初始化好的decorview
onStart();
mDecor = mWindow.getDecorView();
...
WindowManager.LayoutParams l = mWindow.getAttributes();
...
// 利用windowManager來添加window
mWindowManager.addView(mDecor, l);
...
mShowing = true;
sendShowMessage();
}
注意這里的mWindowManager是Activity的WindowManager,所以實際上,這里是添加到了Activity的PhoneWindow中,接下來的和前面的添加流程一樣,這里我也不多講解了,
總結一下:
- dialog和popupWindow不同,dialog創建了新的PhoneWindow,使用了PhoneWindow的DecorView模板,而popupWindow沒有
- dialog的顯示層級數更高,會直接顯示在Activity上面,在dialog后添加的popUpWindow也會顯示在dialog下
- dialog的創建流程和activity非常像
從Android架構角度看Window
前面我們介紹過關于PhoneWindow和window之間的關系,了解到PhoneWindow其實不是Window,只是一個window容器,不知讀者有沒想過一個問題,為什么谷歌要建一個不是window但卻名字是window的類?是故意要迷惑我們嗎?要了解這個問題,我們先來回顧一下整個android的window機制結構,
首先從WindowManagerService開始,我們知道WMS是window的最終管理者,在WMS中為每一個應用持有一個session,關于session前面我們講過,每個應用都是全域單例,負責和WMS通信的binder物件,WMS為每個window都建立了一個windowStatus物件,同一個應用的window使用同個session進行跨行程通信,結構大概如下:
而負責與WMS通信的,是viewRootImpl,前面我們講過每個view樹即為一個window,viewRootImpl負責和WMS進行通信,同時也負責view的繪制,如果把上面的圖畫仔細一點就是:
圖中每一個windowStatus對應一個viewRootImpl,WMS通過viewRootImpl來控制view,這也就是window機制的管理結構,當我們需要添加window的時候,最終的邏輯實作是WindowManagerGlobal,他的內部使用自己的session創建一個viewRootImpl,然后向WMS申請添加window,結構圖大概如下:
windowManagerGlobal使用自己的IWindowSession創建viewRootImpl,這個IWindowSession是全域單例,viewRootImpl和WMS申請創建window,然后WMS允許之后,再通知viewRootImpl繪制view,同時WMS通過windowStatus存盤了viewRootImpl的相關資訊,這樣如果WMS需要修改view,直接通過viewRootImpl就可以修改view了,
從上面的描述中可以發現我全程沒有提及到PhoneWindow和WindowManagerImpl,這是因為他們不屬于window機制內的類,而是封裝于window機制之上的框架,假設如果沒有PhoneWindow和WindowManager我們該如何添加一個window?首先需要呼叫WindowGlobal獲取session,再創建viewRootImpl,再訪問wms,然后再利用viewRootImpl繪制view,是不是很復雜,而這僅僅只是整體的步驟,而WindowManagerImpl正是這個功能,他內部擁有WindowManagerGlobal的單例,然后幫助我們完成了這一系列的步驟,同時,windowManagerImpl也是只有一個實體,其他的windowManagerImpl都是建立在windowManagerImpl單例上,這一點在前面有通過原始碼介紹到,
另外,上面我講到PhoneWindow并不是window而是一個輔助Activity管理的工具類,那為什么他不要命名為windowUtils呢?首先,PhoneWindow這個類是谷歌給window機制進行更上一層的封裝,PhoneWindow內部擁有一個DecorView,我們的布局view都是添加到decorView中的,因為我們可以通過給decorView設定背景,寬高度,標題欄,按鍵反饋等等,來間接給我們的布局view設定,這樣一來,PhoneWindow的存在,向開發者屏蔽真正的window,暴露給開發者一個“存在的”window,我們可以認為PhoneWindow就是一個window,window是view容器,當我們需要在螢屏上添加view的時候,只需要獲得應用window對應的windowManagerImpl,然后直接呼叫addView方法添加view即可,這里也可以解釋為什么windowManager的介面方法是addView而不是addWindow,一是window確實是以view的存在形式沒錯,二是為了向開發者屏蔽真正的window,讓我們以為是在往window中添加view,window是真實存在的東西,他們的關系畫個圖如下:
黃色部分輸于谷歌提供給開發者的window框架,而綠色是真正的window機制結構,通過PhoneWindow我們可以很方便地進行window操作,而不須了解底層究竟是如何作業的,PhoneWindow的存在,更是讓window的“可見性”得到了實作,讓window變成了一個“view容器”,
好了最后來總結一下:
- Android內部的window機制與谷歌暴露給我們的api是不一樣的,谷歌封裝的目的是為了讓我們更好地使用window,
- dialog、popupWindow等框架更是對具體場景進行更進一步的封裝,
- 我們在了解window機制的時候,需要跳過應用層,看到window的本質,才能更好地幫助我們理解window,
- 在android的其他地方也是一樣,利用封裝向開發者屏蔽底層邏輯,讓我們更好地運用,但如果我們需要了解他的機制的時候,就需要繞過這層封裝,看到本質,
總結
全文到這里,就基本結束了,下面先總結一下我這篇文章說了什么:
- 詳述了什么是window
- 對window的各種引數進行講解
- 講解window機制內的關鍵類
- 從原始碼講解window的添加流程以及各大組件的window添加流程
- 詳解了PhoneWindow與window的關系,談了關于谷歌的封裝思想
文中最重要的一點就是認識window的本質,區分好window和view之間的關系以及window與PhoneWindow的關系,
筆者在寫這篇文章的時候,對于各節的安排是比較猶豫的:如果先講概念,沒有原始碼流程的講解很難懂;先講原始碼流程,沒有概念的認知很難讀懂原始碼,最侄訓是決定了先講window的真正概念,先讓讀者有個整體上的感知,
文章很長,筆者對于window想要講的都在這篇文章中,
希望文章對你有幫助,
全文到此,感謝你的閱讀
原創不易,覺得有幫助可以點贊收藏評論轉發關注,
筆者才疏學淺,有任何錯誤歡迎評論區或私信交流,
如需轉載請私信或評論區交流,另外歡迎光臨筆者的個人博客:傳送門
參考文獻
- 直面底層:探索 Android 中的 Window
- 直面底層:WindowManager 視圖系結以及體系結構
- 奇妙的Window之旅
- Android進階必備,Window機制探索
《Android開發藝術探索》
《Android進階解密》
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/234196.html
標籤:Android
