Android窗口機(jī)制(五)最終章:WindowManager.LayoutParams和Token以及其他窗口Dialog,Toast

Android窗口機(jī)制系列

Android窗口機(jī)制(一)初識Android的窗口結(jié)構(gòu)
Android窗口機(jī)制(二)Window,PhoneWindow盏檐,DecorView,setContentView源碼理解
Android窗口機(jī)制(三)Window和WindowManager的創(chuàng)建與Activity
Android窗口機(jī)制(四)ViewRootImpl與View和WindowManager
Android窗口機(jī)制(五)最終章:WindowManager.LayoutParams和Token以及其他窗口Dialog驶悟,Toast

前面幾篇文章基本介紹完Activity上的窗口機(jī)制胡野,但是我們常見的窗口就還有Dialog,Toast這些痕鳍,本篇文章就來介紹這兩個的窗口機(jī)制以及WindowManager.LayoutParams和Token

WindowManager.LayoutParams

首先硫豆,先跟大家介紹這個WindowManager.LayoutParams,在前面幾篇文章中笼呆,都有出現(xiàn)過這個LayoutParams熊响,我們看下具體的源碼。
翻譯參考

  public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
        //窗口的絕對XY位置诗赌,需要考慮gravity屬性
        public int x;
        public int y;
        //在橫縱方向上為相關(guān)的View預(yù)留多少擴(kuò)展像素耘眨,如果是0則此view不能被拉伸,其他情況下擴(kuò)展像素被widget均分
        public float horizontalWeight;
        public float verticalWeight;
        //窗口類型
        //有3種主要類型如下:
        //ApplicationWindows取值在FIRST_APPLICATION_WINDOW與LAST_APPLICATION_WINDOW之間境肾,是常用的頂層應(yīng)用程序窗口剔难,須將token設(shè)置成Activity的token;
        //SubWindows取值在FIRST_SUB_WINDOW和LAST_SUB_WINDOW之間奥喻,與頂層窗口相關(guān)聯(lián)偶宫,需將token設(shè)置成它所附著宿主窗口的token;
        //SystemWindows取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW之間环鲤,不能用于應(yīng)用程序纯趋,使用時需要有特殊權(quán)限,它是特定的系統(tǒng)功能才能使用;
        public int type;

        //WindowType:開始應(yīng)用程序窗口
        public static final int FIRST_APPLICATION_WINDOW = 1;
        //WindowType:所有程序窗口的base窗口吵冒,其他應(yīng)用程序窗口都顯示在它上面
        public static final int TYPE_BASE_APPLICATION   = 1;
        //WindowType:普通應(yīng)用程序窗口纯命,token必須設(shè)置為Activity的token來指定窗口屬于誰
        public static final int TYPE_APPLICATION        = 2;
        //WindowType:應(yīng)用程序啟動時所顯示的窗口,應(yīng)用自己不要使用這種類型痹栖,它被系統(tǒng)用來顯示一些信息亿汞,直到應(yīng)用程序可以開啟自己的窗口為止
        public static final int TYPE_APPLICATION_STARTING = 3;
        //WindowType:結(jié)束應(yīng)用程序窗口
        public static final int LAST_APPLICATION_WINDOW = 99;

        //WindowType:SubWindows子窗口,子窗口的Z序和坐標(biāo)空間都依賴于他們的宿主窗口
        public static final int FIRST_SUB_WINDOW        = 1000;
        //WindowType: 面板窗口揪阿,顯示于宿主窗口的上層
        public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
        //WindowType:媒體窗口(例如視頻)疗我,顯示于宿主窗口下層
        public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
        //WindowType:應(yīng)用程序窗口的子面板,顯示于所有面板窗口的上層
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
        //WindowType:對話框南捂,類似于面板窗口吴裤,繪制類似于頂層窗口,而不是宿主的子窗口
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
        //WindowType:媒體信息溺健,顯示在媒體層和程序窗口之間麦牺,需要實現(xiàn)半透明效果
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;
        //WindowType:子窗口結(jié)束
        public static final int LAST_SUB_WINDOW         = 1999;

        //WindowType:系統(tǒng)窗口,非應(yīng)用程序創(chuàng)建
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        //WindowType:狀態(tài)欄鞭缭,只能有一個狀態(tài)欄剖膳,位于屏幕頂端,其他窗口都位于它下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        //WindowType:搜索欄缚去,只能有一個搜索欄潮秘,位于屏幕上方
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        //WindowType:電話窗口,它用于電話交互(特別是呼入)易结,置于所有應(yīng)用程序之上枕荞,狀態(tài)欄之下
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        //WindowType:系統(tǒng)提示,出現(xiàn)在應(yīng)用程序窗口之上
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
        //WindowType:鎖屏窗口
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        //WindowType:信息窗口搞动,用于顯示Toast
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
        //WindowType:系統(tǒng)頂層窗口躏精,顯示在其他一切內(nèi)容之上,此窗口不能獲得輸入焦點鹦肿,否則影響鎖屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //WindowType:電話優(yōu)先矗烛,當(dāng)鎖屏?xí)r顯示,此窗口不能獲得輸入焦點箩溃,否則影響鎖屏
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        //WindowType:系統(tǒng)對話框
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        //WindowType:鎖屏?xí)r顯示的對話框
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        //WindowType:系統(tǒng)內(nèi)部錯誤提示瞭吃,顯示于所有內(nèi)容之上
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        //WindowType:內(nèi)部輸入法窗口,顯示于普通UI之上涣旨,應(yīng)用程序可重新布局以免被此窗口覆蓋
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        //WindowType:內(nèi)部輸入法對話框歪架,顯示于當(dāng)前輸入法窗口之上
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        //WindowType:墻紙窗口
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        //WindowType:狀態(tài)欄的滑動面板
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        //WindowType:安全系統(tǒng)覆蓋窗口,這些窗戶必須不帶輸入焦點霹陡,否則會干擾鍵盤
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        //WindowType:拖放偽窗口和蚪,只有一個阻力層(最多)止状,它被放置在所有其他窗口上面
        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
        //WindowType:狀態(tài)欄下拉面板
        public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
        //WindowType:鼠標(biāo)指針
        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
        //WindowType:導(dǎo)航欄(有別于狀態(tài)欄時)
        public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
        //WindowType:音量級別的覆蓋對話框,顯示當(dāng)用戶更改系統(tǒng)音量大小
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
        //WindowType:起機(jī)進(jìn)度框攒霹,在一切之上
        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
        //WindowType:假窗怯疤,消費導(dǎo)航欄隱藏時觸摸事件
        public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;
        //WindowType:夢想(屏保)窗口,略高于鍵盤
        public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
        //WindowType:導(dǎo)航欄面板(不同于狀態(tài)欄的導(dǎo)航欄)
        public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
        //WindowType:universe背后真正的窗戶
        public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
        //WindowType:顯示窗口覆蓋催束,用于模擬輔助顯示設(shè)備
        public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;
        //WindowType:放大窗口覆蓋集峦,用于突出顯示的放大部分可訪問性放大時啟用
        public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;
        //WindowType:......
        public static final int TYPE_KEYGUARD_SCRIM           = FIRST_SYSTEM_WINDOW+29;
        public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;
        public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;
        public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
        //WindowType:系統(tǒng)窗口結(jié)束
        public static final int LAST_SYSTEM_WINDOW      = 2999;

        //MemoryType:窗口緩沖位于主內(nèi)存
        public static final int MEMORY_TYPE_NORMAL = 0;
        //MemoryType:窗口緩沖位于可以被DMA訪問,或者硬件加速的內(nèi)存區(qū)域
        public static final int MEMORY_TYPE_HARDWARE = 1;
        //MemoryType:窗口緩沖位于可被圖形加速器訪問的區(qū)域
        public static final int MEMORY_TYPE_GPU = 2;
        //MemoryType:窗口緩沖不擁有自己的緩沖區(qū)泣崩,不能被鎖定少梁,緩沖區(qū)由本地方法提供
        public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;

        //指出窗口所使用的內(nèi)存緩沖類型洛口,默認(rèn)為NORMAL 
        public int memoryType;

        //Flag:當(dāng)該window對用戶可見的時候矫付,允許鎖屏
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
        //Flag:讓該window后所有的東西都成暗淡
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        //Flag:讓該window后所有東西都模糊(4.0以上已經(jīng)放棄這種毛玻璃效果)
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        //Flag:讓window不能獲得焦點,這樣用戶快就不能向該window發(fā)送按鍵事
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //Flag:讓該window不接受觸摸屏事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //Flag:即使在該window在可獲得焦點情況下第焰,依舊把該window之外的任何event發(fā)送到該window之后的其他window
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //Flag:當(dāng)手機(jī)處于睡眠狀態(tài)時买优,如果屏幕被按下,那么該window將第一個收到
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        //Flag:當(dāng)該window對用戶可見時挺举,讓設(shè)備屏幕處于高亮(bright)狀態(tài)
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
        //Flag:讓window占滿整個手機(jī)屏幕杀赢,不留任何邊界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //Flag:window大小不再不受手機(jī)屏幕大小限制,即window可能超出屏幕之外
        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
        //Flag:window全屏顯示
        public static final int FLAG_FULLSCREEN      = 0x00000400;
        //Flag:恢復(fù)window非全屏顯示
        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
        //Flag:開啟抖動(dithering)
        public static final int FLAG_DITHER             = 0x00001000;
        //Flag:當(dāng)該window在進(jìn)行顯示的時候湘纵,不允許截屏
        public static final int FLAG_SECURE             = 0x00002000;
        //Flag:一個特殊模式的布局參數(shù)用于執(zhí)行擴(kuò)展表面合成時到屏幕上
        public static final int FLAG_SCALED             = 0x00004000;
        //Flag:用于windows時,經(jīng)常會使用屏幕用戶持有反對他們的臉,它將積極過濾事件流,以防止意外按在這種情況下,可能不需要為特定的窗口,在檢測到這樣一個事件流時,應(yīng)用程序?qū)⒔邮杖∠\(yùn)動事件表明,這樣應(yīng)用程序可以處理這相應(yīng)地采取任何行動的事件,直到手指釋放
        public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
        //Flag:一個特殊的選項只用于結(jié)合FLAG_LAYOUT_IN_SC
        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
        //Flag:轉(zhuǎn)化的狀態(tài)FLAG_NOT_FOCUSABLE對這個窗口當(dāng)前如何進(jìn)行交互的方法
        public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
        //Flag:如果你設(shè)置了該flag,那么在你FLAG_NOT_TOUNCH_MODAL的情況下脂崔,即使觸摸屏事件發(fā)送在該window之外,其事件被發(fā)送到了后面的window,那么該window仍然將以MotionEvent.ACTION_OUTSIDE形式收到該觸摸屏事件
        public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
        //Flag:當(dāng)鎖屏的時候梧喷,顯示該window
        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
        //Flag:在該window后顯示系統(tǒng)的墻紙
        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
        //Flag:當(dāng)window被顯示的時候砌左,系統(tǒng)將把它當(dāng)做一個用戶活動事件,以點亮手機(jī)屏幕
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
        //Flag:消失鍵盤
        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
        //Flag:當(dāng)該window在可以接受觸摸屏情況下铺敌,讓因在該window之外汇歹,而發(fā)送到后面的window的觸摸屏可以支持split touch
        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
        //Flag:對該window進(jìn)行硬件加速,該flag必須在Activity或Dialog的Content View之前進(jìn)行設(shè)置
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //Flag:讓window占滿整個手機(jī)屏幕偿凭,不留任何邊界
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        //Flag:請求一個半透明的狀態(tài)欄背景以最小的系統(tǒng)提供保護(hù)
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        //Flag:請求一個半透明的導(dǎo)航欄背景以最小的系統(tǒng)提供保護(hù)
        public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;
        //Flag:......
        public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
        public static final int FLAG_SLIPPERY = 0x20000000;
        public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;
        public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;

        //行為選項標(biāo)記
        public int flags;

        //PrivateFlags:......
        public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;
        public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
        public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
        public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
        public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;
        public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;
        public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;
        public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;
        public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;
        public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;

        //私有的行為選項標(biāo)記
        public int privateFlags;

        public static final int NEEDS_MENU_UNSET = 0;
        public static final int NEEDS_MENU_SET_TRUE = 1;
        public static final int NEEDS_MENU_SET_FALSE = 2;
        public int needsMenuKey = NEEDS_MENU_UNSET;

        public static boolean mayUseInputMethod(int flags) {
            ......
        }

        //SOFT_INPUT:用于描述軟鍵盤顯示規(guī)則的bite的mask
        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
        //SOFT_INPUT:沒有軟鍵盤顯示的約定規(guī)則
        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
        //SOFT_INPUT:可見性狀態(tài)softInputMode产弹,請不要改變軟輸入?yún)^(qū)域的狀態(tài)
        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
        //SOFT_INPUT:用戶導(dǎo)航(navigate)到你的窗口時隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
        //SOFT_INPUT:總是隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
        //SOFT_INPUT:用戶導(dǎo)航(navigate)到你的窗口時顯示軟鍵盤
        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
        //SOFT_INPUT:總是顯示軟鍵盤
        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
        //SOFT_INPUT:顯示軟鍵盤時用于表示window調(diào)整方式的bite的mask
        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
        //SOFT_INPUT:不指定顯示軟件盤時,window的調(diào)整方式
        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤時弯囊,調(diào)整window內(nèi)的控件大小以便顯示軟鍵盤
        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤時痰哨,調(diào)整window的空白區(qū)域來顯示軟鍵盤,即使調(diào)整空白區(qū)域匾嘱,軟鍵盤還是有可能遮擋一些有內(nèi)容區(qū)域斤斧,這時用戶就只有退出軟鍵盤才能看到這些被遮擋區(qū)域并進(jìn)行
        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
        //SOFT_INPUT:當(dāng)顯示軟鍵盤時,不調(diào)整window的布局
        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
        //SOFT_INPUT:用戶導(dǎo)航(navigate)到了你的window
        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;

        //軟輸入法模式選項
        public int softInputMode;

        //窗口如何脱僬保靠
        public int gravity;
        //水平邊距折欠,容器與widget之間的距離,占容器寬度的百分率
        public float horizontalMargin;
        //縱向邊距
        public float verticalMargin;
        //積極的insets繪圖表面和窗口之間的內(nèi)容
        public final Rect surfaceInsets = new Rect();
        //期望的位圖格式,默認(rèn)為不透明锐秦,參考android.graphics.PixelFormat
        public int format;
        //窗口所使用的動畫設(shè)置咪奖,它必須是一個系統(tǒng)資源而不是應(yīng)用程序資源,因為窗口管理器不能訪問應(yīng)用程序
        public int windowAnimations;
        //整個窗口的半透明值酱床,1.0表示不透明羊赵,0.0表示全透明
        public float alpha = 1.0f;
        //當(dāng)FLAG_DIM_BEHIND設(shè)置后生效,該變量指示后面的窗口變暗的程度扇谣,1.0表示完全不透明昧捷,0.0表示沒有變暗
        public float dimAmount = 1.0f;

        public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
        public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
        public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
        public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
        //用來覆蓋用戶設(shè)置的屏幕亮度,表示應(yīng)用用戶設(shè)置的屏幕亮度罐寨,從0到1調(diào)整亮度從暗到最亮發(fā)生變化
        public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;

        public static final int ROTATION_ANIMATION_ROTATE = 0;
        public static final int ROTATION_ANIMATION_CROSSFADE = 1;
        public static final int ROTATION_ANIMATION_JUMPCUT = 2;
        //定義出入境動畫在這個窗口旋轉(zhuǎn)設(shè)備時使用
        public int rotationAnimation = ROTATION_ANIMATION_ROTATE;

        //窗口的標(biāo)示符
        public IBinder token = null;
        //此窗口所在的包名
        public String packageName = null;
        //屏幕方向
        public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
        //首選的刷新率的窗口
        public float preferredRefreshRate;
        //控制status bar是否顯示
        public int systemUiVisibility;
        //ui能見度所請求的視圖層次結(jié)構(gòu)
        public int subtreeSystemUiVisibility;
        //得到關(guān)于系統(tǒng)ui能見度變化的回調(diào)
        public boolean hasSystemUiListeners;

        public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;
        public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;
        public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;
        public int inputFeatures;
        public long userActivityTimeout = -1;

        ......
        public final int copyFrom(LayoutParams o) {
            ......
        }

        ......
        public void scale(float scale) {
            ......
        }

        ......
    }

可以看到在WindowManager.LayoutParams上有三種窗口類型type靡挥,對應(yīng)為

  • **應(yīng)用程序窗口 : **type值在 FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW 須將token設(shè)置成Activity的token
    eg: 前面介紹的Activity窗口,Dialog
  • 子窗口: type值在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows與頂層窗口相關(guān)聯(lián)鸯绿,需將token設(shè)置成它所附著宿主窗口的token跋破。
    eg: PopupWindow(想要依附在Activity上需要將token設(shè)置成Activity的token)
  • **系統(tǒng)窗口: type值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW ** SystemWindows不能用于應(yīng)用程序,使用時需要有特殊權(quán)限瓶蝴,它是特定的系統(tǒng)功能才能使用毒返。
    eg: Toast,輸入法等舷手。

WindowManager.LayoutParams源碼中也講到輸入法的問題拧簸,里面有很多種模式,通過設(shè)置softInputMode來調(diào)整輸入法男窟。這里舉個常見例子吧盆赤,平時我們在Activity的底部放置EditText的時候,輸入法的彈出可能會遮擋住界面蝎宇。
這里通過設(shè)置相應(yīng)的softInputMode就可以解決這個問題

<activity  
     android:name=".TestActivity"  
     android:windowSoftInputMode="stateVisible|adjustResize" >  
     <intent-filter>  
          <action android:name="android.intent.action.MAIN" />  
          <category android:name="android.intent.category.LAUNCHER" />  
     </intent-filter>  
</activity>  

或者

public class TestActivity extends AppCompatActivity {      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);                 
        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
        setContentView(R.layout.activity_test);  
    }
}  

另外弟劲,三種類型里面出現(xiàn)了個概念,就是token問題姥芥。
在應(yīng)用程序窗口中兔乞,token是用來標(biāo)識Activity的,一個Activity就對應(yīng)一個token令牌
而在子窗口中凉唐,某個子窗口想要依附在對應(yīng)的宿主窗口上設(shè)置要將token設(shè)置為對應(yīng)宿主窗口的token庸追。

token

token是用來表示窗口的一個令牌,只有符合條件的token才能被WMS通過添加到應(yīng)用上台囱。
我們來看下token的傳遞過程

首先對于Activity里面的token淡溯,它的創(chuàng)建則是在AMS啟動Activity開始的,之后保存在ActivityRecord.appToken中簿训。而對于Activity中的token綁定到對應(yīng)的Window上
我們知道咱娶,應(yīng)用程序窗口的Activity窗口Window是在Activity創(chuàng)建過程中創(chuàng)建的米间,具體是在activity.attach方法中創(chuàng)建的。

 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) {
        ...
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        //設(shè)置軟鍵盤
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        ...
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ...
        mWindowManager = mWindow.getWindowManager();
    }

追蹤token可看到最后傳遞到window.setWindowManager中

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

在setWindowManager中膘侮,appToken賦值到Window上屈糊,同時在當(dāng)前Window上創(chuàng)建了WindowManager。

在將DecorView添加到WindowManager時候琼了,會調(diào)用到windowManagerGlobal.addView方法

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       ...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
           ...
        }
       ...
    }

parentWindow.adjustLayoutParamsForSubWindow(wparams);方法里面的重要一步就是給token設(shè)置值逻锐。不過在這以前要判斷parentWindow是否為null。

  • 如果是應(yīng)用程序窗口的話雕薪,這個parentWindow就是activity的window
  • 如果是子窗口的話昧诱,這個parentWindow就是activity的window
  • 如果是系統(tǒng)窗口的話,那個parentWindow就是null

這個parentWindow則是在創(chuàng)建WindowManagerImpl的時候被賦值的

 private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }

為什么說子窗口中的parentWindow是Activity的window所袁,因為子窗口中用到的是Activity的WindowManager盏档,這里會在下面分析到Dialog的時候說。
在Window.adjustLayoutParamsForSubWindow方法中

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
            wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
            ...
        } else {
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
           ...
        }
     ...
    }

可以看到在adjustLayoutParamsForSubWindow通過wp.type來判斷當(dāng)前窗口的類型纲熏,如果是子窗口類型妆丘,則wp.token = decor.getWindowToken();這里賦值的是父窗口的W對象锄俄。關(guān)于W對象在下面講解局劲。
如果是應(yīng)用程序窗口,則走分支奶赠。一般應(yīng)用程序窗口的話鱼填,mContainer為null,也就是mAppToken毅戈,就是Activity的mToken對象苹丸。

獲取到Token后就保存在了LayoutParams里面,接著到WindowManagerGlobal.addView中去苇经。

   root = new ViewRootImpl(view.getContext(), display);

   view.setLayoutParams(wparams);

   mViews.add(view);
   mRoots.add(root);
   mParams.add(wparams);
   ...
   root.setView(view, wparams, panelParentView);</pre>

可以看到token保存在WindowManager.LayoutParams中赘理,之后再傳到了ViewRootImpl.setView

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
        
        final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
        final IWindowSession mWindowSession;
        ...
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
                mWindowAttributes.copyFrom(attrs);
                ...
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
            }
        }
    }
}

可以看到從WindowManagerGlobal中傳遞過來的params賦值到了ViewRootImpl中的mWindowAttributes中,之后調(diào)用到了ViewRootImpl.setView方法中的mWindowSession的addToDisplay方法扇单,該方法用來請求WMS添加Window
mWindowSession的類型是IWindowSession它的實現(xiàn)類是Session商模,用來與WMS通信

 final class Session extends IWindowSession.Stub
        implements IBinder.DeathRecipient {
      final WindowManagerService mService;
      ...
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
 }

我們看下WindowManagerService中是如何判斷這個token的

 public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        int[] appOp = new int[1];
        //判斷權(quán)限
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        ...
        final int type = attrs.type;
        synchronized(mWindowMap) {
            ...
            boolean addToken = false;
            WindowToken token = mTokenMap.get(attrs.token);
            if (token == null) {
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_INPUT_METHOD) {
                    Slog.w(TAG, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_WALLPAPER) {
                    Slog.w(TAG, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_DREAM) {
                    Slog.w(TAG, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                AppWindowToken atoken = token.appWindowToken;
                if (atoken == null) {
                    Slog.w(TAG, "Attempted to add window with non-application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                    Slog.w(TAG, "Attempted to add window with exiting application token "
                          + token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_APP_EXITING;
                }
                if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
                    // No need for this guy!
                    if (localLOGV) Slog.v(
                            TAG, "**** NO NEED TO START: " + attrs.getTitle());
                    return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
                }
            } else if (type == TYPE_INPUT_METHOD) {
                if (token.windowType != TYPE_INPUT_METHOD) {
                    Slog.w(TAG, "Attempted to add input method window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_VOICE_INTERACTION) {
                if (token.windowType != TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG, "Attempted to add voice interaction window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_WALLPAPER) {
                if (token.windowType != TYPE_WALLPAPER) {
                    Slog.w(TAG, "Attempted to add wallpaper window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_DREAM) {
                if (token.windowType != TYPE_DREAM) {
                    Slog.w(TAG, "Attempted to add Dream window with bad token "
                            + attrs.token + ".  Aborting.");
                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG, "Attempted to add Accessibility overlay window with bad token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
            } else if (token.appWindowToken != null) {
                Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type);
                // It is not valid to use an app token with other system types; we will
                // instead make a new token for it (as if null had been passed in for the token).
                attrs.token = null;
                token = new WindowToken(this, null, -1, false);
                addToken = true;
            }
        ...
        return res;
    }

可以看到在WMS中,做了很多的判斷蜘澜,顯示判斷對應(yīng)的權(quán)限施流,如果不滿足則直接return到ViewRootImpl,如果滿足權(quán)限鄙信,則在mWindowMap中去匹配params.token值瞪醋,如果不滿足,則return對應(yīng)的錯誤装诡。都沒問題則開始添加Window银受。
而在ViewRootImpl則有判斷對應(yīng)的返回值來報錯

  public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
      ...
      res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
      if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    switch (res) {
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token " + attrs.token
                                    + " is exiting");
                       ...
                }

可以看到ViewRootImpl會根據(jù)WMS檢測token返回對應(yīng)的情況践盼,再去判斷是否報錯。

ViewRootImpl 和View的mAttachInfo

token與View的綁定宾巍,前面講到的token則是綁定在對應(yīng)的Window宏侍,而對于View而言,它的所有綁定信息都是存在一個靜態(tài)內(nèi)部類AttachInfo中
在ViewRootImpl的創(chuàng)建中蜀漆,可以看到

public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        ...
        mWindow = new W(this);
        ...
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
        ...
    }

可以看到谅河,這里傳遞了mWindow和mWindowSession,而這里賦值的mWindow對象确丢,是通過new W(ViewRootImpl v)創(chuàng)建出來的绷耍,有留意的話,會發(fā)現(xiàn)在向WMS請求添加窗口鲜侥,也就是在addToDisplay中褂始,傳遞了mWindow這個參數(shù)

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);

這個mWindow也可以說是token,可以通過mWindow.asBinder()拿到描函。它是WMS回調(diào)的接口崎苗。

 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;
        }
        ...
 }

在AttachInfo的構(gòu)造參數(shù)中

final static class AttachInfo {
        final IWindowSession mSession;
        final IWindow mWindow;
        final IBinder mWindowToken;
        AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
            mSession = session;
            mWindow = window;
            mWindowToken = window.asBinder();
            mDisplay = display;
            mViewRootImpl = viewRootImpl;
            mHandler = handler;
            mRootCallbacks = effectPlayer;
        }
        ....
}

IWindow mWindow:WMS回調(diào)應(yīng)用程序的Binder接口
WindowSession mSession : 就是訪問wms的Binder接口
IBinder mWindowToken : 它的賦值是window.asBinder,代表的是W對象舀寓,IWindow是通過new W創(chuàng)建的胆数。mWindowToken也是WMS和應(yīng)用程序交互的Binder接口。獲取到后就可以通過view.getWindowToken獲取

可以說AttachInfo代表了一系列綁定的狀態(tài)信息互墓,接著通過ViewRootImpl賦值到每個Window上的View上必尼,如何賦值呢?
在ViewRootImpl的setView過程中篡撵,調(diào)用到了View的繪制performTraversals判莉,這些前幾篇有講過,在這個方法中

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        ...
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        ...
}

而在View這個方法中

 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //System.out.println("Attached! " + this);
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        ...
 }

會判斷是否是ViewGroup育谬,如果是券盅,則調(diào)用到ViewGroup里面的方法

  void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
        super.dispatchAttachedToWindow(info, visibility);
        mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
        }
       ...
  }

可以看到在這個方法中遍歷調(diào)用了dispatchAttachedToWindow去賦值A(chǔ)ttachInfo,而這些AttachInfo在同一個ViewGroup則是相同的值膛檀。之后View就獲得了這些綁定信息锰镀。

Dialog

好了,做了那么多鋪墊宿刮,可以開始看其他窗口了互站。
先看下Dialog的創(chuàng)建

 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == 0) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

我們知道,關(guān)于Dialog的創(chuàng)建過程中要傳入?yún)?shù)Activity僵缺,主要是Dialog的創(chuàng)建過程與Activity相似胡桃,它同時也需要一些主題資源也就是ContextThemeWrapper,但是Dialog只是一個類磕潮,它并沒有繼承于ContextThemeWrapper翠胰,顧也就需要繼承于ContextThemeWrapper的Activity來結(jié)合使用剂府。

可以看到畔况,在上面的構(gòu)造方法中十性,傳入的Context的是Activity的Context熊尉,接著獲取了一個WindowManager,這里的WindowManager是通過context.getSystemService(Context.WINDOW_SERVICE)锻狗,而這里的Context是Activity满力,我們看下Activity里面這個方法

 @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        ...
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

可以看到這里返回的WindowManager也就是Activity的WindowManager。
接著創(chuàng)建了一個新的Window轻纪,類型是PhoneWindow油额,與Activity的Window相比,是不同的對象刻帚。Dialog與Activity潦嘶,同個WindowManager,不同Window。接著設(shè)置了Callback接口回調(diào)崇众,這也是Dialog能夠接受到按鍵事件的原因掂僵。接著調(diào)用setWindowManager設(shè)置到Window中。注意這個方法參數(shù):這里第二個參數(shù)傳遞的是null顷歌,也就是token為null

public void setWindowManager(WindowManager wm, IBinder appToken, String appName){
    ...
}

token居然為null锰蓬,那么Dialog到底是如何依附在Activity上的,我們看下show方法

public void show() {
        ...
        if (!mCreated) {
            dispatchOnCreate(null);
        }

        onStart();
        mDecor = mWindow.getDecorView();
        ...
        WindowManager.LayoutParams l = mWindow.getAttributes();
        ...
        try {
            mWindowManager.addView(mDecor, l);
            mShowing = true;
   
            sendShowMessage();
        } finally {
        }
    }

可以看到衙吩,show方法會先調(diào)用dispatchOnCreate來創(chuàng)建互妓,最后會調(diào)用到onCreate

/**
     * Similar to {@link Activity#onCreate}, you should initialize your dialog
     * in this method, including calling {@link #setContentView}.
     * @param savedInstanceState If this dialog is being reinitalized after a
     *     the hosting activity was previously shut down, holds the result from
     *     the most recent call to {@link #onSaveInstanceState}, or null if this
     *     is the first time.
     */
    protected void onCreate(Bundle savedInstanceState) {
    }

可以看到,這個跟Activity相似坤塞,只不過在這里Dialog是空的,但是它的子類澈蚌,AlertDialog這些都是重寫了它摹芙,既然與Activity相似,那也就需要setContentView(這個很重要)了宛瞄。
接著調(diào)用到

mDecor = mWindow.getDecorView();

這個mWindow就是前面創(chuàng)建的PhoneWindow實例浮禾,前面明明沒創(chuàng)建DecorView啊,為啥這里Window能得到DecorView啊份汗。咦盈电,沒錯,你可能會猜到是在setContentView中創(chuàng)建的杯活,因為這點跟Activity很像匆帚。

 public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
    }

可以看到這里調(diào)用到了window.setContentView,而在第二篇文章也講過吸重,Activity.setContentView,實際上也是調(diào)用到了window.setContentView颜矿,在它的實現(xiàn)類PhoneWindow.setContentView中就會創(chuàng)建DecorView了。

接著到了

 WindowManager.LayoutParams l = mWindow.getAttributes();

看下getAttributes

 public final WindowManager.LayoutParams getAttributes() {
        return mWindowAttributes;
    }

mWindowAttributes則是Window的一個成員變量

private final WindowManager.LayoutParams mWindowAttributes =
        new WindowManager.LayoutParams();

可以看到這里是個默認(rèn)創(chuàng)建

public LayoutParams() {
            super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
            type = TYPE_APPLICATION;
            format = PixelFormat.OPAQUE;
        }

可以看到這里的類型是應(yīng)用程序類型骑疆,對應(yīng)WindowManager.LayoutParams說明是

//WindowType:普通應(yīng)用程序窗口替废,token必須設(shè)置為Activity的token來指定窗口屬于誰
public static final int TYPE_APPLICATION        = 2;

接著調(diào)用到了 mWindowManager.addView(mDecor, l); 不過這里調(diào)用到的是Activity的WindowManager封断,之后就到了WindowGlobal.addView舶担,ViewRootImpl.setView,addToDisplay柄瑰。和Activity的添加一致教沾。也就是說因為傳入的參數(shù)是Activity的context授翻,使得在添加窗口的時調(diào)用的是Activity的WindowManager孙咪,而Activity的WindowManager則保存了對應(yīng)的token翎蹈,所以Dialog才可以被添加荤堪。如果此時傳遞的是getApplication或者是Service澄阳,則在ViewRootImpl.setView中會報錯,找不到對應(yīng)的token碎赢,這也就是我們設(shè)置Dialog的時候要傳遞Activity的原因揩抡。

非Activity報錯

Toast

講完了Dialog就來講講一個系統(tǒng)窗口Toast蕊唐,它與Dialog替梨,Activity不同。我們從平常用法開始

Toast.makeText(MainActivity.this , "Hohohong" , Toast.LENGTH_SHORT);

在Toast的makeText方法中

/**
     * Make a standard toast that just contains a text view.
     *
     * @param context  The context to use.  Usually your {@link android.app.Application}
     *                 or {@link android.app.Activity} object.
     * @param text     The text to show.  Can be formatted text.
     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
     *                 {@link #LENGTH_LONG}
     *
     */
    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        Toast result = new Toast(context);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);
        
        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }

可以看到這里Context的說明允許Application或者Activity了,接著創(chuàng)建Toast對象挽鞠,實例化默認(rèn)的布局信认。
我們看下構(gòu)造方法

   public Toast(Context context) {
        mContext = context;
        mTN = new TN();
        mTN.mY = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.toast_y_offset);
        mTN.mGravity = context.getResources().getInteger(
                com.android.internal.R.integer.config_toastDefaultGravity);
    }

在構(gòu)造方法中嫁赏,創(chuàng)建了TN對象潦蝇,這個TN又是什么

 private static class TN extends ITransientNotification.Stub {
        final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

        final Runnable mHide = new Runnable() {
            @Override
            public void run() {
                handleHide();
                // Don't do this in handleHide() because it is also invoked by handleShow()
                mNextView = null;
            }
        };

        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        ...

        WindowManager mWM;
        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            ...
        }

        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }
       /**
         * schedule handleHide into the right thread
         */
        @Override
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }
        ....
 }

可以看到TN是一個Binder對象,用來跨進(jìn)程調(diào)用的持灰,里面封裝了show,hide方法返十,可以說洞坑,Toast的show迟杂,hide實際上就是調(diào)用了TN里面的方法排拷。為什么這么說的监氢,我們看下Toast的show方法浪腐。此外议街,注意WindowManager.LayoutParams.type,這里的Type是WindowManager.LayoutParams.TYPE_TOAST吧雹,表示系統(tǒng)窗口吮炕。

Toast.show()

 public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

Toast的show方法中龙亲,會先判斷mNextView是否為空鳄炉,這個mNextView是在Toast.makeText的時候創(chuàng)建賦值出來的拂盯。接著通過getService拿到NotificationManagerService的訪問接口谈竿,接著把TN空凸,包信息呀洲,以及設(shè)置時常發(fā)送到NotificationManagerService.enqueueToast中道逗,我們看下這個方法

 @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
           ...
            synchronized (mToastQueue) {
                ...
                try {
                    ToastRecord record;
                    //從當(dāng)前隊列檢查是否已經(jīng)添加過了
                    int index = indexOfToastLocked(pkg, callback);
                    if (index >= 0) {
                        //添加過滓窍,則直接獲取
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {
                        ...
                        //否則重新創(chuàng)建個添加到隊列
                        record = new ToastRecord(callingPid, pkg, callback, duration);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveLocked(callingPid);
                    }
                    ...
                    if (index == 0) {
                        //開始顯示
                        showNextToastLocked();
                    }
                }
               ...
            }

可以看到坏平,在enqueueToast中锦亦,首先會調(diào)用indexOfToastLocked來判斷當(dāng)前的TN也就是callback是否在隊列中杠园,如果有抛蚁,則在mToastQueue中直接獲取瞧甩,更新時間。否則爷辙,則重新創(chuàng)建一個帶有TN,時間,包信息的ToastRecord血当,再添加到隊列臊旭。最后調(diào)用了showNextToastLocked顯示

void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            ...
            try {
                record.callback.show();
                scheduleTimeoutLocked(record);
                return;
            } 
            ...
    }

在showNextToastLocked中巍扛,先獲取當(dāng)前隊列最前的ToastRecord,再調(diào)用recoed.callback.show()喊括,這里的callback郑什,就是前面我們傳入的TN對象蘑拯,也就是說調(diào)用到我們Toast中TN的show方法申窘。

  @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.post(mShow);
        }
  final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                handleShow();
            }
        };

最終執(zhí)行方法則是handleShow

 public void handleShow() {
            ...
            if (mView != mNextView) {
                //移除之前的View
                handleHide();
                mView = mNextView;
                ...
                mWM =(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                ...
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }

在handleHide中碎捺,會先移除之前的View收厨,然后把mNextView的值賦值給mView诵叁,前面也說到了拧额,這個mNextView就是我們要顯示的內(nèi)容势腮。接著獲取WindowManager捎拯,調(diào)用addView請求WMS添加到窗口上署照,而因為是系統(tǒng)窗口,所以token為Null也是可以顯示禁荸。

執(zhí)行完show方法后赶熟,Toast已經(jīng)顯示出來了映砖,后面還調(diào)用了scheduleTimeoutLocked方法邑退,沒錯地技,這個就是來限制顯示時間的乓土。

    private void scheduleTimeoutLocked(ToastRecord r)
    {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
        mHandler.sendMessageDelayed(m, delay);
    }

可以看到它把ToastRecode封裝到Message趣苏,而你設(shè)置你時間則設(shè)置為Handler的delay時間尽棕,到達(dá)指定時間滔悉,則發(fā)送帶Handler去回官,我們看下mHandler里面what為MESSAGE_TIMEOUT的執(zhí)行情況歉提。

        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case MESSAGE_TIMEOUT:
                    handleTimeout((ToastRecord)msg.obj);
                    break;
                ...
            }
        }

可以看到到達(dá)指定時間后調(diào)用了handleTimeout苔巨,接下來調(diào)用的方法肯定是來取消Toast顯示的。

    private void handleTimeout(ToastRecord record)
    {
        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
        synchronized (mToastQueue) {
            int index = indexOfToastLocked(record.pkg, record.callback);
            if (index >= 0) {
                cancelToastLocked(index);
            }
        }
    }

繼續(xù)看

void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();
        } 
        ...
        mToastQueue.remove(index);
        keepProcessAliveLocked(record.pid);
        if (mToastQueue.size() > 0) {
            // Show the next one. If the callback fails, this will remove
            // it from the list, so don't assume that the list hasn't changed
            // after this point.
            showNextToastLocked();
        }
    }

可以看到悼尾,到達(dá)指定時間后诀豁,最終調(diào)用了TN的hide方法,然后移除隊列烹骨,如果隊列中還有其他的沮焕,則繼續(xù)顯示其他的峦树。

同理魁巩,看下hide方法

 @Override
        public void hide() {
            if (localLOGV) Log.v(TAG, "HIDE: " + this);
            mHandler.post(mHide);
        }

  final Runnable mHide = new Runnable() {
            @Override
            public void run() {
                handleHide();
                // Don't do this in handleHide() because it is also invoked by handleShow()
                mNextView = null;
            }
        };


  public void handleHide() {
            if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
            if (mView != null) {
                // note: checking parent() just to make sure the view has
                // been added...  i have seen cases where we get here when
                // the view isn't yet added, so let's try not to crash.
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }

                mView = null;
            }
        }

可以看到hide方法最終調(diào)用了windowManager.removeView來取消顯示。這也從另一方面看到看WindowManager的重要性。

小結(jié)

  • WindowManager.LayoutParams中有三種類型集晚,分別為

    • **應(yīng)用程序窗口 : **type值在 FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW 須將token設(shè)置成Activity的token偷拔。
      eg: 前面介紹的Activity窗口,Dialog

    • 子窗口: type值在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows與頂層窗口相關(guān)聯(lián)条摸,需將token設(shè)置成它所附著宿主窗口的token
      eg: PopupWindow(想要依附在Activity上需要將token設(shè)置成Activity的token)

    • **系統(tǒng)窗口: type值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW ** SystemWindows不能用于應(yīng)用程序顷啼,使用時需要有特殊權(quán)限钙蒙,它是特定的系統(tǒng)功能才能使用躬厌。
      eg: Toast扛施,輸入法等疙渣。

  • 對于Activity里面ActivityRecord的token泼菌,它間接標(biāo)識了一個Activity哗伯。想要依附在Activity上需要將token設(shè)置成Activity的token笋颤,接著傳到WMS中判斷返回ViewRootImpl去判斷報錯。

  • View的綁定信息通過它的靜態(tài)內(nèi)部類AttachInfo在ViewRootImpl中綁定

  • Dialog中非凌,與Activity共用同個WindowManager敞嗡,但是他們兩者的Window并不相同喉悴。可以說一個Window可以對應(yīng)一個Activity勺像,但一個Activity不一定對應(yīng)一個Window吟宦,它也有可能對應(yīng)Dialog

五篇窗口機(jī)制總結(jié)

  • 了解掌握了Android中的窗口分類
  • 懂得Window袁波,PhoneWindow锋叨,WindowManager,WindowManagerGlobal它們的分類區(qū)別作用
  • 掌握View的真正創(chuàng)建繪制
  • 熟悉了ViewRoot和View樹機(jī)制
  • View的綁定信息,token

小感悟

五篇文章寫了一個星期了吧,挺久的了吆倦。一開始從一個小例子一步一步摸索蚕泽,順蔓摸瓜出這么多知識,對于自己來說掌握的也相對全面了荒吏,也逐漸對源碼越來越感興趣了绰更。以前對于View繪制窗口這些只懂怎么用,但不知道為什么徐钠?可以說丹皱,現(xiàn)在自己在用的時候摊崭,不妨多問自己個為什么矮台,再去摸索摸索瘦赫,才能更快的成長也說不定哦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末校辩,一起剝皮案震驚了整個濱河市辆童,隨后出現(xiàn)的幾起案子宜咒,更是在濱河造成了極大的恐慌,老刑警劉巖把鉴,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件故黑,死亡現(xiàn)場離奇詭異,居然都是意外死亡庭砍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門逗威,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峰搪,“玉大人,你說我怎么就攤上這事凯旭「懦埽” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵罐呼,是天一觀的道長鞠柄。 經(jīng)常有香客問我,道長嫉柴,這世上最難降的妖魔是什么厌杜? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上夯尽,老公的妹妹穿的比我還像新娘瞧壮。我一直安慰自己,他們只是感情好匙握,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布咆槽。 她就那樣靜靜地躺著,像睡著了一般圈纺。 火紅的嫁衣襯著肌膚如雪秦忿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天蛾娶,我揣著相機(jī)與錄音灯谣,去河邊找鬼。 笑死蛔琅,一個胖子當(dāng)著我的面吹牛胎许,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罗售,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼呐萨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了莽囤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤切距,失蹤者是張志新(化名)和其女友劉穎朽缎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谜悟,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡话肖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了葡幸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片最筒。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蔚叨,靈堂內(nèi)的尸體忽然破棺而出床蜘,到底是詐尸還是另有隱情,我是刑警寧澤蔑水,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布邢锯,位于F島的核電站,受9級特大地震影響搀别,放射性物質(zhì)發(fā)生泄漏丹擎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒂培。 院中可真熱鬧再愈,春花似錦、人聲如沸护戳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽灸异。三九已至府适,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肺樟,已是汗流浹背檐春。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留么伯,地道東北人疟暖。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像田柔,于是被迫代替她去往敵國和親俐巴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

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