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

Android窗口機制系列

Android窗口機制(一)初識Android的窗口結構
Android窗口機制(二)Window,PhoneWindow,DecorView,setContentView源碼理解
Android窗口機制(三)Window和WindowManager的建立與Activity
Android窗口機制(四)ViewRootImpl與View和WindowManager
Android窗口機制(五)最終章:WindowManager.LayoutParams和Token以及其餘窗口Dialog,Toastandroid

前面幾篇文章基本介紹完Activity上的窗口機制,可是咱們常見的窗口就還有Dialog,Toast這些,本篇文章就來介紹這兩個的窗口機制以及WindowManager.LayoutParams和Tokenwindows

WindowManager.LayoutParams

首先,先跟你們介紹這個WindowManager.LayoutParams,在前面幾篇文章中,都有出現過這個LayoutParams,咱們看下具體的源碼。
翻譯參考安全

public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
        //窗口的絕對XY位置,須要考慮gravity屬性
        public int x;
        public int y;
        //在橫縱方向上爲相關的View預留多少擴展像素,若是是0則此view不能被拉伸,其餘狀況下擴展像素被widget均分
        public float horizontalWeight;
        public float verticalWeight;
        //窗口類型
        //有3種主要類型以下:
        //ApplicationWindows取值在FIRST_APPLICATION_WINDOW與LAST_APPLICATION_WINDOW之間,是經常使用的頂層應用程序窗口,須將token設置成Activity的token;
        //SubWindows取值在FIRST_SUB_WINDOW和LAST_SUB_WINDOW之間,與頂層窗口相關聯,需將token設置成它所附着宿主窗口的token;
        //SystemWindows取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW之間,不能用於應用程序,使用時須要有特殊權限,它是特定的系統功能才能使用;
        public int type;

        //WindowType:開始應用程序窗口
        public static final int FIRST_APPLICATION_WINDOW = 1;
        //WindowType:全部程序窗口的base窗口,其餘應用程序窗口都顯示在它上面
        public static final int TYPE_BASE_APPLICATION   = 1;
        //WindowType:普通應用程序窗口,token必須設置爲Activity的token來指定窗口屬於誰
        public static final int TYPE_APPLICATION        = 2;
        //WindowType:應用程序啓動時所顯示的窗口,應用本身不要使用這種類型,它被系統用來顯示一些信息,直到應用程序能夠開啓本身的窗口爲止
        public static final int TYPE_APPLICATION_STARTING = 3;
        //WindowType:結束應用程序窗口
        public static final int LAST_APPLICATION_WINDOW = 99;

        //WindowType:SubWindows子窗口,子窗口的Z序和座標空間都依賴於他們的宿主窗口
        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:應用程序窗口的子面板,顯示於全部面板窗口的上層
        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:媒體信息,顯示在媒體層和程序窗口之間,須要實現半透明效果
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;
        //WindowType:子窗口結束
        public static final int LAST_SUB_WINDOW         = 1999;

        //WindowType:系統窗口,非應用程序建立
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        //WindowType:狀態欄,只能有一個狀態欄,位於屏幕頂端,其餘窗口都位於它下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        //WindowType:搜索欄,只能有一個搜索欄,位於屏幕上方
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        //WindowType:電話窗口,它用於電話交互(特別是呼入),置於全部應用程序之上,狀態欄之下
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        //WindowType:系統提示,出如今應用程序窗口之上
        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:系統頂層窗口,顯示在其餘一切內容之上,此窗口不能得到輸入焦點,不然影響鎖屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //WindowType:電話優先,當鎖屏時顯示,此窗口不能得到輸入焦點,不然影響鎖屏
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        //WindowType:系統對話框
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        //WindowType:鎖屏時顯示的對話框
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        //WindowType:系統內部錯誤提示,顯示於全部內容之上
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        //WindowType:內部輸入法窗口,顯示於普通UI之上,應用程序可從新佈局以避免被此窗口覆蓋
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        //WindowType:內部輸入法對話框,顯示於當前輸入法窗口之上
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        //WindowType:牆紙窗口
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        //WindowType:狀態欄的滑動面板
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        //WindowType:安全系統覆蓋窗口,這些窗戶必須不帶輸入焦點,不然會干擾鍵盤
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        //WindowType:拖放僞窗口,只有一個阻力層(最多),它被放置在全部其餘窗口上面
        public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;
        //WindowType:狀態欄下拉麪板
        public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
        //WindowType:鼠標指針
        public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;
        //WindowType:導航欄(有別於狀態欄時)
        public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;
        //WindowType:音量級別的覆蓋對話框,顯示當用戶更改系統音量大小
        public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;
        //WindowType:起機進度框,在一切之上
        public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;
        //WindowType:假窗,消費導航欄隱藏時觸摸事件
        public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;
        //WindowType:夢想(屏保)窗口,略高於鍵盤
        public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
        //WindowType:導航欄面板(不一樣於狀態欄的導航欄)
        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:顯示窗口覆蓋,用於模擬輔助顯示設備
        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:系統窗口結束
        public static final int LAST_SYSTEM_WINDOW      = 2999;

        //MemoryType:窗口緩衝位於主內存
        public static final int MEMORY_TYPE_NORMAL = 0;
        //MemoryType:窗口緩衝位於能夠被DMA訪問,或者硬件加速的內存區域
        public static final int MEMORY_TYPE_HARDWARE = 1;
        //MemoryType:窗口緩衝位於可被圖形加速器訪問的區域
        public static final int MEMORY_TYPE_GPU = 2;
        //MemoryType:窗口緩衝不擁有本身的緩衝區,不能被鎖定,緩衝區由本地方法提供
        public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;

        //指出窗口所使用的內存緩衝類型,默認爲NORMAL 
        public int memoryType;

        //Flag:當該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以上已經放棄這種毛玻璃效果)
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        //Flag:讓window不能得到焦點,這樣用戶快就不能向該window發送按鍵事
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //Flag:讓該window不接受觸摸屏事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //Flag:即便在該window在可得到焦點狀況下,依舊把該window以外的任何event發送到該window以後的其餘window
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //Flag:當手機處於睡眠狀態時,若是屏幕被按下,那麼該window將第一個收到
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        //Flag:當該window對用戶可見時,讓設備屏幕處於高亮(bright)狀態
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
        //Flag:讓window佔滿整個手機屏幕,不留任何邊界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //Flag:window大小再也不不受手機屏幕大小限制,即window可能超出屏幕以外
        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
        //Flag:window全屏顯示
        public static final int FLAG_FULLSCREEN      = 0x00000400;
        //Flag:恢復window非全屏顯示
        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
        //Flag:開啓抖動(dithering)
        public static final int FLAG_DITHER             = 0x00001000;
        //Flag:當該window在進行顯示的時候,不容許截屏
        public static final int FLAG_SECURE             = 0x00002000;
        //Flag:一個特殊模式的佈局參數用於執行擴展表面合成時到屏幕上
        public static final int FLAG_SCALED             = 0x00004000;
        //Flag:用於windows時,常常會使用屏幕用戶持有反對他們的臉,它將積極過濾事件流,以防止意外按在這種狀況下,可能不須要爲特定的窗口,在檢測到這樣一個事件流時,應用程序將接收取消運動事件代表,這樣應用程序能夠處理這相應地採起任何行動的事件,直到手指釋放
        public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
        //Flag:一個特殊的選項只用於結合FLAG_LAYOUT_IN_SC
        public static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;
        //Flag:轉化的狀態FLAG_NOT_FOCUSABLE對這個窗口當前如何進行交互的方法
        public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;
        //Flag:若是你設置了該flag,那麼在你FLAG_NOT_TOUNCH_MODAL的狀況下,即便觸摸屏事件發送在該window以外,其事件被髮送到了後面的window,那麼該window仍然將以MotionEvent.ACTION_OUTSIDE形式收到該觸摸屏事件
        public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
        //Flag:當鎖屏的時候,顯示該window
        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
        //Flag:在該window後顯示系統的牆紙
        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
        //Flag:當window被顯示的時候,系統將把它當作一個用戶活動事件,以點亮手機屏幕
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
        //Flag:消失鍵盤
        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
        //Flag:當該window在能夠接受觸摸屏狀況下,讓因在該window以外,而發送到後面的window的觸摸屏能夠支持split touch
        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
        //Flag:對該window進行硬件加速,該flag必須在Activity或Dialog的Content View以前進行設置
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //Flag:讓window佔滿整個手機屏幕,不留任何邊界
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        //Flag:請求一個半透明的狀態欄背景以最小的系統提供保護
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        //Flag:請求一個半透明的導航欄背景以最小的系統提供保護
        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;

        //行爲選項標記
        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;

        //私有的行爲選項標記
        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:用於描述軟鍵盤顯示規則的bite的mask
        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
        //SOFT_INPUT:沒有軟鍵盤顯示的約定規則
        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
        //SOFT_INPUT:可見性狀態softInputMode,請不要改變軟輸入區域的狀態
        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
        //SOFT_INPUT:用戶導航(navigate)到你的窗口時隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
        //SOFT_INPUT:老是隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
        //SOFT_INPUT:用戶導航(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調整方式的bite的mask
        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
        //SOFT_INPUT:不指定顯示軟件盤時,window的調整方式
        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
        //SOFT_INPUT:當顯示軟鍵盤時,調整window內的控件大小以便顯示軟鍵盤
        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
        //SOFT_INPUT:當顯示軟鍵盤時,調整window的空白區域來顯示軟鍵盤,即便調整空白區域,軟鍵盤仍是有可能遮擋一些有內容區域,這時用戶就只有退出軟鍵盤才能看到這些被遮擋區域並進行
        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
        //SOFT_INPUT:當顯示軟鍵盤時,不調整window的佈局
        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
        //SOFT_INPUT:用戶導航(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繪圖表面和窗口之間的內容
        public final Rect surfaceInsets = new Rect();
        //指望的位圖格式,默認爲不透明,參考android.graphics.PixelFormat
        public int format;
        //窗口所使用的動畫設置,它必須是一個系統資源而不是應用程序資源,由於窗口管理器不能訪問應用程序
        public int windowAnimations;
        //整個窗口的半透明值,1.0表示不透明,0.0表示全透明
        public float alpha = 1.0f;
        //當FLAG_DIM_BEHIND設置後生效,該變量指示後面的窗口變暗的程度,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;
        //用來覆蓋用戶設置的屏幕亮度,表示應用用戶設置的屏幕亮度,從0到1調整亮度從暗到最亮發生變化
        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;
        //定義出入境動畫在這個窗口旋轉設備時使用
        public int rotationAnimation = ROTATION_ANIMATION_ROTATE;

        //窗口的標示符
        public IBinder token = null;
        //此窗口所在的包名
        public String packageName = null;
        //屏幕方向
        public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
        //首選的刷新率的窗口
        public float preferredRefreshRate;
        //控制status bar是否顯示
        public int systemUiVisibility;
        //ui能見度所請求的視圖層次結構
        public int subtreeSystemUiVisibility;
        //獲得關於系統ui能見度變化的回調
        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,對應爲session

  • **應用程序窗口 : **type值在 FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW 須將token設置成Activity的token
    eg: 前面介紹的Activity窗口,Dialog
  • 子窗口: type值在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows與頂層窗口相關聯,需將token設置成它所附着宿主窗口的token
    eg: PopupWindow(想要依附在Activity上須要將token設置成Activity的token)
  • **系統窗口: type值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW ** SystemWindows不能用於應用程序,使用時須要有特殊權限,它是特定的系統功能才能使用。
    eg: Toast,輸入法等。

WindowManager.LayoutParams源碼中也講到輸入法的問題,裏面有不少種模式,經過設置softInputMode來調整輸入法。這裏舉個常見例子吧,平時咱們在Activity的底部放置EditText的時候,輸入法的彈出可能會遮擋住界面。
這裏經過設置相應的softInputMode就能夠解決這個問題app

<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>

或者ide

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

另外,三種類型裏面出現了個概念,就是token問題。
在應用程序窗口中,token是用來標識Activity的,一個Activity就對應一個token令牌
而在子窗口中,某個子窗口想要依附在對應的宿主窗口上設置要將token設置爲對應宿主窗口的token。佈局

token

token是用來表示窗口的一個令牌,只有符合條件的token才能被WMS經過添加到應用上。
咱們來看下token的傳遞過程post

首先對於Activity裏面的token,它的建立則是在AMS啓動Activity開始的,以後保存在ActivityRecord.appToken中。而對於Activity中的token綁定到對應的Window上
咱們知道,應用程序窗口的Activity窗口Window是在Activity建立過程當中建立的,具體是在activity.attach方法中建立的。動畫

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);
        //設置軟鍵盤
        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中ui

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上,同時在當前Window上建立了WindowManager。

在將DecorView添加到WindowManager時候,會調用到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設置值。不過在這之前要判斷parentWindow是否爲null。

  • 若是是應用程序窗口的話,這個parentWindow就是activity的window
  • 若是是子窗口的話,這個parentWindow就是activity的window
  • 若是是系統窗口的話,那個parentWindow就是null

這個parentWindow則是在建立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來判斷當前窗口的類型,若是是子窗口類型,則wp.token = decor.getWindowToken();這裏賦值的是父窗口的W對象。關於W對象在下面講解。
若是是應用程序窗口,則走分支。通常應用程序窗口的話,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中,以後調用到了ViewRootImpl.setView方法中的mWindowSession的addToDisplay方法,該方法用來請求WMS添加Window
mWindowSession的類型是IWindowSession它的實現類是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];
        //判斷權限
        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中,作了不少的判斷,顯示判斷對應的權限,若是不知足則直接return到ViewRootImpl,若是知足權限,則在mWindowMap中去匹配params.token值,若是不知足,則return對應的錯誤。都沒問題則開始添加Window。
而在ViewRootImpl則有判斷對應的返回值來報錯

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會根據WMS檢測token返回對應的狀況,再去判斷是否報錯。

ViewRootImpl 和View的mAttachInfo

token與View的綁定,前面講到的token則是綁定在對應的Window,而對於View而言,它的全部綁定信息都是存在一個靜態內部類AttachInfo中
在ViewRootImpl的建立中,能夠看到

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)建立出來的,有留意的話,會發如今向WMS請求添加窗口,也就是在addToDisplay中,傳遞了mWindow這個參數

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

這個mWindow也能夠說是token,能夠經過mWindow.asBinder()拿到。它是WMS回調的接口。

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的構造參數中

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回調應用程序的Binder接口
WindowSession mSession : 就是訪問wms的Binder接口
IBinder mWindowToken : 它的賦值是window.asBinder,表明的是W對象,IWindow是經過new W建立的。mWindowToken也是WMS和應用程序交互的Binder接口。獲取到後就能夠經過view.getWindowToken獲取

能夠說AttachInfo表明了一系列綁定的狀態信息,接着經過ViewRootImpl賦值到每一個Window上的View上,如何賦值呢?
在ViewRootImpl的setView過程當中,調用到了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,若是是,則調用到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()));
        }
       ...
  }

能夠看到在這個方法中遍歷調用了dispatchAttachedToWindow去賦值AttachInfo,而這些AttachInfo在同一個ViewGroup則是相同的值。以後View就得到了這些綁定信息。

Dialog

好了,作了那麼多鋪墊,能夠開始看其餘窗口了。
先看下Dialog的建立

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

咱們知道,關於Dialog的建立過程當中要傳入參數Activity,主要是Dialog的建立過程與Activity類似,它同時也須要一些主題資源也就是ContextThemeWrapper,可是Dialog只是一個類,它並無繼承於ContextThemeWrapper,顧也就須要繼承於ContextThemeWrapper的Activity來結合使用。

能夠看到,在上面的構造方法中,傳入的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。
接着建立了一個新的Window,類型是PhoneWindow,與Activity的Window相比,是不一樣的對象。Dialog與Activity,同個WindowManager,不一樣Window。接着設置了Callback接口回調,這也是Dialog可以接受到按鍵事件的緣由。接着調用setWindowManager設置到Window中。注意這個方法參數:這裏第二個參數傳遞的是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方法會先調用dispatchOnCreate來建立,最後會調用到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(這個很重要)了。
接着調用到

mDecor = mWindow.getDecorView();

這個mWindow就是前面建立的PhoneWindow實例,前面明明沒建立DecorView啊,爲啥這裏Window能獲得DecorView啊。咦,沒錯,你可能會猜到是在setContentView中建立的,由於這點跟Activity很像。

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

能夠看到這裏調用到了window.setContentView,而在第二篇文章也講過,Activity.setContentView,實際上也是調用到了window.setContentView,在它的實現類PhoneWindow.setContentView中就會建立DecorView了。

接着到了

WindowManager.LayoutParams l = mWindow.getAttributes();

看下getAttributes

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

mWindowAttributes則是Window的一個成員變量

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

能夠看到這裏是個默認建立

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

能夠看到這裏的類型是應用程序類型,對應WindowManager.LayoutParams說明是

//WindowType:普通應用程序窗口,token必須設置爲Activity的token來指定窗口屬於誰
public static final int TYPE_APPLICATION        = 2;

接着調用到了 mWindowManager.addView(mDecor, l); 不過這裏調用到的是Activity的WindowManager,以後就到了WindowGlobal.addView,ViewRootImpl.setView,addToDisplay。和Activity的添加一致。也就是說由於傳入的參數是Activity的context,使得在添加窗口的時調用的是Activity的WindowManager,而Activity的WindowManager則保存了對應的token,因此Dialog才能夠被添加。若是此時傳遞的是getApplication或者是Service,則在ViewRootImpl.setView中會報錯,找不到對應的token,這也就是咱們設置Dialog的時候要傳遞Activity的緣由。

非Activity報錯

Toast

講完了Dialog就來說講一個系統窗口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了,接着建立Toast對象,實例化默認的佈局。
咱們看下構造方法

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

在構造方法中,建立了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對象,用來跨進程調用的,裏面封裝了show,hide方法,能夠說,Toast的show,hide實際上就是調用了TN裏面的方法。爲何這麼說的,咱們看下Toast的show方法。此外,注意WindowManager.LayoutParams.type,這裏的Type是WindowManager.LayoutParams.TYPE_TOAST,表示系統窗口。

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的時候建立賦值出來的。接着經過getService拿到NotificationManagerService的訪問接口,接着把TN,包信息,以及設置時常發送到NotificationManagerService.enqueueToast中,咱們看下這個方法

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

能夠看到,在enqueueToast中,首先會調用indexOfToastLocked來判斷當前的TN也就是callback是否在隊列中,若是有,則在mToastQueue中直接獲取,更新時間。不然,則從新建立一個帶有TN,時間,包信息的ToastRecord,再添加到隊列。最後調用了showNextToastLocked顯示

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

在showNextToastLocked中,先獲取當前隊列最前的ToastRecord,再調用recoed.callback.show(),這裏的callback,就是前面咱們傳入的TN對象,也就是說調用到咱們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();
            }
        };

最終執行方法則是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就是咱們要顯示的內容。接着獲取WindowManager,調用addView請求WMS添加到窗口上,而由於是系統窗口,因此token爲Null也是能夠顯示。

執行完show方法後,Toast已經顯示出來了,後面還調用了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,而你設置你時間則設置爲Handler的delay時間,到達指定時間,則發送帶Handler去,咱們看下mHandler裏面what爲MESSAGE_TIMEOUT的執行狀況。

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

能夠看到到達指定時間後調用了handleTimeout,接下來調用的方法確定是來取消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);
            }
        }
    }

繼續看

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();
        }
    }

能夠看到,到達指定時間後,最終調用了TN的hide方法,而後移除隊列,若是隊列中還有其餘的,則繼續顯示其餘的。

同理,看下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方法最終調用了windowManager.removeView來取消顯示。這也從另外一方面看到看WindowManager的重要性。

小結

  • WindowManager.LayoutParams中有三種類型,分別爲

    • **應用程序窗口 : **type值在 FIRST_APPLICATION_WINDOW ~ LAST_APPLICATION_WINDOW 須將token設置成Activity的token
      eg: 前面介紹的Activity窗口,Dialog

    • 子窗口: type值在 FIRST_SUB_WINDOW ~ LAST_SUB_WINDOW SubWindows與頂層窗口相關聯,需將token設置成它所附着宿主窗口的token
      eg: PopupWindow(想要依附在Activity上須要將token設置成Activity的token)

    • **系統窗口: type值在 FIRST_SYSTEM_WINDOW ~ LAST_SYSTEM_WINDOW ** SystemWindows不能用於應用程序,使用時須要有特殊權限,它是特定的系統功能才能使用。
      eg: Toast,輸入法等。

  • 對於Activity裏面ActivityRecord的token,它間接標識了一個Activity。想要依附在Activity上須要將token設置成Activity的token,接着傳到WMS中判斷返回ViewRootImpl去判斷報錯。

  • View的綁定信息經過它的靜態內部類AttachInfo在ViewRootImpl中綁定

  • Dialog中,與Activity共用同個WindowManager,可是他們二者的Window並不相同。能夠說一個Window能夠對應一個Activity,但一個Activity不必定對應一個Window,它也有可能對應Dialog

五篇窗口機制總結

  • 瞭解掌握了Android中的窗口分類
  • 懂得Window,PhoneWindow,WindowManager,WindowManagerGlobal它們的分類區別做用
  • 掌握View的真正建立繪製
  • 熟悉了ViewRoot和View樹機制
  • View的綁定信息,token

小感悟

五篇文章寫了一個星期了吧,挺久的了。一開始從一個小例子一步一步摸索,順蔓摸瓜出這麼多知識,對於本身來講掌握的也相對全面了,也逐漸對源碼愈來愈感興趣了。之前對於View繪製窗口這些只懂怎麼用,但不知道爲何?能夠說,如今本身在用的時候,不妨多問本身個爲何,再去摸索摸索,才能更快的成長也說不定哦。

 

做者:Hohohong 連接:https://www.jianshu.com/p/bac61386d9bf 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索