結(jié)合Windows窗口深入分析Android窗口的實(shí)現(xiàn)
前言
從windows窗口的概念開始,通過對(duì)比去理解Android窗口體系,本文沒有深入源碼,重在理解概念
代碼都是抄來抄去,概念也是互相借鑒 ??,先看看Windows窗口的一些概念。
概念如下:
是我們使用軟件時(shí)看到的界面,包含各種各樣的控件,與用戶交互
窗口有三種類型: 系統(tǒng)窗口類,應(yīng)用程序全局窗口,應(yīng)用程序局部窗口
窗口具有Z軸層級(jí)
可以設(shè)置窗口的大小與位置
平時(shí)打開文件夾,微信,網(wǎng)易云,AndroidStudio都是打開一個(gè)窗口。
了解Android窗口的同志們可能會(huì)感覺到Windows窗口的概念好熟悉呀。
窗口這個(gè)概念在Android中并不清晰,手機(jī)移動(dòng)終端屏幕太小。很少有機(jī)會(huì)和Windows一樣一塊屏幕同時(shí)展示多個(gè)窗口。
操作也不一樣,在Windows中可以隨意對(duì) 不同窗口調(diào)整,最大化最小化,拖拽,改變尺寸,切換窗口巴拉巴拉的一頓操作。
所以Windos天生就更容易理解窗口,畢竟能夠直接操作。打開微信和網(wǎng)易云一眼就能看出 這是兩個(gè)窗口。
Windows先放一哈,視線會(huì)到Android,看一個(gè)情景,包含:Activity,Dialog,Toast。 問:圖里有幾個(gè)窗口
答:三個(gè),Activity應(yīng)用窗口,Dialog子窗口,Toast系統(tǒng)窗口。
如果沒看過源碼的同志肯定會(huì)猶豫,說到底還是移動(dòng)端手機(jī)屏幕太小了,沒辦法給人直觀感性的認(rèn)識(shí)窗口。
通過對(duì)比還是看出窗口的一些特性:
具有層級(jí)概念,在上述場景中Activity層級(jí)最低,Dialog次之,Toast最高
窗口可以設(shè)置位置大小,屏幕中的一塊區(qū)域展示內(nèi)容。只不過Activity是全屏的,不像Windows可以最大化最小化, 用戶手動(dòng)改變窗口大小,弱化了窗口的概念。
對(duì)于Window的認(rèn)識(shí)階段
第一階段
剛剛學(xué)習(xí)Android的時(shí)候,都聽說過一個(gè)概念,Activity代表一個(gè)界面。實(shí)際開發(fā)起來也是如此,在Activity中加載xml文件,綁定數(shù)據(jù)與view。
Activity == UI
第二階段
看了幾篇文章,接觸了Window,WMS 概念 。發(fā)現(xiàn)Activity并不是UI界面,Activity內(nèi)部持有Window對(duì)象,Window的實(shí)現(xiàn)類PhoneWindow內(nèi)部持有DecorView作為根布局,開發(fā)人員編寫的xml 會(huì)添加到DecorView中。 哦!結(jié)合對(duì)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è)計(jì)一個(gè)類的意義可以從它的屬性以及暴露的方法來推測。
如下是從:PhoneWindow中摘取的一些通過名字可以大概推測出作用的屬性
大部分都是關(guān)于資源的設(shè)置:狀態(tài)欄,導(dǎo)航欄,是否透明,轉(zhuǎn)場動(dòng)畫,應(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.
頂級(jí)窗口外觀和行為策略的抽象基類。此類的實(shí)例應(yīng)用作添加到窗口管理器的頂級(jí)視圖。它提供標(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
才對(duì)么? 難道view才是window?(下面代碼會(huì)報(bào)錯(cuò) 添加系統(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
是個(gè)接口,實(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
第一個(gè)參數(shù) mWindow
代表真正的window。
mWindow的實(shí)現(xiàn)類W,類型是 IWindow.Stub ,Binder對(duì)象 對(duì)其他進(jìn)程暴露方法。
W類 持有ViewRootImpl ,公開的接口方法內(nèi)部調(diào)用ViewRootImpl 類。
所以 IWindowSession
是把一個(gè)Binder對(duì)象傳遞給WMS,WMS通過進(jìn)程間通信操作ViewRootImpl ,ViewRootImpl 操作View
ViewRootImpl 操作的View
對(duì)應(yīng)到當(dāng)前場景是 windowManager.addView() 添加的View
對(duì)應(yīng)到Activity則是PhoneWindow中DecorView
經(jīng)過這一頓分析 好像沒有確定Window的實(shí)體對(duì)象 難以捉摸。它不像一個(gè)User類,Person類那樣明晃晃的放在開發(fā)者面前。原本以為 傳遞給WMS肯定會(huì)是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是一個(gè)抽象的概念,對(duì)應(yīng)手機(jī)屏幕的一塊區(qū)域,實(shí)際是view。
View成了Window??? 什么場景下可以把View叫做Window呢?
想象一個(gè)場景:一個(gè)Activity內(nèi)有DialogA,DialogB
這個(gè)場景會(huì)創(chuàng)建三個(gè)Window,Activity一個(gè),Dialog兩個(gè),對(duì)應(yīng)三個(gè)xml布局。是三個(gè)抽象的Window,對(duì)應(yīng)三個(gè)具體的View,應(yīng)該叫做View樹
它們彼此之間互不影響,為DialogA添加View,不會(huì)影響到Activity和DialogB。因?yàn)樗鼈儗儆诓煌腤indow。
這也應(yīng)該是添加Window的Api 叫做 addView()
而不是 addWidnow()
的原因。
根本就沒有具體的Window,只有具體的View,Window是抽象的。
理解了什么是Window之后,在簡單說一下添加window的Api。
View表示需要在屏幕展示的內(nèi)容
layoutParams 則是對(duì)內(nèi)容進(jìn)行約束,基本的寬高,位置。
layoutParams.type 設(shè)置window類型,其實(shí)是彈窗的顯示層級(jí)。
應(yīng)用window:1 ~ 99
子window:1000 ~ 1999
系統(tǒng)window:2000~ 2999
數(shù)值越大層級(jí)越高,層級(jí)高覆蓋層級(jí)低的,一般通過常量設(shè)置,系統(tǒng)window需要申請(qǐng)權(quán)限
layoutParams.flags 設(shè)置Window不同場景下的邏輯,比如:
// 全屏顯示,隱藏所有的 Window 裝飾,比如在游戲、播放器中的全屏顯示 public static final int FLAG_FULLSCREEN = 0x00000400;
// 表示比FLAG_FULLSCREEN低一級(jí),會(huì)顯示狀態(tài)欄 public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
// 當(dāng)用戶的臉貼近屏幕時(shí)(比如打電話),不會(huì)去響應(yīng)此事件 public static final int FLAG_IGNORE_CHEEK_PRESSES = 0x00008000;
// 全屏顯示,隱藏所有的 Window 裝飾,比如在游戲、播放器中的全屏顯示 public static final int FLAG_FULLSCREEN = 0x00000400;
// 表示比FLAG_FULLSCREEN低一級(jí),會(huì)顯示狀態(tài)欄 public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
// 當(dāng)用戶的臉貼近屏幕時(shí)(比如打電話),不會(huì)去響應(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是對(duì)上述內(nèi)容的一個(gè)模板實(shí)現(xiàn)。
軟件設(shè)計(jì)中很重要的一點(diǎn)就是找到業(yè)務(wù)當(dāng)中的 “變與不變”。 在Window體系中,一個(gè)頁面通用不變的部分交給PhoneWindow實(shí)現(xiàn)。變化的部分就是View,讓開發(fā)人員能夠自由定制。
PhoneWindow的存在也是幫Activity減輕負(fù)擔(dān),指責(zé)單一是一個(gè)好理解并且非常有效的原則。Activity已經(jīng)非常復(fù)雜了, 設(shè)計(jì)出PhoneWindow把UI相關(guān)的代碼從Activity中剝離出去。
由了上述層層抽象封裝才有了最初學(xué)習(xí)Android時(shí)的概念,Activity == 頁面。
到此這篇關(guān)于結(jié)合Windows窗口深入分析Android窗口的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Android窗口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實(shí)現(xiàn)開機(jī)自動(dòng)啟動(dòng)Service或app的方法
這篇文章主要介紹了Android實(shí)現(xiàn)開機(jī)自動(dòng)啟動(dòng)Service或app的方法,結(jié)合實(shí)例形式分析了Android開機(jī)自啟動(dòng)程序的具體步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-07-07Android Flutter制作交錯(cuò)動(dòng)畫的示例代碼
這篇文章我們將用Flutter實(shí)現(xiàn)一個(gè)交錯(cuò)動(dòng)畫的應(yīng)用實(shí)例,我們讓輪子在草地滾動(dòng)著前進(jìn),而且還能粘上“綠色的草”,感興趣的可以動(dòng)手嘗試一下2022-06-06新浪微博第三方登錄界面上下拉伸圖片之第三方開源PullToZoomListViewEx(二)
這篇文章主要介紹了新浪微博第三方登錄界面上下拉伸圖片之第三方開源PullToZoomListViewEx(二) 的相關(guān)資料,需要的朋友可以參考下2015-12-12Android開發(fā)中button按鈕的使用及動(dòng)態(tài)添加組件方法示例
這篇文章主要介紹了Android開發(fā)中button按鈕的使用及動(dòng)態(tài)添加組件方法,涉及Android針對(duì)button按鈕的事件響應(yīng)及TextView動(dòng)態(tài)添加相關(guān)操作技巧,需要的朋友可以參考下2017-11-11Android開發(fā)Kotlin語言協(xié)程的依賴及使用示例
這篇文章主要為大家介紹了Android開發(fā)Kotlin語言協(xié)程的依賴及使用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08