結(jié)合Windows窗口深入分析Android窗口的實(shí)現(xiàn)
前言
從windows窗口的概念開始,通過對比去理解Android窗口體系,本文沒有深入源碼,重在理解概念
代碼都是抄來抄去,概念也是互相借鑒 ??,先看看Windows窗口的一些概念。
概念如下:
是我們使用軟件時看到的界面,包含各種各樣的控件,與用戶交互
窗口有三種類型: 系統(tǒng)窗口類,應(yīng)用程序全局窗口,應(yīng)用程序局部窗口
窗口具有Z軸層級
可以設(shè)置窗口的大小與位置
平時打開文件夾,微信,網(wǎng)易云,AndroidStudio都是打開一個窗口。
了解Android窗口的同志們可能會感覺到Windows窗口的概念好熟悉呀。
窗口這個概念在Android中并不清晰,手機(jī)移動終端屏幕太小。很少有機(jī)會和Windows一樣一塊屏幕同時展示多個窗口。
操作也不一樣,在Windows中可以隨意對 不同窗口調(diào)整,最大化最小化,拖拽,改變尺寸,切換窗口巴拉巴拉的一頓操作。
所以Windos天生就更容易理解窗口,畢竟能夠直接操作。打開微信和網(wǎng)易云一眼就能看出 這是兩個窗口。
Windows先放一哈,視線會到Android,看一個情景,包含:Activity,Dialog,Toast。 問:圖里有幾個窗口
答:三個,Activity應(yīng)用窗口,Dialog子窗口,Toast系統(tǒng)窗口。
如果沒看過源碼的同志肯定會猶豫,說到底還是移動端手機(jī)屏幕太小了,沒辦法給人直觀感性的認(rèn)識窗口。

通過對比還是看出窗口的一些特性:
具有層級概念,在上述場景中Activity層級最低,Dialog次之,Toast最高
窗口可以設(shè)置位置大小,屏幕中的一塊區(qū)域展示內(nèi)容。只不過Activity是全屏的,不像Windows可以最大化最小化, 用戶手動改變窗口大小,弱化了窗口的概念。
對于Window的認(rèn)識階段
第一階段
剛剛學(xué)習(xí)Android的時候,都聽說過一個概念,Activity代表一個界面。實(shí)際開發(fā)起來也是如此,在Activity中加載xml文件,綁定數(shù)據(jù)與view。
Activity == UI
第二階段
看了幾篇文章,接觸了Window,WMS 概念 。發(fā)現(xiàn)Activity并不是UI界面,Activity內(nèi)部持有Window對象,Window的實(shí)現(xiàn)類PhoneWindow內(nèi)部持有DecorView作為根布局,開發(fā)人員編寫的xml 會添加到DecorView中。 哦!結(jié)合對WMS粗淺的理解,WMS是窗口管理服務(wù),window不就是窗口么,可能window在創(chuàng)建完成后最終傳遞給WMS管理。 Window == UI
第三階段
之后深入到源碼中發(fā)現(xiàn)Window雖然翻譯過來是窗口,但實(shí)際上并不是真正的窗口。
理由有二:Window并沒有與WMS交互,Window沒有view管理之類的功能。
首先可以確定的是wms是系統(tǒng)窗口服務(wù),所有窗口都要與wms打交道。如果window代表的窗口,那么它或者它的唯一子類PhoneWindow,必然存在Binder機(jī)制與wms交互,然而并沒有。
既然Window沒有與wms交互,那它做了什么工作呢?
在面向?qū)ο笾?,設(shè)計一個類的意義可以從它的屬性以及暴露的方法來推測。
如下是從:PhoneWindow中摘取的一些通過名字可以大概推測出作用的屬性
大部分都是關(guān)于資源的設(shè)置:狀態(tài)欄,導(dǎo)航欄,是否透明,轉(zhuǎn)場動畫,應(yīng)用主題等等
private DecorView mDecor; private TextView mTitleView; int mStatusBarColor = 0; int mNavigationBarColor = 0; private int mTitleColor = 0; private CharSequence mTitle = null; boolean mIsFloating; private boolean mIsTranslucent; private LayoutInflater mLayoutInflater; private Transition mEnterTransition = null; private Transition mReturnTransition = USE_DEFAULT_TRANSITION; private int mTheme = -1; private boolean mIsStartingWindow;
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.
頂級窗口外觀和行為策略的抽象基類。此類的實(shí)例應(yīng)用作添加到窗口管理器的頂級視圖。它提供標(biāo)準(zhǔn)的UI策略,例如背景、標(biāo)題區(qū)域、默認(rèn)鍵處理等。
結(jié)合window類注釋可以做出結(jié)論,Window也是一層封裝,提供通用頁面模板,并不是真正的window。
尋找真正的Window
上面討論了Window類 并不是真正的Window,只是一層封裝。系統(tǒng)提供了WindowManager 允許開發(fā)人員添加Window。
如下代碼是在Activity獲取windowManager 添加Window。為什么api是addView 。不應(yīng)該是addWindow 才對么? 難道view才是window?(下面代碼會報錯 添加系統(tǒng)window需要權(quán)限)
val wm:WindowManager =windowManager
val layoutParams = WindowManager.LayoutParams()
layoutParams.run{
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
format = PixelFormat.TRANSLUCENT
gravity = Gravity.STARTor Gravity.TOP
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
type =
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
else WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
}
val view :View = LayoutInflater.from(this).inflate(R.layout.xxx,null)
wm.addView(view, layoutParams)跟蹤源碼
WindowManager 是個接口,實(shí)現(xiàn)類為 WindowManagerImpl
WindowManagerImpl 內(nèi)部把邏輯轉(zhuǎn)發(fā)給WindowManagerGlobal
WindowManagerGlobal 調(diào)用 ViewRootImpl
ViewRootImpl 通過WindowSession 與 wms 完成進(jìn)程間通信
具體方法調(diào)用流程
WindowManager.addView() —WindowManagerImpl.addView() —WindowManagerGlobal.addView() — ViewRootImpl.setView() — WindowSession.addToDisplayAsUser()
ViewRootImpl類核心邏輯如下:
WindowManager.addView() 在應(yīng)用層最終調(diào)用 ViewRootImpl.setView()
添加的View通過 WindowSession 進(jìn)入 wms,方法 IWindowSession.addToDisplay 第一個參數(shù) mWindow 代表真正的window。
mWindow的實(shí)現(xiàn)類W,類型是 IWindow.Stub ,Binder對象 對其他進(jìn)程暴露方法。
W類 持有ViewRootImpl ,公開的接口方法內(nèi)部調(diào)用ViewRootImpl 類。
所以 IWindowSession 是把一個Binder對象傳遞給WMS,WMS通過進(jìn)程間通信操作ViewRootImpl ,ViewRootImpl 操作View
ViewRootImpl 操作的View
對應(yīng)到當(dāng)前場景是 windowManager.addView() 添加的View
對應(yīng)到Activity則是PhoneWindow中DecorView
經(jīng)過這一頓分析 好像沒有確定Window的實(shí)體對象 難以捉摸。它不像一個User類,Person類那樣明晃晃的放在開發(fā)者面前。原本以為 傳遞給WMS肯定會是Window了,結(jié)果是Binder,WMS進(jìn)程間通信最終操控的是View。
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
final W mWindow;
View mView;
final IWindowSession mWindowSession;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
mView = view;
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
}
static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
mWindowSession = viewAncestor.mWindowSession;
}
....
@Override
public void hideInsets(@InsetsType int types, boolean fromIme) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.hideInsets(types, fromIme);
}
}
@Override
public void moved(int newX, int newY) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
viewAncestor.dispatchMoved(newX, newY);
}
}
....省略其他方法
}
}Window到底是什么
window是一個抽象的概念,對應(yīng)手機(jī)屏幕的一塊區(qū)域,實(shí)際是view。
View成了Window??? 什么場景下可以把View叫做Window呢?
想象一個場景:一個Activity內(nèi)有DialogA,DialogB
這個場景會創(chuàng)建三個Window,Activity一個,Dialog兩個,對應(yīng)三個xml布局。是三個抽象的Window,對應(yīng)三個具體的View,應(yīng)該叫做View樹
它們彼此之間互不影響,為DialogA添加View,不會影響到Activity和DialogB。因?yàn)樗鼈儗儆诓煌腤indow。
這也應(yīng)該是添加Window的Api 叫做 addView() 而不是 addWidnow() 的原因。
根本就沒有具體的Window,只有具體的View,Window是抽象的。
理解了什么是Window之后,在簡單說一下添加window的Api。
View表示需要在屏幕展示的內(nèi)容
layoutParams 則是對內(nèi)容進(jìn)行約束,基本的寬高,位置。
layoutParams.type 設(shè)置window類型,其實(shí)是彈窗的顯示層級。
應(yīng)用window:1 ~ 99
子window:1000 ~ 1999
系統(tǒng)window:2000~ 2999
數(shù)值越大層級越高,層級高覆蓋層級低的,一般通過常量設(shè)置,系統(tǒng)window需要申請權(quán)限
layoutParams.flags 設(shè)置Window不同場景下的邏輯,比如:
// 全屏顯示,隱藏所有的 Window 裝飾,比如在游戲、播放器中的全屏顯示 public static final int FLAG_FULLSCREEN = 0x00000400;
// 表示比FLAG_FULLSCREEN低一級,會顯示狀態(tài)欄 public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
// 當(dāng)用戶的臉貼近屏幕時(比如打電話),不會去響應(yīng)此事件 public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
// 全屏顯示,隱藏所有的 Window 裝飾,比如在游戲、播放器中的全屏顯示 public static final int FLAG_FULLSCREEN = 0x00000400;
// 表示比FLAG_FULLSCREEN低一級,會顯示狀態(tài)欄 public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
// 當(dāng)用戶的臉貼近屏幕時(比如打電話),不會去響應(yīng)此事件 public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
val wm:WindowManager =windowManager
val layoutParams = WindowManager.LayoutParams()
layoutParams.run{
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
format = PixelFormat.TRANSLUCENT
gravity = Gravity.STARTor Gravity.TOP
x = 0
y = 0
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
type =
if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
else WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG
}
val view :View = LayoutInflater.from(this).inflate(R.layout.xxx,null)
wm.addView(view, layoutParams)Activity-Window-View的關(guān)系
Activity是一層封裝,屏蔽復(fù)雜的系統(tǒng)實(shí)現(xiàn)細(xì)節(jié),抽象出UI生命周期,方便開發(fā)人員工作,專注于界面樣式的編寫
Window,指PhoneWindow,頁面通用模板,所有的Window都需要主題,狀態(tài)欄,導(dǎo)航欄,背景等等設(shè)置。PhoneWindow是對上述內(nèi)容的一個模板實(shí)現(xiàn)。
軟件設(shè)計中很重要的一點(diǎn)就是找到業(yè)務(wù)當(dāng)中的 “變與不變”。 在Window體系中,一個頁面通用不變的部分交給PhoneWindow實(shí)現(xiàn)。變化的部分就是View,讓開發(fā)人員能夠自由定制。
PhoneWindow的存在也是幫Activity減輕負(fù)擔(dān),指責(zé)單一是一個好理解并且非常有效的原則。Activity已經(jīng)非常復(fù)雜了, 設(shè)計出PhoneWindow把UI相關(guān)的代碼從Activity中剝離出去。
由了上述層層抽象封裝才有了最初學(xué)習(xí)Android時的概念,Activity == 頁面。
到此這篇關(guān)于結(jié)合Windows窗口深入分析Android窗口的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Android窗口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android RadioGroup多行顯示效果 解決單選問題
這篇文章主要為大家詳細(xì)介紹了Android RadioGroup多行顯示效果,解決單選問題,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11
android實(shí)現(xiàn)拍照或從相冊選取圖片
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)拍照或從相冊選取圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-03-03
在Android中使用Anntation來代替ENUM的方法
本篇文章主要介紹了在Android中使用Anntation來代替ENUM的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02
Android Canvas和Bitmap結(jié)合繪圖詳解流程
在 Android Canvas 上繪圖非常難,在繪圖時需要理解許多不同的類和概念。這篇文章中,將介紹 Android 框架中可用的一些類,它們可以讓畫布使用時更輕松2021-11-11
Android studio 4.1打包失敗和插件錯誤提示的解決
這篇文章主要介紹了Android studio 4.1打包失敗和插件錯誤提示的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

