一、WindowManager
WindowManager 是一個接口,它繼承自 ViewManager,ViewManager 接口很簡單,只提供了三個在 Activity 中添加和移除子 View 的抽象方法 addView、updateView、removeView:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
WindowManager 接口繼承了 ViewManager,同時自己還定義了一個內(nèi)部類 LayoutParams,這個 LayoutParams 類是繼承自 ViewGroup 的內(nèi)部類 LayoutParams
WindowManager 的實(shí)現(xiàn)類是 WindowManagerImpl,實(shí)現(xiàn) WindowManager 中定義的接口功能。
而 ViewGroup 類也實(shí)現(xiàn)了 ViewManager 接口,因?yàn)?ViewGroup 要添加或者刪除子 View,ViewGroup 層層嵌套,最頂層的是 DecorView,最終顯示在 Window 中,Window 是 View 的實(shí)際管理者。
二、Window
我們都知道 Window 是一個抽象類,唯一實(shí)現(xiàn)類是 PhoneWindow。創(chuàng)建一個 Window 很簡單,只需要通過 WindowManager 即可完成,WindowManager 是外界訪問 Window 的入口,Window 具體實(shí)現(xiàn)在 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一個 IPC 過程,這里不作深究。
怎么通過 WindowManager 添加一個 Window 呢?很簡單,使用 addView 方法
windowManager.addView(view,layoutParams);
1、WindowManager.LayoutParams
Window 的一些屬性都是通過 WindowManager.LayoutParams 來定義的。
Window 類型
Window 是分層的,層級大的會覆蓋在層級小的上面。
Window 有三種類型,
- 應(yīng)用 Window。一個應(yīng)用 Window 對應(yīng)著 Activity,層級范圍1-99;
- 子 Window。不能單獨(dú)存在,需要依附在特定的父 Window 之中,比如一些 Dialog,層級范圍 1000-1999;
- 系統(tǒng) Window。需要聲明權(quán)限才能創(chuàng)建,比如 Toast 和系統(tǒng)狀態(tài)欄,層級范圍2000-2999;
Window 的層級范圍對應(yīng)著 WindowManager.LayoutParams 的 type 參數(shù),WindowManager.LayoutParams 內(nèi)部定義了一些靜態(tài)常量值。需要注意的是,如果定義系統(tǒng) Window,別忘記聲明權(quán)限。
//以下定義都是描述窗口的類型
public int type;
//第一個應(yīng)用窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
//所有程序窗口的base窗口,其他應(yīng)用程序窗口都顯示在它上面
public static final int TYPE_BASE_APPLICATION = 1;
//所有Activity的窗口
public static final int TYPE_APPLICATION = 2;
//目標(biāo)應(yīng)用窗口未啟動之前的那個窗口
public static final int TYPE_APPLICATION_STARTING = 3;
//最后一個應(yīng)用窗口
public static final int LAST_APPLICATION_WINDOW = 99;
//第一個子窗口
public static final int FIRST_SUB_WINDOW = 1000;
// 面板窗口,顯示于宿主窗口的上層
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
// 媒體窗口(例如視頻),顯示于宿主窗口下層
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1;
// 應(yīng)用程序窗口的子面板,顯示于所有面板窗口的上層
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
//對話框窗口
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
//
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4;
//最后一個子窗口
public static final int LAST_SUB_WINDOW = 1999;
//系統(tǒng)窗口,非應(yīng)用程序創(chuàng)建
public static final int FIRST_SYSTEM_WINDOW = 2000;
//狀態(tài)欄,只能有一個狀態(tài)欄,位于屏幕頂端,其他窗口都位于它下方
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//搜索欄,只能有一個搜索欄,位于屏幕上方
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
//電話窗口,它用于電話交互(特別是呼入),置于所有應(yīng)用程序之上,狀態(tài)欄之下
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
//系統(tǒng)警告提示窗口,出現(xiàn)在應(yīng)用程序窗口之上
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
//鎖屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
//信息窗口,用于顯示Toast
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
//系統(tǒng)頂層窗口,顯示在其他一切內(nèi)容之上,此窗口不能獲得輸入焦點(diǎn),否則影響鎖屏
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
//電話優(yōu)先,當(dāng)鎖屏?xí)r顯示,此窗口不能獲得輸入焦點(diǎn),否則影響鎖屏
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
//系統(tǒng)對話框窗口
public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
//鎖屏?xí)r顯示的對話框
public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
//系統(tǒng)內(nèi)部錯誤提示,顯示在任何窗口之上
public static final int TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10;
//內(nèi)部輸入法窗口,顯示于普通UI之上,應(yīng)用程序可重新布局以免被此窗口覆蓋
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
//內(nèi)部輸入法對話框,顯示于當(dāng)前輸入法窗口之上
public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
//墻紙窗口
public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
//狀態(tài)欄的滑動面板
public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
//安全系統(tǒng)覆蓋窗口,這些窗戶必須不帶輸入焦點(diǎn),否則會干擾鍵盤
public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
//最后一個系統(tǒng)窗口
public static final int LAST_SYSTEM_WINDOW = 2999;
Window 顯示特性
WindowManager.LayoutParams 的 flags 屬性定義了 Window 的顯示特性
//窗口特征標(biāo)記
public int flags;
//當(dāng)該window對用戶可見的時候,允許鎖屏
public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
//窗口后面的所有內(nèi)容都變暗
public static final int FLAG_DIM_BEHIND = 0x00000002;
//Flag:窗口后面的所有內(nèi)容都變模糊
public static final int FLAG_BLUR_BEHIND = 0x00000004;
//窗口不能獲得焦點(diǎn)
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
//窗口不接受觸摸屏事件
public static final int FLAG_NOT_TOUCHABLE = 0x00000010;
//即使在該window在可獲得焦點(diǎn)情況下,允許該窗口之外的點(diǎn)擊事件傳遞到當(dāng)前窗口后面的的窗口去
public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020;
//當(dāng)手機(jī)處于睡眠狀態(tài)時,如果屏幕被按下,那么該window將第一個收到觸摸事件
public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
//當(dāng)該window對用戶可見時,屏幕出于常亮狀態(tài)
public static final int FLAG_KEEP_SCREEN_ON = 0x00000080;
//:讓window占滿整個手機(jī)屏幕,不留任何邊界
public static final int FLAG_LAYOUT_IN_SCREEN = 0x00000100;
//允許窗口超出整個手機(jī)屏幕
public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200;
//window全屏顯示
public static final int FLAG_FULLSCREEN = 0x00000400;
//恢復(fù)window非全屏顯示
public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800;
//開啟窗口抖動
public static final int FLAG_DITHER = 0x00001000;
//安全內(nèi)容窗口,該窗口顯示時不允許截屏
public static final int FLAG_SECURE = 0x00002000;
//鎖屏?xí)r顯示該窗口
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
//系統(tǒng)的墻紙顯示在該窗口之后
public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
//當(dāng)window被顯示的時候,系統(tǒng)將把它當(dāng)做一個用戶活動事件,以點(diǎn)亮手機(jī)屏幕
public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
//該窗口顯示,消失鍵盤
public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
//當(dāng)該window在可以接受觸摸屏情況下,讓因在該window之外,而發(fā)送到后面的window的觸摸屏可以支持split touch
public static final int FLAG_SPLIT_TOUCH = 0x00800000;
//對該window進(jìn)行硬件加速,該flag必須在Activity或Dialog的Content View之前進(jìn)行設(shè)置
public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
//讓window占滿整個手機(jī)屏幕,不留任何邊界
public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
//透明狀態(tài)欄
public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
//透明導(dǎo)航欄
public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
除了 type 和 flags 外,WindowManager.LayoutParams 還有一些屬性,這里說幾個比較重要的:
//窗口的起點(diǎn)坐標(biāo)
public int x;
public int y;
//窗口內(nèi)容的對齊方式
public int gravity;
//描述窗口的寬度和高度,是父類 ViewGroup.LayoutParams 的成員變量
public int width;
public int height;
2、Window 內(nèi)部機(jī)制
Window 是一個抽象概念,并不是實(shí)際存在的,而是以 View 的形式存在的,這從 WindowManager 的定義可以看出來,addView,updateView,removeView 都是針對 View 的,View 才是 Window 的實(shí)體,在實(shí)際使用中必須通過 WindowManager 才能訪問 Window。
每個 Window 都對應(yīng)著一個 View 和 ViewRootImpl,Window 和 View 是通過 ViewRootImpl 聯(lián)系起來的。
Window 添加過程
Window 的添加、更新和刪除需要 WindowManager 的 addView、updateView、removeView 方法,WindowManager 是接口,真正的實(shí)現(xiàn)是 WindowManagerImpl,但是其實(shí) WindowManagerImpl 也沒有直接實(shí)現(xiàn) WindowManager 的三大操作,而是交給 WindowManagerGlobal 來處理。WindowManagerImpl 這種工作模式是典型的橋接模式
WindowManagerImpl 的 addView 方法主要分如下幾步:
1)檢查 view、diaplay、params 等參數(shù)是否合法,如果是子 Window(parentWindow != null) 還需要調(diào)整一些布局參數(shù),LayoutParams 必須是 WindowManager.LayoutParams
2)創(chuàng)建 ViewRootImpl 并將 View 加入 List
WindowManagerGlobal 有幾個 ArrayList 比較重要:
//存儲所有 Window 對應(yīng)的 View
private final ArrayList<View> mViews = new ArrayList<View>();
//存儲所有 Window 對應(yīng)的 ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//存儲所有 Window 的布局參數(shù) LayoutParams
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
//存儲正在被刪除的 View 對象,已經(jīng)調(diào)用 removeView 還未執(zhí)行
private final ArrayList<> mDyingViews = new ArrayList<>();
在 addView 中如下方式把一系列對象添加到 List
root = newViewRootImpl(view.getContext,display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparam);
3)通過 ViewRootImpl 來更新界面并完成 Window 的添加過程
這個步驟由 ViewRootImpl 的 setView 來完成,setView 內(nèi)部通過 requestLayout 完成異步刷新請求,requestLayout 內(nèi)部調(diào)用 scheduleTraversals 來進(jìn)行 View 的繪制。
接著通過 WindowSession 完成 Window 的添加過程,Session 內(nèi)部是通過 WindowManagerService 實(shí)現(xiàn) Window 的添加的。
Window 的刪除過程
WindowManagerGlobal 的 removeView 先通過 findViewLocked 來查找待刪除的 View 的索引,然后調(diào)用 removeViewLoacked 來做進(jìn)一步刪除,內(nèi)部是通過 ViewRootImpl 來完成刪除操作的
Window 更新過程
首先更新 View 的LayoutParams,接著更新 ViewRootImpl 中的 LayoutParams,這一步是通過 ViewRootImpl 的 setLayoutParams 來實(shí)現(xiàn)的,在 ViewRootImpl 中會通過 scheduleTraversals 來對 View 重新布局,并通過 WindowSession 來更新 Window 視圖。
三、Window 創(chuàng)建過程
Activity 窗口添加流程
前面的文章說過,Activity 的實(shí)例化是在 ActivityThread 的 performLaunchActivity 方法開始的,在創(chuàng)建好 ContextImpl 對象后調(diào)用了 Activity 的 attach 方法,把 ContextImpl 的實(shí)例賦值給 mBase 成員變量,除此之外,attach 方法里面還做了初始化 Activity 成員變量 mWindow 和 mWindowManager 的工作:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
......
//創(chuàng)建Window類型的mWindow對象,實(shí)際為PhoneWindow類實(shí)現(xiàn)了抽象Window類
mWindow = PolicyManager.makeNewWindow(this);
......
//通過抽象Window類的setWindowManager方法給Window類的成員變量WindowManager賦值實(shí)例化
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
......
//把抽象Window類相關(guān)的WindowManager對象拿出來關(guān)聯(lián)到Activity的WindowManager類型成員變量mWindowManager
mWindowManager = mWindow.getWindowManager();
......
}
- 通過 PolicyManager 的 makeNewWindow 方法創(chuàng)建一個 PhoneWindow 對象,賦值給 Activity 的成員變量mWindow;
- 通過 Window 類的 setWindowManager 方法給 Window 的 mWindowManager 成員變量賦值實(shí)例化;
- 把前面實(shí)例化的 Window 的成員變量 mWindowManager 賦值給 Activity 的成員變量 mWindowManager,使 Activity 和 WindowManager 關(guān)聯(lián)起來;
再看 Window 的 setWindowManager 方法,看WindowManager 的實(shí)例是怎么創(chuàng)建的:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
......
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//實(shí)例化Window類的WindowManager類型成員mWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
就是調(diào)用穿進(jìn)去的或者重新獲取的 WindowManagerImpl 對象的 createLocalWindowManager 方法
public final class WindowManagerImpl implements WindowManager {
......
private WindowManagerImpl(Display display, Window parentWindow) {
mDisplay = display;
mParentWindow = parentWindow;
}
......
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
......
}
而 createLocalWindowManager 方法很簡單,就是通過 WindowMangerImpl 兩個參數(shù)的構(gòu)造函數(shù) new 了一個 WindowManagerImpl 對象。
那在 setWindowManager 中傳進(jìn)去的第一個參數(shù) (WindowManager)context.getSystemService(Context.WINDOW_SERVICE) 是從哪來的呢?
在 Context 的實(shí)現(xiàn)類 ContextImpl 中有靜態(tài)代碼塊:
class ContextImpl extends Context {
......
//靜態(tài)代碼塊,類加載時執(zhí)行一次
static {
......
//這里有一堆類似的XXX_SERVICE的注冊
......
registerService(WINDOW_SERVICE, new ServiceFetcher() {
Display mDefaultDisplay;
public Object getService(ContextImpl ctx) {
//搞一個Display實(shí)例
Display display = ctx.mDisplay;
if (display == null) {
if (mDefaultDisplay == null) {
DisplayManager dm = (DisplayManager)ctx.getOuterContext().
getSystemService(Context.DISPLAY_SERVICE);
mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
}
display = mDefaultDisplay;
}
//返回一個WindowManagerImpl實(shí)例
return new WindowManagerImpl(display);
}});
......
}
//這就是你在外面調(diào)運(yùn)Context的getSystemService獲取到的WindowManagerImpl實(shí)例
@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
//上面static代碼塊創(chuàng)建WindowManagerImpl實(shí)例用到的方法
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
}
在靜態(tài)代碼塊中注冊了一堆服務(wù),其中就包括 WINDOW_SERVICE 服務(wù),返回了一個 WindowManagerImpl 實(shí)例。
這個 WindowManagerIMpl 與 Activity 中關(guān)聯(lián)的 WindowManagerImpl 實(shí)例的不同之處在于這個是通過一個參數(shù)的構(gòu)造方法創(chuàng)建的,其實(shí)也就是 parentWindow 是 null,而Activity 中 Window 的 WindowManager 成員在構(gòu)造實(shí)例化時傳入給 WindowManagerImpl 中 mParentWindow 成員的是當(dāng)前 Window 對象,;還要就是靜態(tài)代碼塊是只在初始時加載一次,所以這個 WindowManager 是全局單例的。
每一個 Activity 都會新創(chuàng)建一個 WindowManager 實(shí)例來顯示 Activity 的界面的,在 setContentView 觸發(fā) Activity 的 resume 狀態(tài)后會調(diào)用 makeVisible 方法,其中就是獲取 Activity 的 mWindowManager 成員 addView 的:
void makeVisible() {
if (!mWindowAdded) {
//也就是獲取Activity的mWindowManager
//這個mWindowManager是在Activity的attach中通過mWindow.getWindowManager()獲得
ViewManager wm = getWindowManager();
//調(diào)運(yùn)的實(shí)質(zhì)就是ViewManager接口的addView方法,傳入的是mDecorView
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
可以看到這里 addView 的 View 參數(shù)是 mDecor 也就是 Phone Window 的內(nèi)部類 DecorView。
Context的WindowManager對每個APP來說是一個全局單例的,而Activity的WindowManager是每個Activity都會新創(chuàng)建一個的(其實(shí)你從上面分析的兩個實(shí)例化WindowManagerImpl的構(gòu)造函數(shù)參數(shù)傳遞就可以看出來,Activity中Window的WindowManager成員在構(gòu)造實(shí)例化時傳入給WindowManagerImpl中mParentWindow成員的是當(dāng)前Window對象,而ContextImpl的static塊中單例實(shí)例化WindowManagerImpl時傳入給WindowManagerImpl中mParentWindow成員的是null值),使用 Activity 的 getSysytemService(WINDOW_SERVICE) 獲取的是 Local 的WindowManager。
Dialog 窗口添加顯示機(jī)制
從 Dialog 的構(gòu)造函數(shù)說起:
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
......
public Dialog(Context context) {
this(context, 0, true);
}
//構(gòu)造函數(shù)最終都調(diào)運(yùn)了這個默認(rèn)的構(gòu)造函數(shù)
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
//默認(rèn)構(gòu)造函數(shù)的createContextThemeWrapper為true
if (createContextThemeWrapper) {
//默認(rèn)構(gòu)造函數(shù)的theme為0
if (theme == 0) {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
outValue, true);
theme = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, theme);
} else {
mContext = context;
}
//mContext已經(jīng)從外部傳入的context對象獲得值(一般是個Activity)?。?!非常重要,先記?。。?!
//獲取WindowManager對象
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
//為Dialog創(chuàng)建新的Window
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
//Dialog能夠接受到按鍵事件的原因
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
//關(guān)聯(lián)WindowManager與新Window,特別注意第二個參數(shù)token為null,也就是說Dialog沒有自己的token
//一個Window屬于Dialog的話,那么該Window的mAppToken對象是null
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
......
}
Dialog 的 Window 也是通過 PolicyManager.makeNewWindow 方法創(chuàng)建的,但是 WindowManager 是 context.getSystemService 方法獲取的,也就是說沒有新建 WindowManager 的實(shí)例,這個 context 是通過構(gòu)造方法傳進(jìn)來的,一般是 Activity Context,所以 Dialog 的 WindowManager 其實(shí)是 Activity 的 mWindowManager,并通過 Window 類的 setWindowManager 方法與 Window 關(guān)聯(lián),Dialog 類也實(shí)現(xiàn)了 Window.Callback,Window.OnWindowDismissedCallback并給 Window.setCallback,所以 Dialog 能夠接受到點(diǎn)擊等事件。
至此Dialog的創(chuàng)建過程Window處理已經(jīng)完畢,接下來我們繼續(xù)看看Dialog的show與cancel方法:
public void show() {
......
if (!mCreated) {
//回調(diào)Dialog的onCreate方法
dispatchOnCreate(null);
}
//回調(diào)Dialog的onStart方法
onStart();
//類似于Activity,獲取當(dāng)前新Window的DecorView對象,所以有一種自定義Dialog布局的方式就是重寫Dialog的onCreate方法,使用setContentView傳入布局,就像前面文章分析Activity類似
mDecor = mWindow.getDecorView();
......
//獲取新Window的WindowManager.LayoutParams參數(shù),和上面分析的Activity一樣type為TYPE_APPLICATION
WindowManager.LayoutParams l = mWindow.getAttributes();
......
try {
//把一個View添加到Activity共用的windowManager里面去
mWindowManager.addView(mDecor, l);
......
} finally {
}
}
就是把獲取要添加的 Window 的 mDecor 和 LayoutParams,調(diào)用 WindowManager 的 addView 方法完成添加。
Activity 和 Dialog 共用了一個 Token 對象,Dialog 必須依賴于 Activity 而顯示(通過別的 context 搞完之后 token 都為 null,最終會在 ViewRootImpl 的 setView 方法中加載時因?yàn)?token 為 null 拋出異常),所以 Dialog 的 Context 傳入?yún)?shù)一般是一個存在的 Activity,如果 Dialog 彈出來之前 Activity 已經(jīng)被銷毀了,則這個 Dialog 在彈出的時候就會拋出異常,因?yàn)?token 不可用了。在 Dialog 的構(gòu)造函數(shù)中我們關(guān)聯(lián)了新 Window 的 callback 事件監(jiān)聽處理,所以當(dāng) Dialog 顯示時 Activity 無法消費(fèi)當(dāng)前的事件,直接回調(diào)了 Dialog 的 Window 的 Callback 監(jiān)聽,Activity 的 Window 接收不到。
PopupWindow 窗口添加顯示機(jī)制
PopWindow 實(shí)質(zhì)就是彈出式菜單,它與 Dialag 不同的地方是不會使依賴的 Activity 組件失去焦點(diǎn)(PopupWindow 彈出后可以繼續(xù)與依賴的 Activity 進(jìn)行交互),Dialog 卻不能這樣。同時PopupWindow 與 Dialog 另一個不同點(diǎn)是 PopupWindow 是一個阻塞的對話框,如果你直接在 Activity 的 onCreate 等方法中顯示它則會報錯,所以 PopupWindow 必須在某個事件中顯示地或者是開啟一個新線程去調(diào)用。
先看 PopupWindow 最常用的一種構(gòu)造函數(shù):
public class PopupWindow {
......
//我們只分析最常用的一種構(gòu)造函數(shù)
public PopupWindow(View contentView, int width, int height, boolean focusable) {
if (contentView != null) {
//獲取mContext,contentView實(shí)質(zhì)是View,View的mContext都是構(gòu)造函數(shù)傳入的,View又層級傳遞,所以最終這個mContext實(shí)質(zhì)是Activity?。?!很重要
mContext = contentView.getContext();
//獲取Activity的getSystemService的WindowManager
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
//進(jìn)行一些Window類的成員變量初始化賦值操作
setContentView(contentView);
setWidth(width);
setHeight(height);
setFocusable(focusable);
}
......
}
mContext 變量是通過 contentView 的 getContext 賦值的,contentView 實(shí)質(zhì)是 View,View 的 context 是通過構(gòu)造函數(shù)傳入的,并且是層層傳遞的,所以這個 context 實(shí)際是 Activity。然后獲取 Activity 的 WindowManager,但是并沒有創(chuàng)建新的 Window。
再看展示方法:
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
......
//anchor是Activity中PopWindow準(zhǔn)備依附的View,這個View的token實(shí)質(zhì)也是Activity的Window中的token,也即Activity的token
//第一步 初始化WindowManager.LayoutParams
WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
//第二步
preparePopup(p);
......
//第三步
invokePopup(p);
}
private WindowManager.LayoutParams createPopupLayout(IBinder token) {
//實(shí)例化一個默認(rèn)的WindowManager.LayoutParams,其中type=TYPE_APPLICATION
WindowManager.LayoutParams p = new WindowManager.LayoutParams();
//設(shè)置Gravity
p.gravity = Gravity.START | Gravity.TOP;
//設(shè)置寬高
p.width = mLastWidth = mWidth;
p.height = mLastHeight = mHeight;
//依據(jù)背景設(shè)置format
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
//設(shè)置flags
p.flags = computeFlags(p.flags);
//修改type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type類型為子窗口
p.type = mWindowLayoutType;
//設(shè)置token為Activity的token
p.token = token;
......
return p;
}
private void preparePopup(WindowManager.LayoutParams p) {
......
//有無設(shè)置PopWindow的background區(qū)別
if (mBackground != null) {
......
//如果有背景則創(chuàng)建一個PopupViewContainer對象的ViewGroup
PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, height
);
//把背景設(shè)置給PopupViewContainer的ViewGroup
popupViewContainer.setBackground(mBackground);
//把我們構(gòu)造函數(shù)傳入的View添加到這個ViewGroup
popupViewContainer.addView(mContentView, listParams);
//返回這個ViewGroup
mPopupView = popupViewContainer;
} else {
//如果沒有通過PopWindow的setBackgroundDrawable設(shè)置背景則直接賦值當(dāng)前傳入的View為PopWindow的View
mPopupView = mContentView;
}
......
}
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
mWindowManager.addView(mPopupView, p);
}
anchor 是 Activity 中 PopWindow 準(zhǔn)備依附的 View,這個 View 的 token 實(shí)質(zhì)也是 Activity 的 Window 中的 token,也即 Activity 的 token。
第一步是創(chuàng)建一個 WindowManager.LayoutParams;
第二步 preparePopup 方法的作用就是判斷設(shè)置 View,如果有背景則會在傳入的 contentView 外面包一層PopupViewContainer(實(shí)質(zhì)是一個重寫了事件處理的 FrameLayout)之后作為 mPopupView,如果沒有背景則直接用 contentView 作為 mPopupView。
PopupViewContainer 是一個 PopWindow 的內(nèi)部私有類,它繼承了 FrameLayout,在其中重寫了 Key 和 Touch 事件的分發(fā)處理邏輯。同時查閱 PopupView 可以發(fā)現(xiàn),PopupView 類自身沒有重寫 Key 和 Touch 事件的處理,所以如果沒有將傳入的 View對象放入封裝的 ViewGroup 中,則點(diǎn)擊 Back 鍵或者PopWindow 以外的區(qū)域 PopWindow 是不會消失的(其實(shí)PopWindow 中沒有向 Activity 及 Dialog 一樣 new 新的 Window ,所以不會有新的 callback 設(shè)置,也就沒法處理事件消費(fèi)了)。這也是為什么我們設(shè)置 PopupWindow 點(diǎn)擊外部消失之前要先設(shè)置背景才有效。
第三步就是使用 WindowManager addView,因?yàn)?PopupWindow 沒有 new 新的 Window,所以 mDecor 不是從 Window 里面獲取的,而是在 preparPopup 方法中調(diào)用 createDecorView 方法生成的:
private PopupDecorView createDecorView(View contentView) {
final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
final int height;
if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
height = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
height = ViewGroup.LayoutParams.MATCH_PARENT;
}
final PopupDecorView decorView = new PopupDecorView(mContext);
decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height);
decorView.setClipChildren(false);
decorView.setClipToPadding(false);
return decorView;
}
Toast 窗口添加顯示機(jī)制
我們常用的Toast窗口其實(shí)和前面分析的Activity、Dialog、PopWindow都是不同的,因?yàn)樗洼斎敕?、墻紙類似,都是系統(tǒng)窗口。
通過分析TN類的handler可以發(fā)現(xiàn),如果想在非UI線程使用Toast需要自行聲明Looper,否則運(yùn)行會拋出Looper相關(guān)的異常;UI線程不需要,因?yàn)橄到y(tǒng)已經(jīng)幫忙聲明。
在使用Toast時context參數(shù)盡量使用getApplicationContext(),可以有效的防止靜態(tài)引用導(dǎo)致的內(nèi)存泄漏。
有時候我們會發(fā)現(xiàn)Toast彈出過多就會延遲顯示,因?yàn)樯厦嬖创a分析可以看見Toast.makeText是一個靜態(tài)工廠方法,每次調(diào)用這個方法都會產(chǎn)生一個新的Toast對象,當(dāng)我們在這個新new的對象上調(diào)用show方法就會使這個對象加入到NotificationManagerService管理的mToastQueue消息顯示隊(duì)列里排隊(duì)等候顯示;所以如果我們不每次都產(chǎn)生一個新的Toast對象(使用單例來處理)就不需要排隊(duì),也就能及時更新了。
總結(jié)一下:添加 Window 就是WindowManager 的 addView 方法,需要三個對象,WindowManager、View、LayoutParams
- Activity 的添加操作是在 makeVisible 方法里
wm.addView(mDecor, getWindow().getAttributes());WindowManager 是在 Window 的 setWindowManager 方法里 調(diào)用 WindowManagerImpl 的 createLocalWindowManager 方法
通過 WindowManagerImpl(display,parentWindow) 構(gòu)造函數(shù)構(gòu)創(chuàng)建的,mDecor 是在 Window 的 setContentView 方法中調(diào)用 initDecor 方法創(chuàng)建的,LayoutParams 是 Window 的 getAttributes 方法獲得一個默認(rèn)為 MATCH_PARENT 的 LayoutParams 成員變量。 - Dialog 的 addView 是在 show 方法里調(diào)用的,WindowManger 是
context.getSystemService(Context.WINDOW_SERVICE)獲取的 Activity 的 WindowManager,mDecor 是mWindow.getDecorView();獲取到 Window 的 DecorView,LayoutParams 也是 Window 的 MATCH_PARENT 的 LayoutParamms 成員變量 - PopupWindow 也是
mContext.getSystemService(Context.WINDOW_SERVICE)獲取的 Activity 的 WindowManager,因?yàn)?PopupWindow 沒有創(chuàng)建新的 Window, 添加的 mPopupView 是在 preparePopup 方法中在傳入的 contentView 外面包一層 PopupViewContainer 或者直接就是 contentView,LayoutParams 是在 showXXX 方法中調(diào)用的 createPopupLayout 方法中構(gòu)造的。
參考:
Android應(yīng)用Activity、Dialog、PopWindow、Toast窗口添加機(jī)制及源碼分析