【Android 源碼解析】Activity扛吞、Dialog、PopWindow荆责、Toast窗口添加機制

一滥比、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 的實現(xiàn)類是 WindowManagerImpl屈雄,實現(xiàn) WindowManager 中定義的接口功能村视。
而 ViewGroup 類也實現(xiàn)了 ViewManager 接口,因為 ViewGroup 要添加或者刪除子 View棚亩,ViewGroup 層層嵌套蓖议,最頂層的是 DecorView,最終顯示在 Window 中讥蟆,Window 是 View 的實際管理者勒虾。

二、Window

我們都知道 Window 是一個抽象類瘸彤,唯一實現(xiàn)類是 PhoneWindow修然。創(chuàng)建一個 Window 很簡單,只需要通過 WindowManager 即可完成质况,WindowManager 是外界訪問 Window 的入口愕宋,Window 具體實現(xiàn)在 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一個 IPC 過程结榄,這里不作深究中贝。
怎么通過 WindowManager 添加一個 Window 呢?很簡單臼朗,使用 addView 方法
windowManager.addView(view,layoutParams);

1邻寿、WindowManager.LayoutParams

Window 的一些屬性都是通過 WindowManager.LayoutParams 來定義的。

Window 類型

Window 是分層的视哑,層級大的會覆蓋在層級小的上面绣否。
Window 有三種類型,

  1. 應(yīng)用 Window挡毅。一個應(yīng)用 Window 對應(yīng)著 Activity蒜撮,層級范圍1-99;
  2. 子 Window跪呈。不能單獨存在段磨,需要依附在特定的父 Window 之中取逾,比如一些 Dialog,層級范圍 1000-1999薇溃;
  3. 系統(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)容之上躬审,此窗口不能獲得輸入焦點棘街,否則影響鎖屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //電話優(yōu)先,當(dāng)鎖屏?xí)r顯示盒件,此窗口不能獲得輸入焦點蹬碧,否則影響鎖屏
        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)覆蓋窗口翔始,這些窗戶必須不帶輸入焦點罗心,否則會干擾鍵盤
        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;
        //窗口不能獲得焦點
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //窗口不接受觸摸屏事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //即使在該window在可獲得焦點情況下,允許該窗口之外的點擊事件傳遞到當(dāng)前窗口后面的的窗口去
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //當(dāng)手機處于睡眠狀態(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占滿整個手機屏幕飒箭,不留任何邊界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //允許窗口超出整個手機屏幕
        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)做一個用戶活動事件弦蹂,以點亮手機屏幕
        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進行硬件加速削祈,該flag必須在Activity或Dialog的Content View之前進行設(shè)置
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //讓window占滿整個手機屏幕,不留任何邊界
        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 還有一些屬性髓抑,這里說幾個比較重要的:

//窗口的起點坐標(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)部機制

Window 是一個抽象概念吨拍,并不是實際存在的,而是以 View 的形式存在的劈伴,這從 WindowManager 的定義可以看出來密末,addView,updateView跛璧,removeView 都是針對 View 的严里,View 才是 Window 的實體,在實際使用中必須通過 WindowManager 才能訪問 Window追城。
每個 Window 都對應(yīng)著一個 View 和 ViewRootImpl刹碾,Window 和 View 是通過 ViewRootImpl 聯(lián)系起來的。

Window 添加過程

Window 的添加座柱、更新和刪除需要 WindowManager 的 addView迷帜、updateView、removeView 方法色洞,WindowManager 是接口戏锹,真正的實現(xiàn)是 WindowManagerImpl,但是其實 WindowManagerImpl 也沒有直接實現(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 來進行 View 的繪制宏粤。
接著通過 WindowSession 完成 Window 的添加過程脚翘,Session 內(nèi)部是通過 WindowManagerService 實現(xiàn) Window 的添加的。

Window 的刪除過程

WindowManagerGlobal 的 removeView 先通過 findViewLocked 來查找待刪除的 View 的索引商架,然后調(diào)用 removeViewLoacked 來做進一步刪除堰怨,內(nèi)部是通過 ViewRootImpl 來完成刪除操作的

Window 更新過程

首先更新 View 的LayoutParams,接著更新 ViewRootImpl 中的 LayoutParams蛇摸,這一步是通過 ViewRootImpl 的 setLayoutParams 來實現(xiàn)的,在 ViewRootImpl 中會通過 scheduleTraversals 來對 View 重新布局灿巧,并通過 WindowSession 來更新 Window 視圖赶袄。

三、Window 創(chuàng)建過程

Activity 窗口添加流程

前面的文章說過抠藕,Activity 的實例化是在 ActivityThread 的 performLaunchActivity 方法開始的饿肺,在創(chuàng)建好 ContextImpl 對象后調(diào)用了 Activity 的 attach 方法,把 ContextImpl 的實例賦值給 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對象,實際為PhoneWindow類實現(xiàn)了抽象Window類
        mWindow = PolicyManager.makeNewWindow(this);
        ......
        //通過抽象Window類的setWindowManager方法給Window類的成員變量WindowManager賦值實例化
        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();
        ......
    }
  1. 通過 PolicyManager 的 makeNewWindow 方法創(chuàng)建一個 PhoneWindow 對象零院,賦值給 Activity 的成員變量mWindow溉跃;
  2. 通過 Window 類的 setWindowManager 方法給 Window 的 mWindowManager 成員變量賦值實例化;
  3. 把前面實例化的 Window 的成員變量 mWindowManager 賦值給 Activity 的成員變量 mWindowManager告抄,使 Activity 和 WindowManager 關(guān)聯(lián)起來撰茎;

再看 Window 的 setWindowManager 方法,看WindowManager 的實例是怎么創(chuàng)建的:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ......
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //實例化Window類的WindowManager類型成員mWindowManager
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

就是調(diào)用穿進去的或者重新獲取的 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 中傳進去的第一個參數(shù) (WindowManager)context.getSystemService(Context.WINDOW_SERVICE) 是從哪來的呢?
在 Context 的實現(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實例
                    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實例
                    return new WindowManagerImpl(display);
                }});
        ......
    }
    //這就是你在外面調(diào)運Context的getSystemService獲取到的WindowManagerImpl實例
    @Override
    public Object getSystemService(String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }
    //上面static代碼塊創(chuàng)建WindowManagerImpl實例用到的方法
    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 實例阿浓。
這個 WindowManagerIMpl 與 Activity 中關(guān)聯(lián)的 WindowManagerImpl 實例的不同之處在于這個是通過一個參數(shù)的構(gòu)造方法創(chuàng)建的他嚷,其實也就是 parentWindow 是 null,而Activity 中 Window 的 WindowManager 成員在構(gòu)造實例化時傳入給 WindowManagerImpl 中 mParentWindow 成員的是當(dāng)前 Window 對象,爸舒;還要就是靜態(tài)代碼塊是只在初始時加載一次蟋字,所以這個 WindowManager 是全局單例的。
每一個 Activity 都會新創(chuàng)建一個 WindowManager 實例來顯示 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)運的實質(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)建一個的(其實你從上面分析的兩個實例化WindowManagerImpl的構(gòu)造函數(shù)參數(shù)傳遞就可以看出來,Activity中Window的WindowManager成員在構(gòu)造實例化時傳入給WindowManagerImpl中mParentWindow成員的是當(dāng)前Window對象唱捣,而ContextImpl的static塊中單例實例化WindowManagerImpl時傳入給WindowManagerImpl中mParentWindow成員的是null值)两蟀,使用 Activity 的 getSysytemService(WINDOW_SERVICE) 獲取的是 Local 的WindowManager。

Dialog 窗口添加顯示機制

從 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)運了這個默認(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)U痃浴B柑骸咒钟!非常重要顶燕,先記住J疟洹Q采纭膛堤!

        //獲取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 的實例燕耿,這個 context 是通過構(gòu)造方法傳進來的,一般是 Activity Context潜圃,所以 Dialog 的 WindowManager 其實是 Activity 的 mWindowManager缸棵,并通過 Window 類的 setWindowManager 方法與 Window 關(guān)聯(lián),Dialog 類也實現(xiàn)了 Window.Callback谭期,Window.OnWindowDismissedCallback并給 Window.setCallback堵第,所以 Dialog 能夠接受到點擊等事件。
至此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 方法中加載時因為 token 為 null 拋出異常)伪朽,所以 Dialog 的 Context 傳入?yún)?shù)一般是一個存在的 Activity轴咱,如果 Dialog 彈出來之前 Activity 已經(jīng)被銷毀了,則這個 Dialog 在彈出的時候就會拋出異常烈涮,因為 token 不可用了朴肺。在 Dialog 的構(gòu)造函數(shù)中我們關(guān)聯(lián)了新 Window 的 callback 事件監(jiān)聽處理,所以當(dāng) Dialog 顯示時 Activity 無法消費當(dāng)前的事件坚洽,直接回調(diào)了 Dialog 的 Window 的 Callback 監(jiān)聽戈稿,Activity 的 Window 接收不到。

PopupWindow 窗口添加顯示機制

PopWindow 實質(zhì)就是彈出式菜單讶舰,它與 Dialag 不同的地方是不會使依賴的 Activity 組件失去焦點(PopupWindow 彈出后可以繼續(xù)與依賴的 Activity 進行交互)鞍盗,Dialog 卻不能這樣。同時PopupWindow 與 Dialog 另一個不同點是 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實質(zhì)是View,View的mContext都是構(gòu)造函數(shù)傳入的住拭,View又層級傳遞挪略,所以最終這個mContext實質(zhì)是Activity!L显馈杠娱!很重要
            mContext = contentView.getContext();
            //獲取Activity的getSystemService的WindowManager
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //進行一些Window類的成員變量初始化賦值操作
        setContentView(contentView);
        setWidth(width);
        setHeight(height);
        setFocusable(focusable);
    }
    ......
}

mContext 變量是通過 contentView 的 getContext 賦值的,contentView 實質(zhì)是 View谱煤,View 的 context 是通過構(gòu)造函數(shù)傳入的摊求,并且是層層傳遞的,所以這個 context 實際是 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實質(zhì)也是Activity的Window中的token茧痕,也即Activity的token
        //第一步   初始化WindowManager.LayoutParams
        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
        //第二步
        preparePopup(p);
        ......
        //第三步
        invokePopup(p);
    }

private WindowManager.LayoutParams createPopupLayout(IBinder token) {
        //實例化一個默認(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 實質(zhì)也是 Activity 的 Window 中的 token,也即 Activity 的 token令野。
第一步是創(chuàng)建一個 WindowManager.LayoutParams舀患;
第二步 preparePopup 方法的作用就是判斷設(shè)置 View,如果有背景則會在傳入的 contentView 外面包一層PopupViewContainer(實質(zhì)是一個重寫了事件處理的 FrameLayout)之后作為 mPopupView气破,如果沒有背景則直接用 contentView 作為 mPopupView聊浅。
PopupViewContainer 是一個 PopWindow 的內(nèi)部私有類,它繼承了 FrameLayout堵幽,在其中重寫了 Key 和 Touch 事件的分發(fā)處理邏輯狗超。同時查閱 PopupView 可以發(fā)現(xiàn),PopupView 類自身沒有重寫 Key 和 Touch 事件的處理朴下,所以如果沒有將傳入的 View對象放入封裝的 ViewGroup 中努咐,則點擊 Back 鍵或者PopWindow 以外的區(qū)域 PopWindow 是不會消失的(其實PopWindow 中沒有向 Activity 及 Dialog 一樣 new 新的 Window ,所以不會有新的 callback 設(shè)置殴胧,也就沒法處理事件消費了)渗稍。這也是為什么我們設(shè)置 PopupWindow 點擊外部消失之前要先設(shè)置背景才有效。
第三步就是使用 WindowManager addView团滥,因為 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 窗口添加顯示機制

我們常用的Toast窗口其實和前面分析的Activity灸姊、Dialog拱燃、PopWindow都是不同的,因為它和輸入法力惯、墻紙類似碗誉,都是系統(tǒng)窗口。
通過分析TN類的handler可以發(fā)現(xiàn)父晶,如果想在非UI線程使用Toast需要自行聲明Looper哮缺,否則運行會拋出Looper相關(guān)的異常;UI線程不需要甲喝,因為系統(tǒng)已經(jīng)幫忙聲明尝苇。
在使用Toast時context參數(shù)盡量使用getApplicationContext(),可以有效的防止靜態(tài)引用導(dǎo)致的內(nèi)存泄漏埠胖。
有時候我們會發(fā)現(xiàn)Toast彈出過多就會延遲顯示糠溜,因為上面源碼分析可以看見Toast.makeText是一個靜態(tài)工廠方法,每次調(diào)用這個方法都會產(chǎn)生一個新的Toast對象押袍,當(dāng)我們在這個新new的對象上調(diào)用show方法就會使這個對象加入到NotificationManagerService管理的mToastQueue消息顯示隊列里排隊等候顯示诵冒;所以如果我們不每次都產(chǎn)生一個新的Toast對象(使用單例來處理)就不需要排隊,也就能及時更新了谊惭。
總結(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容诬,因為 PopupWindow 沒有創(chuàng)建新的 Window, 添加的 mPopupView 是在 preparePopup 方法中在傳入的 contentView 外面包一層 PopupViewContainer 或者直接就是 contentView沿腰,LayoutParams 是在 showXXX 方法中調(diào)用的 createPopupLayout 方法中構(gòu)造的览徒。
    參考:
    Android應(yīng)用Activity、Dialog颂龙、PopWindow习蓬、Toast窗口添加機制及源碼分析
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市措嵌,隨后出現(xiàn)的幾起案子躲叼,更是在濱河造成了極大的恐慌,老刑警劉巖企巢,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枫慷,死亡現(xiàn)場離奇詭異,居然都是意外死亡浪规,警方通過查閱死者的電腦和手機流礁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罗丰,“玉大人,你說我怎么就攤上這事再姑∶鹊郑” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵元镀,是天一觀的道長绍填。 經(jīng)常有香客問我,道長栖疑,這世上最難降的妖魔是什么讨永? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮遇革,結(jié)果婚禮上卿闹,老公的妹妹穿的比我還像新娘揭糕。我一直安慰自己,他們只是感情好锻霎,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布著角。 她就那樣靜靜地躺著,像睡著了一般旋恼。 火紅的嫁衣襯著肌膚如雪吏口。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天冰更,我揣著相機與錄音产徊,去河邊找鬼。 笑死蜀细,一個胖子當(dāng)著我的面吹牛舟铜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播审葬,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼深滚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涣觉?” 一聲冷哼從身側(cè)響起痴荐,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎官册,沒想到半個月后生兆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡膝宁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年鸦难,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片员淫。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡合蔽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出介返,到底是詐尸還是另有隱情拴事,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布圣蝎,位于F島的核電站刃宵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏徘公。R本人自食惡果不足惜牲证,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望关面。 院中可真熱鬧坦袍,春花似錦十厢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辛燥,卻和暖如春筛武,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挎塌。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工徘六, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人榴都。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓待锈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嘴高。 傳聞我的和親對象是個殘疾皇子竿音,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容